Spring AOP Advice on Annotated Controllers
Spring AOP (Aspect-oriented programming) framework is used to modularize cross-cutting concerns in aspects. If you want a more simple definition you can think of them as a Interceptor but with more options configurations possible. In Spring there are two different constructs that get called “interceptors”. First, there are Handler Interceptors, which are part of the Spring MVC framework (and similar to Interceptors in Struts 2), and give you the ability to add interceptor logic to requests. But you also have Method Interceptors, which are part of the Spring AOP framework. These are much more general mechanism than Handler Interceptors, but also potentially more complex. In AOP terminology, such interceptors provide a means of coding the “aspects” you’re talking about.
In this article we are not going to cover how Spring AOP work, I am going to present the solution to an error that could appear when you want to use advices with controller methods in Spring 3 using annotation and non declarative configurations.
¿What is the problem?
You need to make some processing (before and/or after) the execution of a controller. You want to use the AspectJ annotation in Spring to do that. Your code could be something like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Controller public class TestController { @RequestMapping("/test.fo") public String test(ModelMap model) { model = new ModelMap(); return "test.jsp"; } } |
The aspect has to include an advice which will be executed before and after the controller is invoked by a request. Using annotation your aspect could be something similar to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @Aspect public class StateAspectImpl extends StateAspectI{ private static final Log LOG = LogFactory.getLog(StateAspectImpl.class); @Pointcut("within(@org.springframework.stereotype.Controller *)") //we define a pointcut for all controllers public void classPointcut() {} @Pointcut("execution(* *getViewNew(..))") // the methods that ends in getViewNew are join to this pointcut public void methodPointcut() {} /** * Operations*/ @Around("classPointcut() && methodPointcut() && args(request,modelMap)") public ModelMap recoverStateView(ProceedingJoinPoint joinPoint, HttpServletRequest request, ModelMap modelMap) throws Throwable { //the operations that you want execute } |
So you want to advice all the methods that contains getViewNew in the name from all classes annotated with @Controller. When you execute this code deploying the app in a server it doesn’t work and don’t raise any error or exception.
The cause is…
After debugging the code I realized that the advice is never been executed. So I tried to look for a solution in StackOverflow. Some of the question/answer that I find there were:
http://stackoverflow.com/questions/3310115/spring-aop-advice-on-annotated-controllers
http://stackoverflow.com/questions/789759/how-can-i-apply-an-aspect-using-annotations-in-spring
http://stackoverflow.com/questions/9310927/aspect-not-executed-in-spring
But anyone of these discussion and the solutions that are proposed in, works in my situation. The question that point me in the right direction was:
http://stackoverflow.com/questions/3991249/how-can-i-combine-aspect-with-controller-in-spring-3
The source of the problem is: Spring AOP is based on a proxy generation, that executed the advices in the pointcuts that you declare.For accomplish this, the classes that contain the methods that match with the pointcuts which you created (the classes we want to intercept the execution) have to be declared with component scanning in the domain on the DispatcherServlet.
At this point, if your using a xml file to describe the dispatched servlet, like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> |
for the web.xml file, and:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- not strictly necessary for this example, but still useful, see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-controller for more information --> <context:component-scan base-package="springmvc.web" /> <!-- the mvc resources tag does the magic --> <mvc:resources mapping="/resources/**" location="/resources/" /> <!-- also add the following beans to get rid of some exceptions --> <bean /> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> </bean> <!-- JSTL resolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> </beans> |
But, what happen if you don’t have a dispatcher servlet XML file and you are using a class that extends WebMvcConfigurerAdapter to create the servlet configuration. In this case the advice is never executed because the controllers are not created in the domain of the DispatcherServlet.
The solution is…
There are two possible solutions to this problem:
- Change the configuration of your spring application to use a xml configuration instead of the WebMvcConfigurerAdapter.
- Create a aop xml file to declare the class that implement the advice and to configure the aop proxy to make use of this advice.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?xml version='1.0' encoding='UTF-8'?> <beans xmlns='http://www.springframework.org/schema/beans' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:aop='http://www.springframework.org/schema/aop' xsi:schemaLocation='http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd'> <!-- AOP support --> <bean id='stateAspectImpl' class='...ui.aspect.StateAspectImpl' /> <aop:aspectj-autoproxy> <aop:include name='stateAspectImpl' /> </aop:aspectj-autoproxy> </beans> |
The disadvantage of this solution is: each time you have to create a new advice, you have to remember change the xml file to declare the advice and add the advice to the proxy.
——————————————–
References:
Spring AOP Reference Documentation.
Article in Java Geek Codes: a quick tutorial with full code.
http://stackoverflow.com/questions/3310115/spring-aop-advice-on-annotated-controllers
http://stackoverflow.com/questions/789759/how-can-i-apply-an-aspect-using-annotations-in-spring
http://stackoverflow.com/questions/9310927/aspect-not-executed-in-spring
http://stackoverflow.com/questions/3991249/how-can-i-combine-aspect-with-controller-in-spring-3
Category: Others
It seems that you must define the following code in serverlet.xml.
Excellent retrieval of sources regarding Spring Controller’s AOP weaving. Thanks for sharing!