Spring AOP self invocation存在的问题以及如何解决

Self invocation存在的问题

假设我们有如下的TestService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class TestServiceImpl implements TestService {

    @Override
    public void saveAB() {
        this.saveA();
        this.saveB();
    }

    @Transactional
    @Override
    public void saveA() {
        System.out.println("saveA");
    }

    @Transactional
    @Override
    public void saveB() {
        System.out.println("saveB");
    }
}

还有如下的TestServiceTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestServiceTest {

    @Autowired
    private TestService testService;

    @Test
    public void test() {
        testService.saveAB();
        testService.saveA();
        testService.saveB();
    }
}

TestServiceTest.test()中,虽然testService.saveA()testService.saveB()会在事务中执行,但是testService.saveAB()中的this.saveA()this.saveB()都不会在事务中执行,这就是self invocation存在的问题。

为什么self invocation会存在问题?

首先我们必须要清楚Spring AOP是基于代理的,但是在介绍代理之前,先来看看没有代理时是怎么样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SimplePojo implements Pojo {

    @Override
    public void foo() {
        System.out.println("foo");
        this.bar();
    }

    @Override
    public void bar() {
        System.out.println("bar");
    }
}

1
2
3
4
5
6
7
public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        pojo.foo();
    }
}

执行Main.main(String[] args),会输出如下内容:

1
2
foo
bar

当我们调用pojo.foo()时,我们是直接调用pojofoo(),如下图所示:


接下来我们就看看调用代理的方法是怎样的。

我们修改Main为如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {

    public static void main(String[] args) {
        SimplePojo target = new SimplePojo();
        ProxyFactory factory = new ProxyFactory(target);
        factory.addInterface(Pojo.class);
        factory.addAdvice(new MethodBeforeAdvice() {
            @Override
            public void before(Method method, Object[] args, Object target) throws Throwable {
                System.out.println("before invoking method");
            }
        });

        Pojo pojo = (Pojo) factory.getProxy();
        pojo.foo();
    }
}

执行Main.main(String[] args),会输出如下内容(注意前面的foo前面的before invoking method):

1
2
3
before invoking method
foo
bar

这里的pojo是一个代理,当我们调用pojo.foo()时,其执行情况如下图所示:

当调用pojo.foo()时,其实是调用代理的foo(),这个时候会打印出before invoking method,之后调用会来到target object,调用targetfoo(),这时会打印出foo,在targetfoo()中会调用this.bar(),而这个this不是代理,而是target,这就是为什么没在打印出bar之前打印before invoking method的原因。

怎么解决?

Spring proxy self-invocation ? my2cents列出了很多种方法来解决这个问题,下面我只介绍我个人认为最简单的方法,我们将TestServiceImpl修改为如下所示(注意看注释)。

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
@Service
public class TestServiceImpl implements TestService {

    private final TestService self;

    // 注入自身,一定要加上@Lazy
    public TestServiceImpl(@Lazy TestService self) {
        this.self = self;
    }

    @Override
    public void saveAB() {
        // 将this替换为self
        self.saveA();
        self.saveB();
    }

    @Transactional
    @Override
    public void saveA() {
        System.out.println("saveA");
    }

    @Transactional
    @Override
    public void saveB() {
        System.out.println("saveB");
    }
}

这个时候再执行TestServiceTest.test(),会发现testService.saveAB()中的saveA()saveB()都会在事务中执行。

参考资料

  • java - Spring @Transactional Annotation : Self Invocation - Stack Overflow
  • https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-proxying
  • java - Self injection with Spring - Stack Overflow