In this article, Alexandre Vasseur explains how to make use of the rich semantics of the new AspectWerkz 2.x AOP framework and its annotation driven approach to implement a subset of the EJB 3 specification with Java 5: annotation driven Container Managed Transaction (CMT).
There has been a lot of hype around using AOP to implement declarative transaction management "à la" EJB 3, based on annotations, but there are still interesting things to explain and for those familiar with it but not with AspectWerkz, this is a good background to have.
This tutorial first explains the way that is most intuitive for users familiar with proxy based frameworks and explains why this approach is not sufficient from an AOP design perspective. It then goes step by step into more AOP semantics details with AspectWerkz Java 5 annotation defined aspects, aspect abstraction and annotation driven AOP.
The end result is a completely reusable implementation for the EJB 3 Container Managed Transaction specification with support for the @javax.ejb.TransactionAttribute
to define transaction boundaries and @javax.ejb.Inject
instance variable injection that simplifies JNDI based lookups. The implementation is not tied to any EJB container and will run without extra compilation phase than just a JVM option introduced by Java 5.
Our objective in this tutorial is to guide you in AspectWerkz Java 5 support to:
The tutorial is based on Java 5 and Java 5 annotations (JSR-175). The complete source code and third parties libraries are part of the project. An Ant based build script is provided. For it to run you will thus need:
PATH
and JAVA_HOME
set accordingly
Since the goal is not to deal with JTA related details, we will use the ObjectWeb JOTM JTA javax.transaction.TransactionManager
implementation but we will ensure that our design is not in any way tied to this implementation.
To fully understand the concepts implemented in this tutorial it is worth explaining what is in the EJB 3 specification and what we aim at implementing using AOP.
EJB 3 includes Container Managed Transaction (CMT) just as EJB 2.x but enables us to declare transaction boundaries using annotations directly into the EJB business methods through the @javax.ejb.TransactionAttribute
that will define which transaction level is required when running the business method. The well known EJB 2.x transaction levels are unchanged and the concepts of CMT as defined in EJB 2.x specification still apply.
EJB 3 provide a way to avoid using JNDI lookup and use various annotation driven dependency injection mechanism to access EJB environment such as javax.transaction.UserTransaction
as regular instance variables for example.
EJB 3 source code for transaction management will thus looks like this (some parts are omitted).
// EJB bean annotations omitted public class BusinessBean { // the EJB container will handle this field @Inject private UserTransaction m_userTransaction; // the EJB container has to handle the transaction level we define @TransactionAttribute(REQUIRED) public void methodRequiredTX() throws BusinessException { // do something // may throw a runtime exception (unchecked) // may throw a business exception (checked) } }
The transaction levels are among the enum javax.ejb.TransactionAttributeType
: MANDATORY, REQUIRED (defaults), REQUIRESNEW, SUPPORTS, NOTSUPPORTED and NEVER. A correct implementation should for example reuse the transaction context in the executing thread if a method is defined with a REQUIRED level and is in the flow of a REQUIRESNEW defined method.
Further on, if the business method fails with an exception, proper transaction handling must happen. In this tutorial, we will assume that the container has to rollback upon unchecked exceptions (subclass of java.lang.RuntimeException
) and not rollback upon checked exception.
Download the tutorial zip here.
When you extract it you will get the following structure:
[tutorial root] build.xml Ant script lib dependencies src main aspectwerkz Aspects implementation in the aspectwerkz.* package javax javax.ejb.* EJB 3 annotations (an incomplete subset suitable for this tutorial only) samples aspectwerkz Sample standalone application META-INF/aop.xml AspectWerkz AOP deployment descriptor
The dependencies are AspectWerkz 2.0.RC1 with Java 5 support, JTA javax.transaction APIs and ObjectWeb JOTM TransactionManager.
In this tutorial we also include source code for a limited subset of the EJB 3 annotations (package names are subject to change since the specification is not final):
@javax.ejb.Inject
for instance variable dependency injection@javax.ejb.TransactionAttribute
for transaction demarcationjavax.ejb.TransactionAttributeType
(enum) to use with @javax.ejb.TransactionAttribute
The complete source code can also be checked out from our CVS anonymously from the tutorials-AW2
module:
cvs -d :pserver:anonymous@cvs.aspectwerkz.codehaus.org:/home/projects/aspectwerkz/scm login cvs -z3 -d :pserver:anonymous@cvs.aspectwerkz.codehaus.org:/home/projects/aspectwerkz/scm co tutorials-AW2
In this section, you will learn how to write the aspect using AspectWerkz annotation based AOP. Our goal is to advise the methods annotated with the @javax.ejb.TransactionAttribute
so that we handle the transaction management.
The dependency injection driven by @javax.ejb.Inject
will be explained in a separate section.
In AspectWerkz an aspect is a regular class with optional class level annotations. The aspect is the unit of modularity, so we will write one aspect to contain the full CMT logic.
In this tutorial, the aspect aims at being generic, so it will only be dependant upon JTA interface APIs. We will subclass the aspect to provide a JOTM based implementation later. Our aspect will thus be an abstract
class, that you can then easily extend to provide an alternative implementation for the application server you are using.
The concrete subclass aspect will be further on instantiated by the AspectWerkz runtime. We decide to have a singleton aspect (one per JVM) and leave the thread safety management (attach a transaction to a thread, get the current transaction context for the executing thread etc) to the actual JTA implementation.
Though perJVM
deployment model is the default in AspectWerkz (and we thus don't have to specify it) we can also
write it explicitly.
// this annotation could be remove since we are using the default deployment-model @Aspect("perJVM") public abstract class TransactionAttributeAwareTransactionProtocol { ... }
We aim at applying transaction management on methods annotated with EJB 3 @javax.ejb.TransactionAttribute
annotation. For such, we will make use of AspectWerkz capabilities to match on Java 5 annotations. The following pointcut will match any method in any class that is annotated with the @javax.ejb.TransactionAttribute
annotation. Note that we don't narrow the pointcut to a specific class since we aim at using our transaction protocol without any EJB.
execution(@javax.ejb.TransactionAttribute * *.*(..))
The execution(...)
syntax means that we want to advise on the callee side of the method invocation (and not on the caller side, which would be different for example if we had some remote calls).The generic syntax for a method pointcut is as follows:
[<annotation>]* <returnedType> <classPattern>.<methodPattern>(<signature pattern>)You can refer to the AspectWerkz documentation for more details on the wildcards.
The pointcut construct in AspectWerkz is defined as an annotated field in the aspect class. The field type is org.codehaus.aspectwerkz.definition.Pointcut
, and the field name will be used when we will bind advice to it.
@Aspect(“perJVM”) public abstract class TransactionAttributeAwareTransactionProtocol { /** * The pointcut that picks out all transacted methods. */ @Expression("execution(@javax.ejb.TransactionAttribute * *.*(..))") Pointcut transactedMethods; ... }
Note: EJB 3 specification also defines a class level annotation that will be the default for business methods without annotation. The pointcut for such a more correct implementation would thus require to match on class annotation as well by using
the within(...)
syntax that will match on types:
execution(@javax.ejb.TransactionAttribute * *.*(..)) || (execution(* *.*(..)) && within(@javax.ejb.TransactionAttribute *))
For each well defined point in the target application (join point) that will match the previously defined pointcut, we need to add transaction management behaviour. This will be realized through the concept of advice.
Using an Around advice
A first approach that has been somehow implemented in several proxy based frameworks would be to use an Around advice that will intercept the annotated methods execution. Such an advice body would look like this (pseudo code):
Intercept @javax.ejb.TransactionAttribute annotated methods (Method interceptedMethod) { txLevel = get the interceptedMethod TransactionAttribute annotation value (f.e. REQUIRED) try { 1 - do transaction management [open / reuse / suspend / go on] 2 - proceed with the actual method execution } catch ( RuntimeException ) { 3 – mark as rollback only } finally { 4 – end transaction management [commit / rollback / go on / resume] } }
If simple, such an approach as a drawback. Indeed, the transaction management logic is cleanly isolated in the aspect, but unfortunately resumed within one single advice with a try / catch / finally
block that interferes on the transaction semantics. It does not make it clear that an unchecked exception will enforce a rollback. This design requirement is lost in one single line of code in the middle of this method.
Using Before and After throwing / finally advice
By making use of richer semantics that have been first introduced by AspectJ, we will write it slightly differently using:
AspectWerkz advice in Java 5 can be defined using annotations. Here are the method level annotations that we will use for such an aspect (in the org.codehaus.aspectwerkz.annotation
package):
@Before(<pointcut>)
for a before advice@AfterFinally(<pointcut>)
for an after finally advice@AfterThrowing(type = <exception class>, pointcut = <pointcut>)
for an after throwing advice on a specific exception (and its subtypes)@Aspect("perJVM") public abstract class TransactionAttributeAwareTransactionProtocol { @Expression("execution(@javax.ejb.TransactionAttribute * *.*(..))") Pointcut transactedMethods; /** * Before advice that will initiate transaction management is bounded to the transactedMethods pointcut */ @Before("transactedMethods") public void enterTransactedMethod(...) throws Throwable { ... } }
In the advice body we will have to get the annotation on the advised method. This information is static, and thus we will make use of AspectWerkz StaticJoinPoint
instead of JoinPoint
. The StaticJoinPoint
provides better performance since it does not expose runtime information (method arguments values etc.). To make use of it, all we have to do is to declare it in our advice signature.
@Before(<pointcut>) public void enterTransactedMethod(final StaticJoinPoint jp) throws Throwable { ... }
To support the REQUIRESNEW
transaction level, we need to suspend the current transaction – if a transaction already exists –, create the new transaction, and after the method invocation has ended, resume the suspended transaction.
When using an around advice approach, this would be straightforward. We would just store the suspended transaction in a local variable and then proceed to the next invocation, and restore it in the finally block.
Using the before and after advice approach brings one drawback to solve this issue. It may seem that we need to add an aspect instance variable so that we can share context between the before and the after finally advice. But since we have decided to use a perJVM
aspect, this field would not be thread safe. We will thus use a ThreadLocal
field to ensure thread safety.
This is a pay as you go model. The ThreadLocal
field will only be used on a specific condition on the transaction boundary, while changing the aspect deployment model to a hypothetical perThread
or better a perCflow
would affect every single transaction.
@Aspect("perJVM") public abstract class TransactionAttributeAwareTransactionProtocol { private ThreadLocal suspendedTxTL; @Before(...) public beforeAdvice(StaticJoinPoint jp) { ... // if we have to suspend the current transaction to build a new one for requiresNew support getTransactionManager().suspend(currentTx); storeInThreadLocal(currentTx); ... } @AfterFinally(...) public afterFinallyAdvice(StaticJoinPoint jp) { ... // if we had to suspend a current transaction to build a new one for requiresNew support // we need to resume it Transaction suspendedTx = (Transaction) fetchFromThreadLocal(); getTransactionManager().resume(suspendedTx); ... } }
The complete skeleton of our aspect is thus:
@Aspect("perJVM") abstract class TransactionAttributeAwareTransactionProtocol { @Expression("execution(@javax.ejb.TransactionAttribute * *.*(..))") Pointcut transactedMethods; private ThreadLocal suspendedTxTL; /** * Before advice to start a TX if needed * Makes use of StaticJoinPoint */ @Before("transactedMethods") public void enterTransactedMethod(final StaticJoinPoint jp) throws Throwable { // get the transaction annotation value from the method or the class as per EJB 3 specification MethodSignature sig = (MethodSignature)jp.getSignature(); Class declaringType = sig.getDeclaringType(); Method method = sig.getMethod(); TransactionAttributeType txType = getTransactionAttributeTypeFor(declaringType, method); // the getTransactionManager() is abstract final TransactionManager tm = getTransactionManager(); // handles transaction management depending on the current transaction context // and the wished transaction level for the method we advise ... } /** * Invoked when an unchecked exception is thrown out of a transacted method. * Marks the current transaction as ROLLBACK_ONLY. */ @AfterThrowing( type = "java.lang.RuntimeException", pointcut = "transactedMethods" ) public void exitTransactedMethodWithException() throws Throwable { final TransactionManager tm = getTransactionManager(); if (isExistingTransaction(tm)) { logInfo("Setting TX to ROLLBACK_ONLY"); tm.setRollbackOnly(); } } /** * Invoked when exiting a transacted method. Performs the actual commit or rollback depending on the * status of the transaction. Resumes suspended transactions. */ @AfterFinally("transactedMethods") public void exitTransactedMethod(final StaticJoinPoint jp) throws Throwable { final TransactionManager tm = getTransactionManager(); // handle transaction logic ... } }
So far, our abstract class is not tied to any JTA Transaction Manager and relies on the JTA APIs only. Two abstract methods are left to be implemented in a subclass for a specific implementation:
// returns the transaction manager protected abstract TransactionManager getTransactionManager() // returns the transaction associated with the current thread protected abstract UserTransaction getUserTransaction()
In this tutorial we have decided to use ObjectWeb JOTM so that we can easily run a sample outside of a container. The concrete implementation is quite straightforward. We are using a public no-argument constructor that will be called once by the underlying runtime upon singleton aspect instantiation. To implement a BEA WebLogic specific implementation you would have to do a JNDI lookup or use WebLogic specific APIs instead of instantiating the Jotm
class.
@Aspect("perJVM") public class JOTMTransactionProtocol extends TransactionAttributeAwareTransactionProtocol { private final TransactionManager m_transactionManager; private final Jotm m_jotm; public JOTMTransactionProtocol() { try { m_jotm = new Jotm(true, false); m_transactionManager = m_jotm.getTransactionManager(); } catch (NamingException e) { throw new TransactionException("Could not create a new JOTM Transaction Manager", e); } } protected TransactionManager getTransactionManager() { return m_transactionManager; } protected UserTransaction getUserTransaction() { return m_jotm.getUserTransaction(); } }
A simple standalone application annotated with @javax.ejb.TransactionAttribute
is part of the project.
Since we are using Java 5, no extra compilation step is needed. Regular javac
compilation will take care of the annotations and our plain java aspects.
You can simply run the following in the root folder of the tutorial:
java –version // make sure you are running with Java 5 ant compile
Before running it we need to weave our target application so that the transaction management is inserted at all the methods
with the TransactionAttribute
annotation. AspectWerkz provides an easy way to go through this step with Java 5 JVMTI (JSR-163) support. We will simply compile and run the application as usual with just a single JVM option (-javaagent
) and a specific XML file in our path to declare which aspects to use.
To declare to the runtime system which aspect to use, we write a simple aop.xml
file that we will put in a META-INF/aop.xml
entry in the classpath. Some more advanced deployment unit to be deployed in an application server could be constituted using one or more aop.xml
but that is out of the scope of this tutorial.
This file simply list the aspect(s) we want to use by their class name:
<!DOCTYPE aspectwerkz PUBLIC "-//AspectWerkz//DTD//EN" "http://aspectwerkz.codehaus.org/dtd/aspectwerkz2.dtd"> <aop> <system id="aop.ejb3.cmt"> <aspect class="aspectwerkz.tutorial.tx.JOTMTransactionProtocol"/> </system> </aop>For more detail and advanced usage of the XML deployment descriptor in AspectWerkz, refer to the documentation.
To run the application, we will use the new Java 5 JVMTI (JSR-163) –javaagent
option that will transparently hook in AspectWerkz in the JVM.
Running it through command line would look like the following:
java –javaagent:lib/aspectwerkz-jdk5-2.0.RC1.jar –classpath ... MyMainHere we have the
classpath
containing the current tutorial root folder "."
so that the META-INF/aop.xml
can be found in the classpath, and containing all required jars and build path.
For convenience, we can run the sample with the Ant script
ant samples:tx
Some information should appear on the standard output. The last part of the output comes from a sample where a method with the REQUIRED
transaction semantic calls another method with the REQUIRED
semantic as well. Only one transaction should cover the two methods. Since we added some simple log to our transaction protocol we can see the expected behaviour.
[TransactionProtocol:INFO] Starts TX with attribute REQUIRED at [aspectwerkz.tutorial.tx.Main.startTxRequired_InvokeTxRequired(..)] [TransactionProtocol:INFO] TX begin [Main:INFO] startTxRequired_InvokeTxRequired [TransactionProtocol:INFO] Starts TX with attribute REQUIRED at [aspectwerkz.tutorial.tx.Main.txRequired(..)] [Main:INFO] txRequired [Main:INFO] noOp [Main:INFO] TX status: STATUS_ACTIVE [TransactionProtocol:INFO] Committing TX
If you want to compile and run the project from within your IDE and without using the Ant script, you have to make sure that the aspectwerkz-jdk5-<version>.jar
file is first in the project classpath. This file contains the AspectWerkz Java 5 annotations implementation and is overriding defaults Java 1.4 version that are in the aspectwerkz-<version>.jar
file.
Our objective in this last section is to add support for instance variable dependency injection as described in the EJB 3 specification.
Our business bean instance will then be able to access the javax.transaction.UserTransaction
instance of the current thread by simply using an instance variable annotated with @javax.ejb.Inject
without any JNDI lookup.
public class BusinessBean { // the EJB container will handle this field @Inject private UserTransaction m_userTransaction; void someMethod() { // direct access to container injected dependencies System.out.println(m_userTransaction.getStatus()) } }
To implement this field value injection we will use a get(...)
pointcut that will match read access on any field annotated with @javax.ejb.Inject
and whose type is javax.transaction.UserTransaction
.
The pointcut is thus :
get(@javax.ejb.Inject javax.transaction.UserTransaction *)
Note that the EJB 3 specification is wider than this and specifies setter injection and some more advanced scenarios. Moreover it would make sense to narrow down this pointcut to the EJB 3 annotated classes only using a within(...)
pointcut
for a specific EJB 3 implementation.
To inject the value when the field is accessed, we will use an Around advice that returns the UserTransaction
instance for the current thread.
The resulting code is thus:
@Aspect("perJVM") abstract class TransactionAttributeAwareTransactionProtocol { @Expression("get(@javax.ejb.Inject javax.transaction.UserTransaction *)") Pointcut injectedUserTransaction; @Around("injectedUserTransaction") public Object resolveUserTransactionInjection() throws Throwable { return getUserTransaction(); } ... }
The provided sample makes use of this instance variable injection mechanism driven by annotation. It is interesting to note that this field access pointcut is not a feature available in any of the proxy based framework like Spring AOP, DynAOP etc but is available in AOP frameworks like AspectWerkz, AspectJ and JBoss AOP which are using bytecode instrumentation.
In this tutorial, we went through a reusable implementation of the EJB 3 CMT specification. Using rich semantics of AOP like before, after throwing and after finally advice enables us to clearly implement the logic for the transaction boundaries.
AspectWerkz supports matching on Java 5 annotations which enables us to add behaviour to the static metadata contained in the Annotation driven nature of the EJB 3 components.
Plain Java AOP, where aspects are annotated Java classes and advanced integration with latest Java 5 innovations reduce the complexity of AOP integration without narrowing its scope like proxy based implementations do.
By using one standardized JVM option introduced by Java 5 we are able to run an ObjectWeb JOTM based EJB 3 transaction implementation without any actual EJBs or J2EE container.
Step by step design decision regarding access to join point information and aspect inheritance as well as some performance considerations brings a modular component that supports EJB 3 container managed transaction and transaction resource injection driven by annotation, while keeping it generic and open for any underlying JTA implementation.
AOP enables us to implement a small but interesting subset of the upcoming EJB 3 specification in just a few hours, while not using any source processing techniques.
AspectWerkz is compatible with Java 1.4 as well, and provides a doclet based (JavaDoc) strongly typed annotation mechanism.
A similar aspect could be used to match on whether EJB 3 like JavaDoc styled annotations or some custom ones that would be implemented with regular interfaces and JavaDoc doclets.
Most of the changes would be implied by the enum
use and the reflective access to annotations using Java 5 reflection API.
Moreover, an extra step to compile the JavaDoc based annotations would be necessary, and the JVM integration would be a bit more effort than just a JVM option unless you are using the BEA JRockit JVM which provides a single JVM option integration feature just as it has been standardized in Java 5.
A Java 1.4 annotation would thus be defined like this:
package javax14.ejb3; // notice that we are using a regular interface // instead of the Java 5 "@interface" keyword to define Annotations public interface Java14TransactionAttribute { Java14TransactionAttributeType value() ; } public class Java14TransactionAttributeType { // emulates an enum public static final REQUIRED = new Java14TransactionAttributeType(“REQUIRED”); ... }
And Java 1.4 annotated source code will looks like this
public class Java 14Main { /** * @javax14.ejb3.Java14TransactionAttribute(javax14.ejb3.Java14TransactionAttributeType.REQUIRED) */ public void businessMethodTxRequired() { ... } ... }
Thanks to the annotation interface class, the annotation is strongly typed and we can access it reflectively using the AspectWerkz Annotations API (that can read Java 5 annotation as well)
String annotationName = "javax14.ejb3.Java14TransactionAttribute"; Method businessMethod = ...; Java14TransactionAttribute txLevel = (Java14TransactionAttribute ) org.codehaus.aspectwerkz.Annotations.getAnnotation(annotationName, businessMethod);
For more information about Annotation implementation for Java 1.3/1.4 in AspectWerkz, refer to the online documentation.
AspectWerkz
JTA specification
ObjectWeb JOTM
EJB 3 JSR 220 specification
Java 5 Annotations JSR 175 specification
Java 5 JVMTI specification
Alexandre Vasseur is Software Engineer at the Java Runtime Products Group, BEA Systems. He is the co-founder of the AspectWerkz AOP framework.