Version 2

    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>