Friday, 28 June 2013

Auditing a Spring MVC Webapp with AspectJ. Part 1

If you’re like me, then you’ll have those kinds of programming days where everything seems to go incredibly well. You write the code and the tests and it just works. And then and there are those other kinds of days, the really bad ones, where you know that everything you’ve written is as right as it can be and the code refuses to work: something is obviously wrong, but you’ve no idea what. I had one of these kinds of days when writing the code for this blog. The idea was to demonstrate how to use Spring and Aspectj to audit a user’s visits to a screen.

Auditing a user’s visits to a screen is one of those few cross-cutting concerns that Aspect Oriented Programming (AOP) solves very well. The idea in the case of my demo code, is that you add an annotation to the appropriate controllers and every time a user visits a page, then that visit is recorded. Using this technique you can construct a picture of the most popular screens and therefore the most popular chunks of functionality in your application. Knowing these details makes it easier to decide where to aim your development effort as it doesn’t pay to develop those chunks of your application that hardly anyone ever uses.

I’ve talked about AspectJ and AOP before in the following blogs, which is great as they demonstrate the basics, but they aren’t a real working Spring MVC application. My previous blogs were:


This time I thought that I’d come up will a fully functioning Spring MVC application using a useful AOP cross-cutting concern.

For the demo-code I created a simple Spring MVC application that has two screens: a home page and a help page. On top of this I’ve created a simple annotation: @Audit, which is used to mark a controller as one that needs auditing (not all of them will, especially if you choose to audit function points rather than individual screens) and to tell the advice object the screen id as demonstrated in the snippet of code below:

  @Audit("Home")
 
@RequestMapping(value = "/", method = RequestMethod.GET)
 
public String home(Locale locale, Model model) {

That is, until it all went pear shaped...

The plan of attack was to write my simple @Audit annotation and handle it using a simple AuditAdvice class with a method annotated with Aspectj's @Before annotation. I would then pretend this was a real advice class, which meant delegating the actual auditing to an autowired AuditService object.

I started by creating a sample Spring MVC application using the Spring project template:


I then put all the code together and expected it to just work, except it didn’t: Spring would not, no matter what I tried, autowire the AuditService into the AuditAdvice class. This meant that when my @Before annotated method was called, it threw a NullPointerException

When you’re in the situation where you strongly suspect your code is right and it just won’t work, then one of the areas for investigation is the project POM file and setup.

The thing is, when you use someone else’s API, project setup or other tool, you tend to trust it more than you would trust your own code. I guess that the reasons for this are that it’s usually written by a highly respectable organization, which makes you kind of think that they have some magical way of writing really good code, plus it usually contains a whole bunch of stuff that you don’t really understand.

This is really irrational as the API, tool, configuration file etc have been written by programmers like you and me, who probably make as many mistakes as we do.

This fact usually worries me every time I step on to an airplane, as a software bug at 30,000ft is not something you want to happen.

The problem with the Spring MVC project POM is that it’s slightly obsolete and full of stuff that you just don’t need for a standard Spring MVC Java application, plus, there’s no links to any of the plugin documentation, so finding out what it all does and what the different settings mean is difficult.

I did write a blog called Dissecting Spring's MVC Project POM some time ago, which tries to explain how the Spring MVC template application POM works.

These are the changes I had to make to the standard Spring MVC template POM file to get my code working.

1) Update the version of Spring used by the project to the latest: 3.2.3.RELEASE
2) Update the version of AspectJ to 1.7.1
3) Remove the Spring Roo version number.

This will create the create the following version properties:

<org.springframework-version>3.2.3.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.1</org.aspectj-version>

4) Remove other Spring Roo dependencies:

<!-- Roo dependencies -->
<dependency>
 <groupId>org.springframework.roo</groupId>
 <artifactId>org.springframework.roo.annotations</artifactId>
 <version>${org.springframework.roo-version}</version>
 <scope>provided</scope>
</dependency>

This is not a Roo project and I hate unnecessary configuration.

5) Remove references to the Spring repositories:

<repositories>
 <!-- For testing against latest Spring snapshots -->
 <repository>
  <id>org.springframework.maven.snapshot</id>
  <name>Spring Maven Snapshot Repository</name>
  <url>http://maven.springframework.org/snapshot</url>
  <releases><enabled>false</enabled></releases>
  <snapshots><enabled>true</enabled></snapshots>
 </repository>
 <!-- For developing against latest Spring milestones -->
 <repository>
  <id>org.springframework.maven.milestone</id>
  <name>Spring Maven Milestone Repository</name>
  <url>http://maven.springframework.org/milestone</url>
  <snapshots><enabled>false</enabled></snapshots>
 </repository>
</repositories>

6) This example uses the default WAR file name, so remove the reference to the maven-war-plugin:

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-war-plugin</artifactId>
 <configuration>
  <warName>abc</warName>
 </configuration>
</plugin>

7) Update the Surefire plugin to remove the Roo references:

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-surefire-plugin</artifactId>
 <configuration>
  <junitArtifactName>junit:junit</junitArtifactName>
  <!-- Remove the excludes -->
  <excludes>
   <exclude>**/*_Roo_*</exclude>
  </excludes>
 </configuration>
</plugin>


8) Add aspectjweaver as a dependency below aspectjrt

<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>${org.aspectj-version}</version>
</dependency>

9) Remove the AspectJ plugin reference:

<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>aspectj-maven-plugin</artifactId>
 <!-- Have to use version 1.2 since version 1.3 does not appear to work with ITDs -->
 <version>1.2</version>
 <dependencies>
  <!-- You must use Maven 2.0.9 or above or these are ignored (see MNG-2972) -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjtools</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency>
 </dependencies>
 <executions>
  <execution>
   <goals>
    <goal>compile</goal>
    <goal>test-compile</goal>
   </goals>
  </execution>
 </executions>
 <configuration>
  <outxml>true</outxml>
  <source>${java-version}</source>
  <target>${java-version}</target>
 </configuration>
</plugin>

This is the reference that causes all the problems. Without it, default values are used and the application works.

10) Update the tomcat-maven-plugin for automatic deployment.

<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>tomcat-maven-plugin</artifactId>
 <version>1.1</version>
 <configuration>
  <server>myserver</server>
  <url>http://localhost:8080/manager/text</url>
 </configuration>
</plugin>

This leaves the following working POM file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.captaindebug</groupId>
 <artifactId>audit</artifactId>
 <packaging>war</packaging>
 <version>1.0.0-BUILD-SNAPSHOT</version>
 <properties>
  <java-version>1.7</java-version>
  <org.springframework-version>3.2.3.RELEASE</org.springframework-version>
  <org.aspectj-version>1.7.1</org.aspectj-version>
  <org.slf4j-version>1.5.10</org.slf4j-version>
 </properties>
 <dependencies>
  <!-- Spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${org.springframework-version}</version>
   <exclusions>
    <!-- Exclude Commons Logging in favor of SLF4j -->
    <exclusion>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
    </exclusion>
   </exclusions>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${org.springframework-version}</version>
  </dependency>

  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency>

  <!-- Logging -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>${org.slf4j-version}</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
   <version>${org.slf4j-version}</version>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>${org.slf4j-version}</version>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.15</version>
   <exclusions>
    <exclusion>
     <groupId>javax.mail</groupId>
     <artifactId>mail</artifactId>
    </exclusion>
    <exclusion>
     <groupId>javax.jms</groupId>
     <artifactId>jms</artifactId>
    </exclusion>
    <exclusion>
     <groupId>com.sun.jdmk</groupId>
     <artifactId>jmxtools</artifactId>
    </exclusion>
    <exclusion>
     <groupId>com.sun.jmx</groupId>
     <artifactId>jmxri</artifactId>
    </exclusion>
   </exclusions>
   <scope>runtime</scope>
  </dependency>

  <!-- @Inject -->
  <dependency>
   <groupId>javax.inject</groupId>
   <artifactId>javax.inject</artifactId>
   <version>1</version>
  </dependency>

  <!-- Servlet -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>jsp-api</artifactId>
   <version>2.1</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
  </dependency>

  <!-- Test -->
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.7</version>
   <scope>test</scope>
  </dependency>

 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>${java-version}</source>
     <target>${java-version}</target>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
     <junitArtifactName>junit:junit</junitArtifactName>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>tomcat-maven-plugin</artifactId>
    <version>1.1</version>
    <configuration>
     <server>myserver</server>
     <url>http://localhost:8080/manager/text</url>
    </configuration>
   </plugin>

  </plugins>
 </build>
</project>

Finally... having got the project setup correct, the next thing to do is to move on the code, which is something I’ll be covering next time.


The code for this and the next blog is available on github: https://github.com/roghughe/captaindebug/tree/master/audit-aspectj


No comments: