【问题标题】:How to configure AspectJ with Load Time Weaving without Interface如何在没有接口的情况下使用加载时间编织配置 AspectJ
【发布时间】:2016-04-28 16:00:58
【问题描述】:

在我的项目中,我目前使用 AspectJ(由于某些限制而不仅仅是 Spring AOP)在编译时进行编织。为了加快 Eclipse 上的开发,我想在 Load Time 进行编织。我成功地做到了这一点,但有一个主要限制:为我的服务使用包含一些事务方法的接口。 如果我用它的实现而不是它的接口声明服务,在调用者类中,没有编织,因此不支持事务。

那么如果AspectJ支持的话,如何配置AspectJ with Load Time Weaving without Interface?

我创建了一个小项目来重现该问题:

以下测试失败。

如果以下测试成功:

  • 注入的服务是用它的接口而不是它的实现来声明的(即将“@Inject MyServiceImpl 服务”替换为“@Inject MyService 服务”),测试成功。

  • 在编译期间执行编织(在这种情况下,配置、POM 和 Spring 应用程序上下文显然不同)。但我的目标是在加载时进行编织,以避免每次保存 Java 文件时都进入编织阶段。

  • 使用 Spring AOP (tx:annotation-driven mode="proxy"),这是一种基于代理的解决方案,而不是 AspectJ。但是在这种情况下,我们遇到了自调用问题,即目标对象中的一个方法调用目标对象的某个其他方法,即使调用的方法标有@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>

运行测试的虚拟机参数:

-javaagent:C:/maven-2_local_repo/org/springframework/spring-instrument/3.0.5.RELEASE/spring-instrument-3.0.5.RELEASE.jar

【问题讨论】:

    标签: spring aop aspectj spring-transactions load-time-weaving


    【解决方案1】:

    如果我没记错的话,这里的问题不在于 AspectJ,而是在于精确的 JUnit 用例中的工作方式。运行测试时,首先加载 MyServiceImplTest 类,然后再创建 Spring 上下文(您需要测试类的注释来获取适当的运行器和配置位置),因此在利用任何 Spring AOP 机制之前。也就是说,至少,当我几个月前遇到同样的情况时,我想出的解释......由于 javaagent 从 JVM 启动开始就在那里,因此必须完全阅读/理解编织者的代码才能准确解释为什么它在这里失败(我没有:p)。

    所以无论如何,MyServiceImplTest 类型,连同它的所有成员类型,连同它一起加载——这也适用于方法签名中的类型——不能被编织。

    要解决这个问题:

    • 要么避免在测试类成员和方法签名中使用编织类型(例如,像你一样使用接口)
    • 或将 AspectJ weaver 添加到您的 javaagents(除了 spring-instrument 之一);有了这个,如果我没记错的话,Spring 应该能够使其基于 AOP 的机制正常工作:

      -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

    注意:在您的META-INF/aop.xml 中,可能需要添加-Xreweavable weaver 选项。

    【讨论】:

    • 谢谢!我认为这是对正在发生的事情的非常接近的解释。我不是 100% 确定,因为据我听说 Spring 使用 2 个类加载器和 2 个类加载传递 - 一个测试类及其引用应该首先加载到第一个加载器中,分析注释等,但随后它们被丢弃并通过 AspectJ 加载提供了进行编织的装载机......理论上它应该允许编织正确发生,但不知何故它没有。我会尝试添加 aspectjweaver javaagent 和/或 reweavable 选项,看看它是否修复它!
    • f***ng....太棒了。在最令人沮丧的几个小时之后,我在听取了您的建议后的很长一段时间内才深吸了一口气。作为记录,包括 aspectjweaverspring-instrument 作为 javaagent 使我能够在 spring-boot 应用程序中正常运行加载时编织,包括 junit 测试
    • @drewmoore 我正在使用一个 spring-boot mvc 应用程序,需要从同一个控制器类中的另一个方法调用一个方法,其中后一个方法用 @Async 注释。有人告诉我,除非我启用AspectJ proxy mode and provide a weaver,否则这是不可能的。这与您正在做的事情相似吗?
    • 我用的是Spring 4.2.6,好像是spring提供的代理,只要用了AspectJ提供的代理,只要spring相关的配置都没用……就这样!至少它对我来说很有魅力(我也在使用多个类加载管理)。
    【解决方案2】:

    首先,如果你使用maven,设置你的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>
    

    然后您必须使用 aspectj 编译器来编译您的代码。 这个编译器在 META-INF/aop.xml 中生成一个 aop.xml 文件

    (我正在使用eclipse sts) 之后,我想运行一个 Junit 测试。 所以你必须在 eclipse 运行配置窗口中设置你的 VM 参数: -javaagent:${ASPECTJ_WEAVER_1.7}\aspectjweaver-1.7.0.jar -javaagent:${SPRING_INSTRUMENT}\spring-instrument-3.1.2.RELEASE.jar

    其中 ${ASPECTJ_WEAVER_1.7} ${SPRING_INSTRUMENT} 是环境变量。使用 var 按钮创建这些变量(位于窗口的右下角)。这些变量定位到 aspectjweaver-1.7.0.jar 和 spring-instrument-3.1.2.RELEASE.jar 所在的文件夹。跟着助手做这个。这并不难。 注意前面的 javaagent 行没有任何不可见的奇怪字符或类似字符。这听起来很奇怪,但我不得不在同一行重写几次,直到 eclipse 说这行没问题。

    然后,您可以运行您的 Junit 测试。 您可以看到的第一个是 aspectj 运行时加载。稍后您将看到弹簧加载......之后您的测试将运行,没有任何弹簧问题或类似问题。 这是一个繁重的过程。

    希望这些信息对你有帮助

    问候

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-10
      • 1970-01-01
      • 2011-07-27
      相关资源
      最近更新 更多