Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure AspectJ with Load Time Weaving without Interface

On my project, I currently use AspectJ (not just Spring AOP due to some limitation) with the weaving at the Compile Time. In order to speed up the development on Eclipse, I want to do the weaving at the Load Time. I succeed to do that but with one major constraint: using an interface for my service that contained some transactional methods. If I declare the service with its implementation instead of its interface, in the caller class, there is no weaving and so no transaction supported.

So if it is supported by AspectJ, how to configure AspectJ with Load Time Weaving without Interface ?

I created a little project that reproduce the issue:

The following test fail.

The following test succeed if :

  • the injected service is declared with its interface instead of its implementation (i.e. replace "@Inject MyServiceImpl service" by "@Inject MyService service"), the test succeed.

  • the weaving is executed during the compilation (the configuration, POM & Spring application context, is obviously different in this case). But my goal is to do the weaving at the Load-Time to avoid a weaving phase every time I save a Java file.

  • Spring AOP (tx:annotation-driven mode="proxy"), that is a proxy-based solution, is used instead of AspectJ. But in this case, we encountered the self-invocation issue, i.e. a method within the target object calling some other method of the target object, won’t lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

aspectj-ltw/src/test/java/mycompany/aspectj_ltw/MyServiceImplTest.java

package mycompany.aspectj_ltw;

import static junit.framework.Assert.assertTrue;

import javax.inject.Inject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/spring/applicationContext.xml" })
public class MyServiceImplTest {

    @Inject
    MyServiceImpl service;

    @Test
    public void shouldBeExecutedInTransaction() {
        assertTrue(this.service.isExecutedInTransaction());
    }
}

aspectj-ltw/src/main/java/mycompany/aspectj_ltw/MyService.java

package mycompany.aspectj_ltw;

public interface MyService {

    boolean isExecutedInTransaction();

}

aspectj-ltw/src/main/java/mycompany/aspectj_ltw/MyServiceImpl.java

package mycompany.aspectj_ltw;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Service
public class MyServiceImpl implements MyService {

    @Transactional
    public boolean isExecutedInTransaction() {
        return TransactionSynchronizationManager.isActualTransactionActive();
    }

}

aspectj-ltw/src/test/resources/META-INF/applicationContext.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd   http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <context:component-scan base-package="mycompany.aspectj_ltw" />

    <context:load-time-weaver aspectj-weaving="on" />
    <aop:config proxy-target-class="true"/>
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <tx:annotation-driven mode="aspectj"
        transaction-manager="transactionManager" proxy-target-class="true" />

    <bean class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" id="dataSource">
        <property name="driverClassName" value="org.h2.Driver" />
        <property name="url" value="jdbc:h2:mem:mydb" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        id="transactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

aspectj-ltw/src/test/resources/META-INF/aop.xml

<!DOCTYPE aspectj PUBLIC
        "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
  <weaver options="-showWeaveInfo -debug -verbose -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
        <include within="mycompany.aspectj_ltw..*"/>
  </weaver>
</aspectj>

aspectj-ltw\pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>mycompany</groupId>
    <artifactId>aspectj-ltw</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>aspectj-ltw</name>

    <properties>
        <spring.version>3.0.5.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.2.143</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>0.9.24</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.24</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.6.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <forkMode>always</forkMode>
                    <argLine>
                        -javaagent:C:/maven-2_local_repo/org/springframework/spring-instrument/3.0.5.RELEASE/spring-instrument-3.0.5.RELEASE.jar
                    </argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

VM arguments to run the test:

-javaagent:C:/maven-2_local_repo/org/springframework/spring-instrument/3.0.5.RELEASE/spring-instrument-3.0.5.RELEASE.jar
like image 698
Thierry Abaléa Avatar asked Oct 31 '12 16:10

Thierry Abaléa


People also ask

What is Weaver in AspectJ?

An aspect weaver is a metaprogramming utility for aspect-oriented languages designed to take instructions specified by aspects (isolated representations of significant concepts in a program) and generate the final implementation code.

What is the primary difference between AspectJ implementation of AOP and Spring implementation of AOP?

AspectJ is known to be 8 to 35 times faster than Spring AOP. Best of Both: Both of these frameworks are fully compatible with each other. We can always take advantage of Spring AOP whenever possible and still use AspectJ to get support of joinpoints that aren't supported by the former.

What is an AOP XML file?

An aop. xml file contains two key sections: "aspects" defines one or more aspects to the weaver and controls which aspects are to be used in the weaving process; "weaver" defines weaver options and which types should be woven.


1 Answers

If I'm not mistaken, the issue here is not due to AspectJ, but rather to the way things work in the precise JUnit use case. When running your test, the MyServiceImplTest class is loaded first, before the Spring context was created (you need the test class' annotations to get the appropriate runner and config locations), hence before any Spring AOP mechanism was leveraged. That is, at least, the explanation I came up with when I faced the very same situation a few months ago... Since the javaagent is there from the JVM startup on, one would have to fully read/understand the weaver's code to precisely explain why it fails here (I didn't :p).

So anyway, the MyServiceImplTest type, along with all its member's types, which are loaded with it - this goes for types in method signatures as well -, cannot be woven.

To work around this:

  • either avoid using the woven types in the test class members and methods signature (e.g. using interfaces like you did)
  • or add the AspectJ weaver to your javaagents (in addition to the spring-instrument one); with this, if I recall correctly, Spring should be able to get its AOP-based mechanisms to work properly:

    -javaagent:/maven-2_local_repo/org/aspectj/aspectjweaver/1.7.0/aspectjweaver-1.7.0.jar -javaagent:/maven-2_local_repo/org/springframework/spring-instrument/3.0.5.RELEASE/spring-instrument-3.0.5.RELEASE.jar

Nota: in your META-INF/aop.xml, it may be necessary to add the -Xreweavable weaver option.

like image 85
Vincent Avatar answered Sep 29 '22 18:09

Vincent