The following example uses JBoss AOP to apply a custom interceptor to any Java method, in any runtime environment.
This is a more flexible alternative to a servlet filter (i.e. the Open Session in View pattern), or most other kinds of interceptors. It wraps a Hibernate Session and a database transaction around any pointcut. Usually you would wrap this interceptor around a service facade method that needs a persistence context, or, in other words, that uses DAOs.
This AOP interceptor should be used if your hibernate.current_session_context_class configuration option is set to thread and you are not using JTA and/or CMT. However, you could easily rewrite it to work with JTA and programmatic transaction demarcation with the UserTransaction API. If your application uses EJBs and container-managed transactions, you don't need any of this. Sessions and transactions is required reading to understand this interceptor and where it is applicable.
Note that this filter always propagates a transaction if one wrapped method would call another wrapped method - the "current" Session is also propagated automatically.
public class SessionTransactionInterceptor
implements org.jboss.aop.advice.Interceptor {
private static Log log = LogFactory.getLog(SessionTransactionInterceptor.class);
private static SessionFactory sf = HibernateUtil.getSessionFactory();
public String getName() {
return "SessionTransactionInterceptor";
}
public Object invoke(Invocation invocation) throws Throwable {
try {
boolean isActive = sf.getCurrentSession().getTransaction().isActive();
if ( !isActive) {
log.debug("Starting a database transaction");
sf.getCurrentSession().beginTransaction();
}
log.debug("Invoking the aspectized service method");
Object result = invocation.invokeNext();
// Commit and cleanup
if (!isActive) {
log.debug("Committing the database transaction");
sf.getCurrentSession().getTransaction().commit();
}
return result;
} catch (StaleObjectStateException staleEx) {
log.error("This interceptor does not implement optimistic concurrency control!");
log.error("Your application will not work until you add compensation actions!");
// Rollback, close everything, possibly compensate for any permanent changes
// during the conversation, and finally restart business conversation. Maybe
// give the user of the application a chance to merge some of his work with
// fresh data... what you do here depends on your applications design.
throw staleEx;
} catch (Throwable ex) {
// Rollback only
try {
log.warn("Trying to rollback database transaction after exception");
sf.getCurrentSession().getTransaction().rollback();
} catch (Throwable rbEx) {
log.error("Could not rollback transaction after exception!", rbEx);
}
// Let others handle it... maybe another interceptor for exceptions?
throw ex;
}
}
}
Now define the AOP pointcuts in your META-INF/jboss-aop.xml descriptor:
<aop>
<!--
Wrap all execute() methods in all Action (interface) implementors inside a database transaction
and a thread-bound persistence context. If one execute() method should call another
execute() method, both transaction and persistence context are propagated.
-->
<bind pointcut="execution(* $instanceof{my.actions.Action}->execute(..))">
<interceptor class="my.persistence.SessionTransactionInterceptor"/>
</bind>
</aop>
To weave the interceptor into your bytecode, use either build time instrumentation in your Ant build.xml:
<taskdef name="aopc" classname="org.jboss.aop.ant.AopC">
<classpath refid="project.libs"/>
</taskdef>
<target name="instrument.aop" depends="compile"
description="Run the AOP compiler to weave interception code">
<aopc classpath="${classes.dir}">
<compilerclasspath><path refid="project.libs"/></compilerclasspath>
<src path="${classes.dir}"/>
<include name="**/*.class"/>
</aopc>
</target>
Or inject the bytecode through a custom boot classloader:
<target name="classloaderaop.run" depends="compile"
description="Run with load-time AOP instrumentation">
<echo>To run on JDK 1.4 download JBoss AOP and don't use lib/api/jboss-aop-jdk5.jar!</echo>
<!-- Compile a custom classloader first, for JDK 5.0 (see note above) -->
<java
classname="org.jboss.aop.hook.GenerateInstrumentedClassLoader"
fork="yes"
failonerror="true">
<arg value="${build.dir}/custom_aop_loader"/>
<classpath refid="project.libs"/>
</java>
<!-- Need custom bootclasspath... -->
<path id="custom.bootclasspath">
<fileset dir="${lib.dir}">
<include name="**/jboss-ejb3-all*.jar"/>
<include name="**/thirdparty-all*.jar"/>
</fileset>
<pathelement path="${build.dir}/custom_aop_loader"/>
</path>
<property name="bootclasspath" refid="custom.bootclasspath"/>
<!-- Run TestNG tests with custom classloader in boot classpath, prepended -->
<mkdir dir="${testng.out.dir}"/>
<testng outputDir="${testng.out.dir}">
<jvmarg value="-Xbootclasspath/p:${bootclasspath}"/>
<jvmarg value="-Djboss.aop.exclude=org.hibernate.impl"/>
<!-- Ignoring these prevents warnings -->
<jvmarg value="-Djboss.aop.ignore=*ByCGLIB$$*,*dom4j*,*oracle*,*objectweb*"/>
<classpath>
<path refid="project.libs"/>
<pathelement path="${classes.dir}"/>
</classpath>
<xmlfileset dir="${etc.dir}">
<include name="testsuite-aop.xml"/>
</xmlfileset>
</testng>
</target>
Comments