14

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
Thierry Abaléa
  • 206
  • 1
  • 2
  • 5

2 Answers2

16

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.

Vincent
  • 1,035
  • 6
  • 14
  • Thank you! I think this is pretty close explanation of what's going on. I am not 100% sure because from what I heard Spring uses 2 class loaders and 2 class load passes - a test class & its references should be first loaded into 1st loader, annotations analyzed etc, but then they get discarded and being loaded via AspectJ provided loader which does the weaving... In theory it should allow weaving to happen properly but somehow it does not. I will try adding aspectjweaver javaagent and/or reweavable options to see if it fixes it! – alexandroid Mar 06 '13 at 18:43
  • 3
    f***ng.... brilliant. i just took the biggest breath, after the most frustrating few hours, that I have in quite a while after taking your advice. For the record, including an `aspectjweaver` along with `spring-instrument` as a javaagent enabled me to get load-time weaving running properly in a spring-boot application, junit tests included – drew moore Sep 13 '14 at 12:23
  • @drewmoore I am working with a spring-boot mvc application and need to invoke one method from another method within the same controller class, where the latter method is annotated with `@Async`. I was told that this is not possible unless I enable `AspectJ proxy mode and provide a weaver`. Is this similar to what you are doing? – Web User Mar 28 '15 at 13:18
  • I'm using Spring 4.2.6 and it seems that the agent provided by spring, as long as the spring related configuration is just useless if you use the agent provided by AspectJ ... that's all ! At least it worked like a charm for me (I'm also using multiple classloading management). – Donatello Sep 30 '16 at 15:18
0

First of all , if you are using maven, set your pom.xml:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-instrument</artifactId>
        <version>3.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.7</version>
    </dependency>

Then you have had to compile your code using the aspectj compiler. This compiler generate an aop.xml file in META-INF/aop.xml

(I'm using eclipse sts) After that, I want to run a Junit test. So you have to set your VM args in the eclipse run configuration window: -javaagent:${ASPECTJ_WEAVER_1.7}\aspectjweaver-1.7.0.jar -javaagent:${SPRING_INSTRUMENT}\spring-instrument-3.1.2.RELEASE.jar

where ${ASPECTJ_WEAVER_1.7} ${SPRING_INSTRUMENT} are an environtment var. Use the var button to create these vars (is located at bottom right of the window). These vars target to the folders where the aspectjweaver-1.7.0.jar and spring-instrument-3.1.2.RELEASE.jar are located. Follow the asistant to make this. It's not difficult. Take care that the previous javaagent lines haven't any invisible strange character or similar. It's sound strange but I had to rewrite several times the same line until eclipse said that this line is fine.

Then, you can run your Junit test. The first you can see is aspectj runtime loading. Later you will see spring loading... and after that your test will run is haven't got any spring problem or similar. This is a heavy process.

I hope this information can help you

Regards

Antonio Martin
  • 361
  • 3
  • 12