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