`

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)

 
分享到:
评论

相关推荐

    spring+aspectjweaver-1.7.1.jar+aspectj-1.8.9.jar+aopalliance.jar

    总结来说,`spring+aspectjweaver-1.7.1.jar+aspectj-1.8.9.jar+aopalliance.jar`组合提供了Spring AOP的强大支持,允许开发者使用AspectJ语言特性实现切面,同时也保持与其他AOP框架的兼容性。通过这个组合,我们...

    Spring AOP + AspectJ annotation example

    AspectJ是Spring AOP支持的一种强大的、独立的AOP语言,它提供了注解来简化切面的定义。本篇将深入探讨如何结合Spring AOP和AspectJ注解进行实践。 首先,我们需要理解AOP的基本概念。面向切面编程是一种编程范式,...

    SpringAOP+AspectJ

    **Spring AOP与AspectJ详解** 在现代软件开发中,面向切面编程(Aspect-Oriented Programming,简称AOP)是一种强大的设计模式,它允许我们分离关注点,将横切关注点(如日志、事务管理、权限控制等)与核心业务...

    Spring AOP + AspectJ in XML 配置示例

    这篇博客“Spring AOP + AspectJ in XML配置示例”旨在指导开发者如何在XML配置中实现Spring AOP和AspectJ的结合。 首先,我们需要理解AOP的基本概念。AOP通过将关注点(如日志、事务管理)与业务逻辑分离,提高了...

    Android+反射+代理+静态代理+AspectJ

    【Android + 反射 + 代理 + 静态代理 + AspectJ】 面向切面编程(AOP)是一种编程范式,与面向对象编程(OOP)相对,它旨在将关注点分离,使得通用功能(如日志记录、事务管理等)可以从核心业务逻辑中解耦。AOP 的...

    maven+spring+springMVC+mybatis

    * aspectjweaver:这是一个面向方面(面向切面)的包,提供了 AspectJ 语言的支持。 * javax.servlet-api:这是一个 servlet 的 API,提供了 Web 应用程序的基础功能。 * mysql-connector-java:这是一个 MySQL ...

    spring+springmvc+mybatis等架包

    `aspectjweaver-1.6.9.jar`是AspectJ的编译器和运行时库,AspectJ是一种强大的面向切面编程(AOP)框架,可以将关注点(如日志、事务管理)与核心业务逻辑分离。 `mysql-connector-java-5.1.0-bin.jar`是MySQL...

    Realm + aspectj aop数据库封装

    本项目结合AspectJ,一个面向切面编程(AOP)框架,将 Realm 进行了进一步的封装,以提高代码的可维护性和可扩展性。 首先,我们要理解 Realm 的基本用法。Realm 提供了类似Java集合的API,可以方便地创建、查询和...

    spring+springmvc+hibernate+redis 需要的jar包

    AspectJ的编织器`aspectjweaver.jar`是AspectJ框架的一部分,用于在运行时或者编译时实现AOP功能,它可以将切面代码织入到主程序中。 `javassist-3.15.0-GA.jar`是Java编程辅助类库,常用于动态修改类和生成新的类...

    开发者突击 精通AOP整合应用开发AspectWerkz+AspectJ+Spring.zip

    本书以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后详细讲解AspectWerkz、AspectJ、Spring框架的AOP应用开发技术。 随书附赠的光盘内容为本书开发的案例程序包。本书内容循序...

    开发者突击 精通AOP整合应用开发AspectWerkz+AspectJ+Spring.z01

    本书以AOP基础理论为主线,首先讲解AOP的产生与发展、为什么要应用AOP、AOP的核心概念,然后详细讲解AspectWerkz、AspectJ、Spring框架的AOP应用开发技术。 随书附赠的光盘内容为本书开发的案例程序包。本书内容循序...

    springmvc+spring+mybatis

    &lt;aop:aspectj-autoproxy proxy-target-class="true"/&gt; &lt;!-- 载入jdbc.properties文件 --&gt; &lt;bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&gt; &lt;value&gt;classpath:jdbc...

    Spring+mybatis环境搭建

    * org.springframework.aspects-3.0.6.RELEASE:提供了对 AspectJ 框架的整合 * org.springframework.beans-3.0.6.RELEASE:提供了 IoC(依赖注入)的基础实现 * org.springframework.context.support-3.0.6.RELEASE...

    spring+springMVC所需的包

    10. **spring-aop-4.2.5.RELEASE.jar**:Spring AOP模块提供了面向切面编程的支持,与AspectJ Weaver结合使用,可以实现声明式事务管理、缓存、性能监控等功能。 通过这些库,开发者可以构建一个完整的Spring + ...

    spring + hibernate 开发需要 jar包

    4. **aspectjweaver-1.6.10.jar**:AspectJ是AOP(面向切面编程)的实现,Spring框架利用它进行声明式事务管理和其他横切关注点,如日志、性能监控等。 5. **jackson-all-1.8.2.jar**:Jackson是一个流行的JSON处理...

    Spring+SpringMVC+Hibernate的jar包集合

    是AspectJ库,SpringAOP就是基于AspectJ实现的,两者结合可以方便地进行日志记录和切面编程。 - `jackson-mapper-asl-1.8.8.jar`是Jackson JSON处理库,用于JSON序列化和反序列化,方便数据交换。 - `mysql-...

    用户注册和登录是每个系统的必须存在的一部分,基于Dwr+Struts2+Spring+Hibernate写了一个用户登录注册系统

    7. // 如果不用此包,在启动时抛 出:nested exception is java.lang.NoClassDefFoundError: org/aspectj /weaver /reflect/ReflectionWorld$ReflectionWorldException 8. aspectjweaver.jar 9. 10. //如果不用此...

    spring基于AspectJ实现事务控制

    在Spring框架中,提供了多种事务管理方式,其中之一就是基于AspectJ的事务控制。本文将深入探讨如何在Spring中利用AspectJ实现事务控制。 首先,我们需要理解什么是AspectJ。AspectJ是一种面向切面编程(AOP)的...

    SpringMVC+Spring+Mybatis整合jar包

    SpringMVC、Spring和Mybatis是Java开发中常用的三大框架,它们各自负责应用程序的不同层面:SpringMVC处理Web层逻辑,Spring提供全面的依赖注入和面向切面编程,而Mybatis则专注于数据库操作。整合这三大框架,可以...

    aspectjweaver.jar+aspectjrt.jar+aspectj-1.8.9-src.jar

    此外,AspectJ 还支持类型安全的 AOP,可以与其他 Java 工具和框架无缝集成,如 Spring 框架。 总之,这个压缩包提供了 AspectJ 的核心组件,允许开发者利用面向切面编程的强大功能来改善 Java 应用程序的设计和...

Global site tag (gtag.js) - Google Analytics