How to configure AspectJ with Load Time Weaving without Interface

15,903

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.

Share:
15,903

Related videos on Youtube

Thierry Abaléa
Author by

Thierry Abaléa

Updated on June 04, 2022

Comments

  • Thierry Abaléa
    Thierry Abaléa almost 2 years

    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
    
  • alexandroid
    alexandroid about 11 years
    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!
  • drew moore
    drew moore over 9 years
    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
  • Web User
    Web User about 9 years
    @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?
  • Donatello
    Donatello over 7 years
    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).