`

Spring + AspectJ + LoadTimeWeaving

 
阅读更多

https://dev.c-ware.de/confluence/pages/viewpage.action?pageId=7471106

 

Spring is easy ... AspectJ is easy ... putting them together should be easy too, but there are quite some pitfalls. On this page I will try to doucment some problems I was having in my Projects.

Spring doesn't need AspectJ to work. Everything works great without it. The actual bean-weaving is relatively straight forward. It starts getting interesting as soon as you start using JPA and Transactions.

As soon as you annotate a method with "@Transactional" Spring has to make sure that a Transaction is created. But how can this be done? Lets think of Bean A calling a method called "doTheBThing" on a Bean B. If "doTheBThing" is marked Transactional, then Somehow a transaction has to be created before executing the method and it has to be committed/rolled-back after it finishes.

The solution is not to give A a real reference to B but to give it a reference to an Object that has the same Interface and which wraps the Transactional code around the real call to B.

Depending on your case there are two basic approaches:
Dynamic-Proxies: In this case you have to have an Interface describing the Interface of your bean since the Proxy class is only able to create an Object based upon an Interface. (http://www.roseindia.net/javatutorials/dynamic_proxies_tutorial.shtml)
CGLib: This approach needs no Interface as it dynamically creates a Class extending the original Class which wraps the transactional Code around the actual call to super.doTheBThing.

I found a good comparison of the two approaches on this page: http://insufficientinformation.blogspot.com/2007/12/spring-dynamic-proxies-vs-cglib-proxies.html

Your system will propably work with the spring defaults ... as long as you dont't want to use AspectJ for creating some of your own Aspects.

For Example I created a SecurityChecker Aspect that should secure calls to specially annotated Classes and Methods. This is where I really got into trouble using Dynamic Proxies. As the Proxy implements an Interface without keeping the annotations, it was allmost impossible for me to access the annotation information. Using CGLib all I had to do, was to access the genericSuperClass property and I could read all my Annotations. Another rather anoying thing is that I sort of like to stick to one concept and having mixed Dynamic Proxies and CGLib Proxies sort of disturbed my asthetic impression of the software.

Weaving types

AspectJ weaving can be done in three fashions:

  • Compile-Time-Weaving, which does the weaving during the compilation of the classes
  • Post-Compile-Time-Weaving, which sort of modifies allredy compiled classes and outputs modified versions of class files
  • Load-Time-Weaving, which does the weaving while loading the classes in the classloader

The last option it the one I'm going to be talking about.

Configuring Load-Time-Weaving (LTW)

In order to utilize the Load-Time-Weaving (LTW) we have to influence the way classes are loaded. This can generally be done in two fashions:

  • Using an "agent"
  • Exchanging the classloader

Using an "agent"

Well I have to admit that I haven't really figgured out what exactly the agents do. All I know it that specifying a LTW-agent makes the entire Java VM become AspectJ-aware. Not by exchanging the classloader but some different means. I have to admit, that at the curent point I don't really care how they do it, as long as they do it.

The agent is configured by adding a

-javaagent:[path-to spring-agent.jar]

in my current maven-based environment this would be

-javaagent:C:\Users\myusername\.m2\repository\org\springframework\spring-agent\2.5.6\spring-agent-2.5.6.jar

which has to be added to the java commandline together with all the other Xmx- and Xms-settings.

Exchanging the classloader

As mentioned in the agent-section, specifying an agent changes the entire Java VM. So what if for example you want to enable LTW for one application of an application-server or servlet-engine? In this case we have to go the classloader-way. I'll explain this for tomcat.

At first tomcat has to be provided with the custom classloader. For this the spring-tomcat-weaver.jar (in my case spring-tomcat-weaver-2.5.6.SEC1.jar) has to be placed in the tomcat lib-directory. The second step, is to configure the classloader for the contexts you want to user LTW in. This is done by placing the following file with the name context.xml in the Web-applications META-INF directory:

<Context path="/myapp">
<Loader loaderClass="org.springframework.instrument.classl oading.tomcat.TomcatInstrumentableClassLoader" />
</Context>

This turns on LTW for only this context.

What happens during startup?

So now it's getting interesting. So as soon as you startup your application with one of the two approaches described above and a class is loaded, the load-time-weaver looks through the classpath for META-INF/aop.xml files. Here comes an example of one of my aop.xml fiels:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="de..*"/>
    </weaver>
 
    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="de.cware.cweb.support.profiling.ExecutionProfiler"/>
    </aspects>
 
  </aspectj>

Yeah ... I know what you're thinking "not a profiler aspect again!" but mine is cooler as 90% of the rest (wink)

Now let's have a look at the file, the first part "weaver" tells the weaver to include the "de" package and all of it's sub-packages (did you notice the double "."?) If you only want to weave one package and not it's sub-packages, just use one period.

In the second part "aspects" the Aspect classes are listed. So even if you might have thousands of Apects in your jar-files, only the ones listed here will be woven.

Now I mentioned that my profiler is cooler than most of the hellow-aspect-world-profilers, so what makes it so cool? ... well it doesn't log to the console, but to a database using spring and jpa.

Here comes the code:

package de.cware.cweb.profiling;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import java.util.Calendar;
 
/**
 * Created by IntelliJ IDEA.
 * User: cdutz
 * Date: 02.06.2010
 * Time: 14:38:52
 */
@Aspect
@Configurable
public
class CollectorProfiler
{
    protected JpaTemplate jpaTemplate;
 
    public CollectorProfiler() {
        System.out.println("Created CollectorProfiler " this);
    }
 
    //////////////////////////////////////////////
    // Spring Properties
    //////////////////////////////////////////////
 
    @Required
    public
    void setJpaTemplate(
            final JpaTemplate aJpaTemplate)
    {
        jpaTemplate = aJpaTemplate;
        System.out.println("Set jpaTempalte on " this);
    }
 
    //////////////////////////////////////////////
    // Aspect Logic
    //////////////////////////////////////////////
 
    @Around(value = "execution(public * de..*.collect(*,*,*)) && args(channel, environment, schedulerJob)", argNames = "pjp, channel, environment, schedulerJob")
    public
    Object profileCollectorCall(ProceedingJoinPoint pjp, Channel channel, MyEnvironment environment, SchedulerJob schedulerJob)
            throws Throwable
    {
        System.out.println("Executing profileCollectorCall on " this);
 
        final long startTime = Calendar.getInstance().getTimeInMillis();
 
        final OvalCollectedObject result = (OvalCollectedObject) pjp.proceed();
 
        final long endTime = Calendar.getInstance().getTimeInMillis();
 
        final ItemCollectorBase collector = (ItemCollectorBase) pjp.getTarget();
        final OvalObject object = collector.getModel().getOvalObject();
 
        // For system info collections no object is set - ignore those
        if((jpaTemplate != null) && (object != null)) {
            try {
                final CollectorSample sample = new CollectorSample();
                sample.setEnvironmentId(environment.getId());
                sample.setObjectIdentifier(object.getIdentifier());
                sample.setObjectVersion(object.getVersion());
                sample.setObjectType(object.getXmlLocalName());
                sample.setExecutionTime(endTime - startTime);
 
                // We have to do it this way, since we can't make AspectJ instrument an Aspect.
                // Therefore we have to manually to what the "Transactional" Aspect would have
                // done.
                jpaTemplate.execute(new JpaCallback() {
                    @Override
                    public Object doInJpa(EntityManager em) throws PersistenceException {
                        em.getTransaction().begin();
                        em.persist(sample);
                        em.getTransaction().commit();
                        return null;
                    }
                });
            catch(final Exception e) {
                e.printStackTrace();
            }
        }
 
        return result;
    }
}

So why is this so cool? ... well I have to admit it isn't but if you try to do this using 90% of the tutorials available in the web you will certainly fail and you'll be stuck for quite a while (as I was). So now lets dig into the problem:

A spring aspect is woven using the META-INF/aop.xml and is configured by a spring config-file. Unfortunately allmost all Spring+AspectJ examples are missing one vital part ... the Spring beans never get dependencies injected as they all use Log4j or the console to output their profiling information. Following the tutorials, I created a aop.xml to configure the weaving and a Spring config-file to configure my Aspect. When starting the application I could see that the Apect was woven as expected. Unfortunately when executing the Aspect logic, I got tons of NullPointerExceptions. When you have to debug Aspect woven code you will relatively soon start cursing (wink) ... I ended up adding System.out.prinln statemnts all over my code. When having a deeper look I could see that at the beginning two instances were created. The setJpaTemplate was called for the second instance and all calls to the Aspect logic were called on the first instance ... so why was this so?

After quite some searching, I stumbled over this page http://www.ibm.com/developerworks/java/library/j-aopwork13.html. This was the first time I read a post in which somebody actually did dependency-injection. In this article there was one sentance that cleared up the picture for me: "For the most common case of singleton aspects such as the RemoteExceptionHandling aspect above, AspectJ defines an aspectOf() method that returns the aspect instance."

So that was the reason! AspectJ created one instance and wove this in the code. Then, when configuring the Spring context, another instance was created and that was configured correctly with the jpaTemplate. Using the information of the IBM article I found out that simply changing the spring config from:

<bean id="collectorProfiler" class="de.cware.utils.support.profiling.CollectorProfiler">
    <property name="jpaTemplate" ref="jpaTemplate"/>
</bean>

to:

<bean id="collectorProfiler" class="de.cware.utils.support.profiling.CollectorProfiler" factory-method="aspectOf">
    <property name="jpaTemplate" ref="jpaTemplate"/>
</bean>

made spring call the public static "aspectOf" method to get a reference to the allready instanciated Aspect instance and to configure that one instead.

Now if you are using IntelliJ or some similarly intelligent IDE it will certainly complain about no "aspectOf"-method being available ... trust me, If you setup your weaving correctly, it will exist.

Just because none of the oher articles ever described this mystical "aspectOf"-Method, I waseted about two full days. This was why I thought I should write this down and mabe it helps someone else save a day or two (smile)

 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics