使用AspectJ Compiler解决Spring @Transactional方法自调用失效

本文介绍如何通过配置Spring使用AspectJ来为私有方法提供事务管理功能,包括配置步骤及如何启用AspectJ编译器,并展示了AspectJ生成的代码示例。

If we want to use the @Transactional annotation on private methods, we cannot use Spring’s default proxy mode - it only works for public methods that are called from client code.

For self-invoked methods we need to use AspectJ.

An Example

This may be the case e.g. when we want to save incoming data and send a letter using that data within a transaction. If saving the data fails we do not want to send the letter. If sending the letter fails we do not want to loose the data that we received. Hence, we want to save the data in a separate transaction, by annotating our saveData(..) method with @Transactional(propagation = Propagation.REQUIRES_NEW).

Configuring Spring To Use AspectJ

On a @Configuration class we just need to set the AspectJ advice mode for the transaction management configuration:

1
2
3
4
5
6
@Configuration
@ComponentScan
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
class ApplicationConfiguration {
    // ...
}

Configuring Maven To Run The AspectJ Compiler

Next we need to use the AspectJ compiler, so let’s add its needed dependencies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.7.4</version>
    </dependency>

    <!-- In this example we use JPA for persistence. -->
    <dependency>
        <groupId>org.hibernate.javax.persistence</groupId>
        <artifactId>hibernate-jpa-2.1-api</artifactId>
        <version>1.0.0.Final</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>4.0.6.RELEASE</version>
    </dependency>
</dependencies>

The AspectJ compiler itself can then be enabled with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.6</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <!-- <goal>test-compile</goal> -->
                    </goals>
                </execution>
            </executions>
            <configuration>
                <complianceLevel>1.7</complianceLevel>
                <forceAjcCompile>true</forceAjcCompile>
                <weaveDirectories>
                    <weaveDirectory>${project.build.outputDirectory}</weaveDirectory>
                </weaveDirectories>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
        </plugin>
    </plugin>
</build>

If we do not use <forceAjcCompile> and do not set the <weaveDirectory> AspectJ will not weave the transaction code into our generated Groovy bytecode. Instead, it will warn us with No sources found skipping aspectJ compile when Maven runs. That is as the aspectj-maven-plugin processes the src/main/java directory by default but not src/main/groovy.

How The AspectJ Generated Code Looks Like

Just for completeness, let’s look how AspectJ has wrapped the saveData(..) method with a transaction. Using the Procyon decompiler we can see:

1
2
3
4
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveData(final SomeData data) {
    ((AbstractTransactionAspect)AnnotationTransactionAspect.aspectOf()).ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c((Object)this, (AroundClosure)new DataService$AjcClosure3(new Object[] { this, data }), DataService.ajc$tjp_1);
}

转载于:https://my.oschina.net/yqz/blog/1603556

<think>好的,用户问的是Spring Boot中方法自调用导致@Transactional事务失效的问题,需要解决方案。我之前整理过相关的引用资料,现在需要结合这些资料来思考如何回答。 首先,用户提供的引用里提到,Spring的事务管理是基于AOP的动态代理机制。当自调用发生时,比如一个类中的方法A调用了方法B,而方法B有@Transactional注解,这时候事务会失效。因为自调用没有经过代理对象,而是直接调用了目标方法,绕过了Spring的AOP处理,导致事务注解没被拦截。 根据引用3中的例子,用户可能是在类内部通过this调用另一个事务方法,导致失效解决方案需要确保调用的是代理对象的方法,而不是this的直接方法。比如引用2中的示例,通过自注入代理对象,然后在内部调用时使用这个代理对象来触发事务方法,这样就能让事务生效。 接下来,我需要总结几种常见的解决方案。首先想到的是自注入代理对象,也就是在类中注入自己的代理实例,然后通过这个代理来调用方法。这样调用会经过Spring的代理,触发事务管理。另外,使用AopContext.currentProxy()方法获取当前代理对象也是一个办法,不过需要开启AspectJ的暴露代理功能。还有,可以将事务方法拆分到另一个Service类中,通过外部调用避免自调用问题。此外,配置文件中启用暴露代理也是一个需要注意的点。 需要检查用户的问题是否还有其他可能的解决方案,比如引用1中提到的PROPAGATION_REQUIRED传播机制,但自调用情况下传播机制可能无法生效,因为事务的上下文没有被正确传递。所以重点还是确保调用的是代理对象。 另外,用户可能需要具体的代码示例,比如如何自注入代理对象,或者如何配置AopContext。需要详细说明每个步骤,确保用户能够理解和实施。 还要注意,使用@Async和@Transactional一起使用时,同样的问题会出现,所以引用2中的解决方法可能也适用,即通过代理对象调用事务方法。同时,用户可能对为什么自调用失效有疑问,所以需要解释清楚Spring的AOP机制和动态代理的原理。 最后,要确保回答结构清晰,分点列出解决方案,每个方案都有具体步骤和示例代码,方便用户参考。可能还需要提醒用户在使用自注入时注意循环依赖的问题,或者使用AopContext时的配置要求。</think>在Spring Boot中解决自调用导致@Transactional事务失效的问题,需理解其根本原因和对应策略。以下是具体解决方案及实现步骤: --- ### 一、问题原因分析 Spring事务基于动态代理实现,自调用(类内部方法相互调用)会绕过代理对象,导致事务注解失效。例如: ```java public void methodA() { this.methodB(); // 直接调用本类方法,事务失效 } @Transactional public void methodB() { // 事务操作... } ``` --- ### 二、解决方案汇总 #### 1. 自注入代理对象(推荐) 通过注入本类的代理对象调用事务方法: ```java @Service public class MyService { // 注入自身代理对象 private MyService proxySelf; @PostConstruct public void initProxy() { proxySelf = SpringContextHolder.getBean(MyService.class); } public void methodA() { proxySelf.methodB(); // 通过代理对象调用事务方法 ✅ } @Transactional public void methodB() { // 事务操作... } } ``` #### 2. 使用AopContext获取当前代理 通过AopContext暴露代理对象调用方法: ```java // 第一步:配置类开启暴露代理 @EnableAspectJAutoProxy(exposeProxy = true) public class AppConfig {} // 第二步:调用时获取代理对象 public void methodA() { ((MyService) AopContext.currentProxy()).methodB(); // 通过代理调用 ✅ } ``` #### 3. 事务方法拆分到其他Service 将事务方法独立到另一个Service类中,通过外部调用避免自调用: ```java @Service public class ServiceA { @Autowired private ServiceB serviceB; public void methodA() { serviceB.methodB(); // 跨类调用事务方法 ✅ } } @Service public class ServiceB { @Transactional public void methodB() { // 事务操作... } } ``` #### 4. 强制使用CGLIB代理(兼容性增强) 在配置文件中明确指定代理方式: ```properties # application.properties spring.aop.proxy-target-class=true ``` --- ### 三、关键原理说明 1. **动态代理机制** Spring通过JDK动态代理或CGLIB生成代理对象,只有通过代理对象调用方法时,事务拦截器才会生效[^3]。 2. **自调用问题本质** 自调用使用`this.method()`会直接调用原始对象的方法,而非代理对象,导致事务拦截器未被触发[^3]。 3. **代理对象注入必要性** 通过注入自身代理对象或使用`AopContext`,可确保调用链经过代理对象,从而激活事务管理[^2]。 --- ### 四、选择建议 1. **小型项目**:推荐自注入代理对象(方案1),实现简单且代码直观。 2. **复杂调用链**:使用AopContext(方案2)或拆分Service(方案3),增强可维护性。 3. **遗留代码改造**:优先采用CGLIB代理(方案4),减少代码侵入性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值