@Transactional 不适用于 AspectJ

@Transactional not applied with AspectJ

我决定使用 AspectJ 来避免无法从同一个 class

调用带有 @Transactional 注释的方法这一事实

所以我添加了这个配置:

@Configuration
@EnableTransactionManagement(mode= AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving= AspectJWeaving.ENABLED)
public class App implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new InstrumentationLoadTimeWeaver();
    }

}

build.gradle

runtimeOnly("org.aspectj:aspectjweaver:1.9.7")

我 运行 应用程序(spring 嵌入了 tomcat 的启动应用程序)-javaagent:C:\xx\xx\.m2\repository\org\springframework\spring-instrument-5.3.12.jar

但是当我尝试

public void m1() {
    this.m2()
}


@Transactional(propagation = Propagation.REQUIRED)
public void m2() {
    ....
}

似乎m2()方法没有在事务中执行,
在我使用这些日志记录级别进行调试时的日志中:

logging.level.org.springframework.transaction.interceptor=trace
logging.level.org.springframework.orm.jpa=trace

没有这样的行:

Creating new transaction with name [xxx.m2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

我是不是遗漏了什么?

我克隆了您的 GitHub 项目,然后添加了

implementation 'org.springframework:spring-instrument'

您还应该将 Spring(事务)方面范围限制为仅编织您自己的应用程序 classes,以避免在尝试编织时出现大量 [Xlint:cantFindType] 消息 Spring 自己的 classes。您可以通过提供自己的 src/main/resources/org/aspectj/aop.xml 文件来执行此操作,如下所示:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

  <!-- You can also add -Xlint:ignore in order to avoid lots of '[Xlint:cantFindType]' warnings -->
  <weaver options="-verbose -showWeaveInfo">
    <!-- Only weave classes in our application-specific packages -->
    <include within="com.example.aspectj..*"/>
  </weaver>

</aspectj>

编织器选项还可以更轻松地查看哪些方面被编织到哪些连接点中,例如在启动期间你会看到

[AppClassLoader@77556fd] weaveinfo Join point 'method-execution(void com.example.aspectj.services.FooService.m2())' in Type 'com.example.aspectj.services.FooService' (FooService.java:27) advised by around advice from 'org.springframework.transaction.aspectj.JtaAnnotationTransactionAspect' (AbstractTransactionAspect.aj:67)

证明FooService.m2()确实在编织

对于不那么“嘈杂”的 AspectJ 编织器,只需使用 <weaver options="-showWeaveInfo -Xlint:ignore"> - 不再有关于哪些方面已注册的警告或信息,但仍然有关于编织连接点的信息,这很重要,IMO。

然后我使用 Spring 工具和 AspectJ 编织器代理启动应用程序。只有前者不足以启动仪器,我需要两个代理。因为在JDK 16+需要打开java.lang包到未命名的模块才能应用LTW,我在最近的JDK上测试,我也添加了相应的--add-opens 选项(在 JDK 15 之前不需要):

--add-opens java.base/java.lang=ALL-UNNAMED
-javaagent:.../aspectjweaver-1.9.7.jar
-javaagent:.../spring-instrument-5.3.12.jar

然后一切如预期的那样工作:

o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
com.example.aspectj.AspectjApplication   : Started AspectjApplication in 7.339 seconds (JVM running for 10.046)
com.example.aspectj.AspectjApplication   : running ..
com.example.aspectj.services.FooService  : m1 : called
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.aspectj.services.BooService.m3]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1516190088<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@543f6ccb]
com.example.aspectj.services.BooService  : m3 : called
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1516190088<open>)]
o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1516190088<open>)] after transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.aspectj.services.FooService.m2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(45178615<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@20e4fce0]
com.example.aspectj.services.FooService  : m2 : called
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(45178615<open>)]
o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(45178615<open>)] after transaction

更新: 对于开箱即用的 Spring (Boot) 带来的问题,我真的很抱歉,但是现在,你要么使用 AspectJ 核心转储文件 ajdump.*.txt - 交易方面编织仍然有效,就像我之前说的 - 或者使用你自己的 aop.xml 文件(见上文)。作为包含您自己的用于方面编织的应用程序基础包的替代方法,您也可以采取相反的方向排除 classes 或导致核心转储的包。在 Spring Boot 2.5.6 中,您只需添加

<exclude within="org.springframework.boot.jdbc.DataSourceBuilder.OraclePoolDataSourceProperties"/>

在 Spring Boot 2.3.3 中,AspectJ 抱怨这个 class:

<exclude within="org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer"/>

我认为这需要在 Spring Boot 或 Spring Core、Spring-TX 或 Spring-Aspects 中修复,无论这些方面位于何处.


更新 2: 我创建了 Spring Core issue #27650 以跟踪 AspectJ 核心转储问题。这不是你原来问题的根本原因,因为交易方面编织无论如何都有效,但无论如何都需要在 Spring(可能在 AspectJ)中解决。

我的理解是,如果您有一个 bean fooService,并且您在 fooService 上调用了一个方法 m1,并且不会创建事务,因此不会创建任何事务当您调用另一个方法时,在本例中为 m2,这是预期的行为。

如果您以相反的方式进行操作,则会创建一个交易,如:m2 调用 m1。我只知道这一点,因为我们在项目中遇到了同样的问题,这非常令人困惑。不确定为什么会这样,我尝试查找有关此的文档,如果找到它,我会将其添加到我的答案中。总之,事务只能通过在 bean 外部调用 bean 上的 @Transactional 方法来创建,而不能由 bean 本身通过另一个非事务性方法来创建。