如何解决spring bean通过this访问实例方法时@Transactional失效

在spring bean里,通过this调用类内另外一个标了@Transactional的方法,结果声明式事务失效。为什么呢?怎么解决呢?

问题

对于如下的示例代码:

@Component
public class TransactionTest {
    public void businessMethod() {
        try {
            this.step1();
        } catch (Exception e) {
            ;
        }
        try {
            this.step1();
        } catch (Exception e) {
            ;
        }
    }

    @Transactional
    public void step1() {
    }

    @Transactional
    public void step2() {
    }
} 

 期望businessMethod被调用时,分别在两个事务里面执行step1,step2,在spring里面,如果不使用aspectj,是达不到期望的效果的。

原因

前面的文章 AOP简介,理解AOP简单地说明了一下aop的原理,spring的aop实现依赖于java动态代理,cglib动态子类和aspectj。前两者本质上都是生成一个被切类的子类作为被切类的代理类,而aspectj则是直接修改我们java代码编译出的class,在各个方法调用处添加额外的aop处理逻辑。代理方式存在一个不足之处,就是通过this调用自己的方法的时候是不会经过aop的。看看下面的示例代码就知道了。对于上面的例子,动态代理类大概会是这个样子:

public class TransactionTest$proxy extends TransactionTest {
    private TransactionTest target;

    //被代理的对象
    public TransactionTest$proxy(TransactionTest target) {
        this.target = target;
    }

    public void businessMethod() { //没有切面
        target.businessMethod();
    }

    @Transactional
    public void step1() {
        aopBefore();
        target.step1(); //执行被代理对象的方法
        aopAfter();
    }

    @Transactional
    public void step2() {
        aopBefore();
        target.step1(); //执行被代理对象的方法
        aopAfter();
    }
}

上面只是示例代码,实际中要复杂得多。当你在spring中配置了TransactionTest类的bean时,实际beanFactory返回的是TransactionTest$proxy的实例,就像这样:

return new TransactionTest$proxy(new TransactionTest());

同样上面只是个示例,实际肯定是用反射之类的技术实现的,也比这复杂的多,但是你得到的对象大体上就是这个样子的。这样外部代码调用这个对象的step1() step2()方法时,就会执行aop相关动作,在本例中aop会去执行事务相关操作。可以看到,如果在TransactionTest类的内部直接调用内部方法,那么就不会执行到TransactionTest$proxy类的方法,也就不会执行aop,那依赖于aop的事务也就不起效果了。

应对之道

为了避免这样的情况发生,要么不要通过this调用有aop的方法,要么不要采用代理方式的AOP。前者就是把step1, step2方法和businessMethod方法分别定义到两个类里面,可以这么写:

@Component
public class TransactionTest {
    @Autowired
    private TransactionTestAopWrapper wrapper;

    public void businessMethod() {
        try {
            wrapper.step1();
        } catch (Exception e) {
            ;
        }
        try {
            wrapper.step1();
        } catch (Exception e) {
            ;
        }
    }

    @Component
    public static class TransactionTestAopWrapper {
        @Transactional
        public void step1() {
        }

        @Transactional
        public void step2() {
        }
    }
} 

如果觉得太难看,不能接受这样的代码,那就采用aspectj,在编译期,或者classloader载入代码时,修改class文件,被修改后的代码大体上如下:

public class TransactionTest {
    public void businessMethod() {
        try {
            this.step1();
        } catch (Exception e) {
            ;
        }
        try {
            this.step1();
        } catch (Exception e) {
            ;
        }
    }

    @Transactional
    public void step1() {
        aopBefore(); 
        //... 
        // 原来的代码 
        aopAfter(); 
    } 
    
    @Transactional public void step2() { 
        aopBefore(); 
        //... 
        // 原来的代码 
        aopAfter(); 
    } 
}

这种方式下,随便怎么调用都会走到aop了。而且,可以拦截几乎所有代码,比如构造函数。aspectj详细使用信息,看spring文档及aspectj官方文档,配置起来还是有点麻烦的。

转载于:https://my.oschina.net/komodo/blog/919163

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值