Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error reporting for issues related to @Controller types requiring AOP proxing [SPR-11281] #15905

Closed
spring-projects-issues opened this issue Jan 2, 2014 · 15 comments

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Jan 2, 2014

Alex opened SPR-11281 and commented

I've a class marked with @RestController annotation that works fine.

But if this class implements the ApplicationEventPublisherAware interface his methods can't be called any more with the following error:

javax.servlet.ServletException: No adapter for handler [xxx.AdmController@66b714dd]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
	org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1144)
	org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
	org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:931)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:822)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:807)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)

Affects: 4.0 GA

Issue Links:

  • #16043 Ignore container callback and marker interfaces for auto-proxy decisions

Backported to: 3.2.8

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Rossen Stoyanchev commented

Sounds unusual, it's not obvious immediately what would cause this. Could you provide a bit more background? For example could you confirm if the same happens if you use @Controller with @ResponseBody on the method (equivalent to using @RestController). Are controllers decorated with proxies (e.g. to support @Transactional on the controller)? It would help to show relevant details about the controller (type and method annotations and signatures) and perhaps some snippets of relevant configuration.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Rossen Stoyanchev commented

We also have a repository for providing repro projects.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Alex commented

The problem arise also when decorated with @Controller instead of @RestController, and yes, it is proxied because of @Transactional

Apart from that is a really simple class, it should be trivial to reproduce, but if you are unable to do it so I'll try to make a sample project.

I don't know if it's relevant but I use a class implementing WebApplicationInitializer to initialize the dispatcher:

public class WebAppInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext container) {
    XmlWebApplicationContext appContext = new XmlWebApplicationContext();
    appContext.setConfigLocation("/WEB-INF/root-context.xml");

    ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(appContext));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");
  }
}

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Rossen Stoyanchev commented

Thanks, are you using interface or class-based proxing, for example if using <tx:annotation-driven> do you set proxy-target-class="true"? Keep in mind that with interface-based proxying, type and method-level Spring MVC annotations (like @RequestMapping and others) are expected to be on an interface (see section 16.3.2 and search for "proxy").

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Alex commented

I'm understanding correctly I'm using JDK dynamic proxies:

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />

I can't set proxy-target-class to true because otherwise spring-data is not able to generate my repository classes and I get an error of this kind:

Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy1020]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy1020
        at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:211)
        at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:111)
        at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:490)
        at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:375)
        at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:335)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:421) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1698)
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:164)
        ... 100 more
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy1020
        at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
        at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
        at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
        at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
        at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
        at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:317)
        at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57)
        at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:201)
        ... 107 more

So, is this expected to happen?

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Rossen Stoyanchev commented

So, is this expected to happen?

The scenario (@Transactional on a controller with interface-based proxying) is supported but you need to implement it as documented. See the reference I provided above and scroll down to the note titled "@RequestMapping On Interface Methods". If you want to see an example of such a controller, see this one here.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 3, 2014

Alex commented

I'm sorry, neither the example nor the documentation are very clear to me (for example I don't understand how how I could annotate an interface not defined by me but by spring).

I solved this particular case creating another simple object implementing WebApplicationInitializer and then injecting it in the controller, but I really would like to understand better the whole problem.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 14, 2014

Rossen Stoyanchev commented

Sorry for not seeing this response earlier. If you could create a sample project demonstrating the issue and approximating what you have, I can then show you more specifically by updating the example.

Fork the spring-framework-issues repository and copy one of the template projects you prefer, either SPR-0000-war-java or SPR-0000-war-xml and then add your controller and configuration, and submit a pull request.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 20, 2014

Rossen Stoyanchev commented

Postponing to allow for reproduction project to be provided.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 10, 2014

Alex commented

I created a sample project to work on this issue, I'm sorry I don't know git and I don't know how to create a pull request for a branch, anyway you can find the project here:

https://github.com/Polve/spring-framework-issues/tree/SPR-11281/SPR-11281

After running it, trying to access the url:
http://localhost:8080/SPR-11281/test

we get the following error:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class
HandlerMethod details: 
Controller [com.sun.proxy.$Proxy391]
Method [public java.lang.Object org.springframework.issues.Controller1.test(java.lang.String)]
Resolved arguments: 
[0] [null] 

	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)

root cause

java.lang.IllegalArgumentException: object is not an instance of declaring class
HandlerMethod details: 
Controller [com.sun.proxy.$Proxy391]
Method [public java.lang.Object org.springframework.issues.Controller1.test(java.lang.String)]
Resolved arguments: 
[0] [null] 

	org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:218)
	org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
	org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:690)
	org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
	org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
	org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)

root cause

java.lang.IllegalArgumentException: object is not an instance of declaring class
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:483)
	org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:214)
	org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
	org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:690)
	org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
	org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
	org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
	org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 10, 2014

Alex commented

Seems like I have been able to create a pull request:

spring-projects/spring-framework-issues#66

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 10, 2014

Rossen Stoyanchev commented

Great. I've updated the project adding an interface and making the controller implement it. As you'll see with that, the example works. Well it gives the exception "org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' is defined" but that's because there is no transactionManager in the project configuration.

The reason this is necessary is because when you use @Transactional directly on a controller, it requires an AOP proxy, and when you set proxy-target-class="false" you get an interface-based proxying mechanism (i.e. JDK dynamic proxies) so the annotations must be on an interface the controller implements or otherwise they're not visible to Spring MVC.

Alternatively, you can keep the annotations on the controller itself when you set proxy-target-class=true in which case you get class-based proxying (i.e. sub-classing proxies via cglib).

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 10, 2014

Alex commented

Ok, so this is by design, or at least it's not avoidable.

But a clearer error message instead of this strange "object is not an instance of declaring class" could be useful!

Thanks for the explanation.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 11, 2014

Rossen Stoyanchev commented

I'm re-opening in order to add a more helpful message. The subject has been modified to reflect that (was "Implementing ApplicationEventPublisherAware causes "No adapter for handler" error").

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Feb 11, 2014

Rossen Stoyanchev commented

I've committed a fix that provides a better error message and I've also updated the documentation to provide better advice around the use of @Controller's and AOP proxying. Please see the commit message for details.

Also note a related fix in the works (see related ticket #16043). Effectively the fix for #16043 would have prevented this issue since we wouldn't have switched to interface-based proxying if the interface the controller implements is a Spring Context callback (like ApplicationEventPublisherAware).

One last point to make. As an alternative to implementing ApplicationEventPublisherAware, you could also inject ApplicationEventPublisher via @Autowire, hence not requiring you to implement an interface and in turn triggering interface-based proxying. Again, #16043 fixes that but it is a good general point to make.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants