- 
                Notifications
    You must be signed in to change notification settings 
- Fork 38.8k
Description
I'm having a difficult time determining the minimal reproduction of this problem, but I can describe the behavior in detail and am available for debugging:
I have an MVC controller written in Groovy that uses traits. This produces a class that implements several interfaces (including the trait itself and some Groovy synthetics), but none of these interfaces is directly relevant to MVC. One of them defines an @Autowired(required = false) setter that is not in use in this case:
@RestController
@RequestMapping(BASE_URL)
class UsersController implements HasIdGenerator {
  public static final String BASE_URL = '/users'
  @PostMapping
  ResponseEntity register(@RequestBody User newUser) {
    // logic
  }
}
This controller is registered properly with RequestMappingHandlerMapping. However, when I add an annotation to register that triggers Spring AOP (specifically, in this case, a @PreAuthorize meta-annotation), the controller disappears from registration.
I've tracked down the immediate problem to RequestMappingHandlerMapping#isHandler(Class). When the Spring Security AOP is active, the argument passed to this method is com.sun.proxy.$Proxy123, and then AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) returns false because the annotation scanner ends up checking the proxy interface and its superinterfaces but not the actual controller class where the controller methods are declared. The result is that all methods that aren't part of an interface API disappear from Spring's visibility of the bean and thence from registration.
So far the logic underlying this failure case appears sound, but I can't figure out why it doesn't usually break; that is, controllers work all the time with Spring Security annotations. I'm suspecting that it has something to do with the decisions of AOP application strategies, but this is getting farther into the infrastructure than I have intuition for. Removing the Groovy trait (and thus the interfaces involved, but not the GroovyObject interface), results in a CGLIB proxy properly exposing all of the controller methods.
Update: On further debugging, it appears that ProxyProcessorSupport explicitly lists groovy.lang.GroovyObject as "not a 'real' interface" for purposes of determining whether a class "looks JDK-proxyable", and it's the addition of the extra interface by the trait that leads #evaluateProxyInterfaces to decide to use a JDK proxy.
The end result appears to be that adding a nontrivial interface to any Spring MVC controller will silently disable its controller methods by means of hiding them from the annotation scan. It seems to me that either classes with @Controller (including a meta) should always be forced to CGLIB, or #isHandler should unwrap proxies (but since this would, I think, break the actual advice itself, that's probably not practical).