A very brief intro to JBoss AOP.
This tutorial is a follow-up to the JBoss Microcontainer intro – read that first if you haven't yet.
AOP (Aspect Oriented Programming) is a concept of moving a „programatic envelopes“ away from the methods. What I call a programatic envelope is that kind of annoying code you have to repeat at the end or/and the beginning of many methods.
Usual textbook examples of AOP usage are:
However, AOP is particulary useful for frameworks for their need of generalization. It can help it to put the envelope code around method which it still does not know.
First, you mark some Java constructs (methods, fields, constructors, …) to
be AOP'ed (encapsulated with your envelope code).
Then you need so-called „aspect“ – that is the official name for envelope
code.
Then you need some tool which will bind the aspect to those methods. This is
done by advanced techniques like byte-code instrumentation and class
reflection.
JBoss AOP offers much more then the other AOP frameworks (like Spring AOP).
@Aspect
,
@Interceptor
, @Bind
etc. See Chapter
6. Annotation Bindings.Copied from JBoss AOP reference docs.
We want to catch all calls to methods that affect the fuel in the
Car
. To make this easily achievable with AOP, let's follow a
convention that all changing methods are setters and all fuel-related properties
names end with „Fuel“.
For the purposes of this example, we've changed the Car
class to
have two fuel-related properties:
public class Car { public int litresOfFuel; public int getLitresOfFuel() { return litresOfFuel; } public void setLitresOfFuel( int litresOfFuel ) { this.litresOfFuel = litresOfFuel; } public int reserveFuel; public int getReserveFuel() { return reserveFuel; } public void setReserveFuel( int reserveFuel ) { this.reserveFuel = reserveFuel; } public String toString(){ return "Car \""+this.name+"\" with "+(this.litresOfFuel + this.reserveFuel)+" litres of fuel."; } ... }
Interceptor
(„envelope“)This is a simple interceptor that will just notify us when triggered.
package cz.zizka.ondra.jbmctest; import java.util.logging.Logger; import org.jboss.aop.advice.Interceptor; import org.jboss.aop.instrument.Untransformable; import org.jboss.aop.joinpoint.Invocation; public class FuelInterceptor implements Interceptor, Untransformable { private static final Logger log = Logger.getLogger( FuelInterceptor.class.getName() ); public Object invoke(Invocation invocation) throws Throwable { Object target = invocation.getTargetObject(); if( target instanceof Car ){ log.info("The fuel is being changed for the car '"+((Car)target).getName()+"'."); } return invocation.invokeNext(); } public String getName() { return FuelInterceptor.class.getName(); } }// class FuelInterceptor
For the syntax of AOP pointcuts, see JBoss AOP documentation.
The syntax for our purpose stated above would be:
execution(* cz.zizka.ondra.jbmctest.Car->set*Fuel(..)
The AOP settings go to a special XML file having <aop>
as
it's root element, and conforming to the JBoss AOP XSD.
From what I've found, this file must be either in
META-INF/jboss-aop.xml
of your .jar
, or anywhere on
the filesystem, but you'll have to point to it by the
-Djboss.aop.path
JVM argument – see 10.2.1. Precompiled
instrumentation and 5.2. Resolving
XML.
To let maven put this file to your .jar
, store it in
src/main/resources
(the default path for resources), giving the
resulting path src/main/resources/META-INF/jboss-aop.xml
.
So let's bind this interceptor to some method calls. In this case, we're
binding it to all methods of the Car
class whose name has the
pattern set*Fuel
.
<?xml version="1.0" encoding="UTF-8"?> <aop> <bind pointcut="execution(* cz.zizka.ondra.*->set*Fuel(..))"> <interceptor class="cz.zizka.ondra.jbmctest.FuelInterceptor"/> </bind> </aop>
Alternatively, it should be possible to put the AOP settings to the JBoss Microcontainer XML (but I wasn't able to get it working):
<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd" xmlns="urn:jboss:bean-deployer:2.0" xmlns:aop="urn:jboss:aop-beans:1.0"> <bean ... /> <!-- Car and garage beans --> <aop:interceptor name="FuelInterceptor" class="cz.zizka.ondra.jbmctest.FuelInterceptor"/> <aop:bind pointcut="execution(* cz.zizka.ondra.jbmctest.Car->set*Fuel(..)"> <aop:interceptor-ref name="FuelInterceptor"/> </aop:bind> </deployment>
After setting Maven to use JBoss repositories (see the JBoss Microcontainer how-to), add these dependencies to your project:
<dependencies> <dependency> <groupId>org.jboss.microcontainer</groupId> <artifactId>jboss-kernel</artifactId> <version>2.0.9.GA</version> </dependency> <dependency> <groupId>org.jboss.aop</groupId> <artifactId>jboss-aop</artifactId> <version>2.1.6.GA</version> </dependency> <dependency> <groupId>org.jboss.microcontainer</groupId> <artifactId>jboss-aop-mc-int</artifactId> <version>2.0.9.GA</version> <scope>runtime</scope> </dependency> </dependencies>
Also, add the JBoss AOP Maven plugin. It binds automatically to the
compile
phase. This will change the classes code to call your
interceptors when a method is being called.
<plugins> <plugin> <groupId>org.jboss.maven.plugins</groupId> <artifactId>maven-jbossaop-plugin</artifactId> <version>2.1.3.GA</version> <executions> <execution> <id>compile</id> <goals> <goal>compile</goal> </goals> <configuration> <includeProjectDependency>true</includeProjectDependency> <aoppaths> <aoppath>src/main/resources/META-INF/jboss-aop.xml</aoppath> </aoppaths> </configuration> </execution> </executions> </plugin> </plugins>
public class JBossAopSampleApp { public static void main( String[] args ) throws Throwable { Car myCar = new Car("Red Devil"); System.out.println( "I have a garage: "+car); // Put some fuel in. myCar.setLitresOfFuel( myCar.getLitresOfFuel() + 1 ); myCar.setReserveFuel( myCar.getReserveFuel() + 1 ); System.out.println( "I have a garage: "+car); } }
Now run the application, and you'll see that the interceptor is really being invoked.
I have a garage: Garage with a car: Car "Red Devil" with 0 litres of fuel. 16.11.2009 11:42:47 FuelInterceptor invoke INFO: The fuel is being changed for the car 'Red Devil'. 16.11.2009 11:42:47 FuelInterceptor invoke INFO: The fuel is being changed for the car 'Red Devil'. I have a garage: Garage with a car: Car "Red Devil" with 2 litres of fuel.
The application created in the examples above can be downloaded here: JBossAOPsample.zip