The Spring documentation with respect to Tomcat (6 and below), Spring and AOP (with and without AspectJ) is excellent. See the Spring docs here for more information. Jersey adds a wrinkle to this because the Jersey web services are not Spring managed beans, so it's a little trickier to get AOP working for the service classes/methods.
So, what I hope to provide here is a simple recipe, if you will, for how to get the combination of technologies listed above working, with the additional requirement to perform load time weaving of the aspects into your code (as opposed to compile time or post compile time weaving). In addition, I will explain what you would see if you miss a step or don't get a step right so you can recognize the symptoms in your own setup and know what might need to be fixed.
Step 0 : you have a project to which you want to apply AOP
Step 1 : configure Tomcat
For load time weaving to work in Tomcat we need to supply a different class loader for Tomcat to use. Just include the spring-instrument-tomcat jar in your Tomcat lib folder (I'll show you how to tell Tomcat to use it in step 6 below).
You can find the correct version for your needs here. I used spring-instrument-tomcat-3.2.6.RELEASE.jar for my example.
If you don't include this jar in your Tomcat lib folder (or anywhere else Tomcat is configured to look for library jars) you will see this error (and several others) in the Tomcat logs:
Apr 26, 2014 11:08:02 AM org.apache.catalina.loader.WebappLoader startInternal
SEVERE: LifecycleException
java.lang.ClassNotFoundException: org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader
Step 2 : configure Maven
You DON'T need this dependency, contrary to many of the examples you will find, but it will allow your aspects to compile, which may be confusing. The runtime classes are already included in the aspectjweaver dependency that follows.
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
My aspectj.version property is set to 1.8.0
You WILL need the below aspectjweaver dependency and if you omit it you will see the following error in the catalina (tomcat) logs:
java.lang.NoClassDefFoundError: org/aspectj/weaver/loadtime/ClassPreProcessorAgentAdapter
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
Likewise, you do NOT need the spring-aop dependency if you're going to use the load time weaver (which we are in this case) and are not using any spring-aop specific capability:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.framework.version}</version>
</dependency>
Step 3 : configure Spring
The only setting you need to add to the Spring application-context.xml file is:
<context:load-time-weaver aspectj-weaving="on"/>
You can omit the aspectj-weaving attribute which will cause the default to be used, but I include it here to call out that you could replace that value with an external property loaded into your Spring app context to control whether load time weaving was 'on' or 'off'.
If you do not include context:load-time-weaver in the Spring app context file you won't notice any errors in the Tomcat logs but your aspects won't execute either.
Step 4 : create your aspects and pointcuts, etc.
@Aspect public class Observer { public Observer() { } // constructor @Around("execution(public * *(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("log from " + joinPoint.toString()); // @todo Object result = joinPoint.proceed(); throw new IllegalArgumentException("this is only a test"); // return result; } } // class Observer
The key thing here is how you define your aspects. In my case above I am using @Around and am intercepting all public methods in all my classes (I only have one web service class with one public method in this example). This was a fairly inclusive pointcut expression, with the intent to make sure it included my Jersey web service class. Consult the wealth of documentation on AOP to learn more about join points, point cuts, advices, etc. The Spring reference cited above is VERY good as is this article.
Step 5 : add META-INF/aop.xml to describe your aspects, pointcuts, etc. to AspectJ
This is the file used by AspectJ (you can have multiple aop files) to find and execute your aspects. If you don't include this file or if you put it in a location that won't make it on the classpath you won't see any errors in the Tomcat logs but your aspects won't execute either. So, in my example I put META-INF/aop.xml in src/main/resources and it will be added to WEB-INF/classes when Maven builds the war file.
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="org.hawksoft..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="org.hawksoft.aop.aspect.Observer"/>
</aspects>
</aspec4j>
Step 6 : add META-INF/context.xml
This is the web context file used by Tomcat and this is where you tell Tomcat to use the instrumented class loader needed to create the proxies for your classes.
<Context path="/hawk-aop">
<Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" />
</Context>
It is VERY IMPORTANT that you put this folder and file at the same level as WEB-INF in your project. If you don't put the web context.xml in the right location you will get the following error in the Tomcat logs when the web app is initialized:
2014 8:20:31 AM org.springframework.web.context.ContextLoader initWebApplicationContext
SEVERE: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.weaving.AspectJWeavingEnabler#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loadTimeWeaver': Initialization of bean failed; nested exception is java.lang.IllegalStateException: ClassLoader [org.apache.catalina.loader.WebappClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:org.springframework.instrument.jar
So, to be clear, you will have TWO META-INF folders - one for the aop.xml that will be pushed into WEB-INF/classes when the war is built and one for the context.xml that is on the same level as WEB-INF.
Figuring this out was where the majority of my time was spent in trying to get this to work. This Spring forum conversation is what led me to figure out what was going on with context.xml and aop.xml and may be helpful to you as well - particularly the part about what Tomcat does/does not do with the context.xml file you include in your war.
Note: the 'path' attribute refers to the web app context path and unless you've instructed Tomcat to use a different context it is the name of your war file.
Here's the folder and file layout for my example Maven project:
No comments:
Post a Comment