Part 6 – Adding AOP support

I heard a story about one senior (and quite highly paid) softwaree engineer. He was given task to log every method in every controller in project he was working on. Engineer rewrote all controller methods, so from code like this:

    @RequestMapping(method = RequestMethod.GET)
    public String showEmployees(Model model) {
        List<Employee> employees = employeeDao.list();
        model.addAttribute("employees", employees);

        return "employees/list";
    }

he made following code:

    @RequestMapping(method = RequestMethod.GET)
    public String showEmployees(Model model) {
	LOGGER.log("Invoking method showEmployees");

        List<Employee> employees = employeeDao.list();
        model.addAttribute("employees", employees);

	LOGGER.log("Returning from method showEmployees");
        return "employees/list";
    }

What’s wrong with this code? Well:

  • It takes lot of time to alter every method with such code
  • It is error prone – you can introduce typos or forget to add logging somewhere
  • It is mixing cross-cutting concerns. That means you are adding same kind of repetetive, boilerplate and unrelated code to places where it doesn’t belong.
  • For example, what is the responsibility of showEmployees method? It is invoking service, getting employees and putting them to model. Logging really isn’t it’s responsibility, so why to mix those concerns?

    If engineer I mentioned knew about Aspect Oriented Programming he would save lot of time and made code better and more readable. Spring supports something called “Aspects” that are made exactly for such a problems. Aspects allow us to define common functionality in one place. Before we write any code, there is some terminology to understand. This terminology is quite huge and I am not going to write it here, but I encourage you to read Spring’s official reference page on AOP if you wish to know more. You should at least understand what is Advice, Join Point, Pointcut, Aspect and Weaving.

    OK let’s add Aspect for logging controller methods, exactly what should’ve done engineer from the story in the beginning.

    We must first add dependencies to pom.xml on AspectJ library:

            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.6.11</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>1.6.11</version>
            </dependency>
    

    Also check if you have dependency on Spring’s AOP (but if you follow this tutorial from the very beginning you already have it):

    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-aop</artifactId>
    			<version>3.1.0.RELEASE</version>
    		</dependency>
    

    Now let’s write Aspect’s code. Create package org.timesheet.aspects and add ControllerLoggingAspect class:

    package org.timesheet.aspects;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    import java.util.Arrays;
    
    /**
     * Will log every invokation of @RequestMapping annotated methods
     * in @Controller annotated beans.
     */
    @Aspect
    public class ControllerLoggingAspect {
    
        @Pointcut("within(@org.springframework.stereotype.Controller *)")
        public void controller() {}
    
        @Pointcut("execution(* *(..))")
        public void methodPointcut() {}
    
        @Pointcut("within(@org.springframework.web.bind.annotation.RequestMapping *)")
        public void requestMapping() {}
    
        @Before("controller() && methodPointcut() && requestMapping()")
        public void aroundControllerMethod(JoinPoint joinPoint) throws Throwable {
            System.out.println("Invoked: " + niceName(joinPoint));
        }
    
        @AfterReturning("controller() && methodPointcut() && requestMapping()")
        public void afterControllerMethod(JoinPoint joinPoint) {
            System.out.println("Finished: " + niceName(joinPoint));
        }
    
        private String niceName(JoinPoint joinPoint) {
            return joinPoint.getTarget().getClass()
                    + "#" + joinPoint.getSignature().getName()
                    + "\n\targs:" + Arrays.toString(joinPoint.getArgs());
        }
    
    }
    

    This code says, that @Before and @AfterReturning from controller method we will log information about it’s invokation (name and arguments). This advices execute when all three pointcuts are matching. controller() pointcut marks matching join point (that matches stereotype Controller) at which advice should be woven. methodPointcut() marks that we’re dealing with method call and requestMapping() pointcut marks methods annotated with @RequestMapping.

    To make it work, we’ll add aop.xml Spring configuration file under src/main/resources:

    <?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-2.0.xsd">
    
        <!-- AOP support -->
        <bean id="controllerAspect" class="org.timesheet.aspects.ControllerLoggingAspect" />
        <aop:aspectj-autoproxy>
            <aop:include name="controllerAspect" />
        </aop:aspectj-autoproxy>
    
    </beans>
    

    And then we’ll import it in timesheet-servlet.xml Spring config:

    <import resource="classpath:aop.xml" />
    

    This was the last part of tutorial. I hope you have now better understanding of what Spring is and how does it help to solve your problems. Remember that we’ve covered only tiny piece of Spring in this tutorial. There is still much more to explore!

    Back to main tutorial page

    About these ads

18 thoughts on “Part 6 – Adding AOP support

  1. Pingback: Part 5 – Adding Spring MVC part 2 | vrtoonjava

  2. Pingback: JavaPins

  3. Thank you for this very informative tutorial. It works well for simple @Controllers but it fails for all autowired fields in such controllers. Couldn’t get it to work so far….

    Any ideas?

    • Hi tien113,

      Put this line to pom.xml right after “name” element (so it will be child of project):

      <packaging>war</packaging>
      

      Then configure plugin in same pom.xml file, add this plugin to “plugins” section:

      		<plugin>            
        			<groupId>org.apache.maven.plugins</groupId>
        			<artifactId>maven-war-plugin</artifactId>
        			<configuration>
      			    <webXml>web/WEB-INF/web.xml</webXml>        
      			  </configuration>
      		</plugin>
      

      then just run “mvn clean install” and WAR file will be located in target folder.

      • tien, make sure to name that WAR “timesheet-app.war”, because I hardcoded some values that rely on this application context name. Or try to paste here some errors.

  4. HTTP Status 500 – Servlet.init() for servlet timesheet threw exception

    type Exception report

    message Servlet.init() for servlet timesheet threw exception

    description The server encountered an internal error that prevented it from fulfilling this request.

    exception

    javax.servlet.ServletException: Servlet.init() for servlet timesheet threw exception
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002)
    org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
    org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    java.lang.Thread.run(Thread.java:680)
    root cause

    org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from ServletContext resource [/WEB-INF/timesheet-servlet.xml]; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/timesheet-servlet.xml]
    org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:341)
    org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
    org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
    org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
    org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
    org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
    org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
    org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:131)
    org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:522)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:436)
    org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:631)
    org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:588)
    org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:645)
    org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:508)
    org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:449)
    org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:133)
    javax.servlet.GenericServlet.init(GenericServlet.java:160)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002)
    org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
    org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    java.lang.Thread.run(Thread.java:680)
    root cause

    java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/timesheet-servlet.xml]
    org.springframework.web.context.support.ServletContextResource.getInputStream(ServletContextResource.java:118)
    org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:328)
    org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
    org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
    org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
    org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
    org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
    org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
    org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:131)
    org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:522)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:436)
    org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:631)
    org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:588)
    org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:645)
    org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:508)
    org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:449)
    org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:133)
    javax.servlet.GenericServlet.init(GenericServlet.java:160)
    org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
    org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1002)
    org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
    org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    java.lang.Thread.run(Thread.java:680)
    note The full stack trace of the root cause is available in the Apache Tomcat/7.0.30 logs.

    • Hi tien113, you are right – it’s my mistake and I am very sorry. There are two ways how to fix this:

      HARD WAY: manually write configuration in maven-war-plugin
      EASY WAY: use default maven structure and war will be correctly assembled. That means, we must rename our “web” folder to “webapp” and then copy it to src/main/, so we will have src/main/webapp.

      I am sorry for this inconvenience, so I made video for you to make sure it’s clear :)

      http://www.screenr.com/Pf58

      Please let me know if it helped!

  5. Excellent tutorial. However, I was ok until part 4, where the tests part but could not get it going afterwards. Having all sort of issues. Is there a place where I can grab the source code for the entire project?

  6. Excellent! I followed the tutorial and could deploy the project using the Jetty plugin. I also made changes to the latest versions of dependencies in Maven.

    org.mortbay.jetty
    maven-jetty-plugin
    6.1H.14.1

    10

    8080
    6000

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s