Calling another method in the same class doesn't trigger Spring AOP issue

源:https://segmentfault.com/a/1190000008379179
评:


起因

考虑如下一个例子:

@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyMonitor {
}

@Component
@Aspect
public class MyAopAdviseDefine {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.xys.demo4.MyMonitor)")
    public void pointcut() {
    }

    // 定义 advise
    @Before("pointcut()")
    public void logMethodInvokeParam(JoinPoint joinPoint) {
        logger.info("---Before method {} invoke, param: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs());
    }
}

@Service
public class SomeService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    public void hello(String someParam) {
        logger.info("---SomeService: hello invoked, param: {}---", someParam);
        test();
    }

    @MyMonitor
    public void test() {
        logger.info("---SomeService: test invoked---");
    }
}

@EnableAspectJAutoProxy(proxyTargetClass = true)
@SpringBootAppliMyion
public class MyAopDemo {
    @Autowired
    SomeService someService;

    public static void main(String[] args) {
        SpringAppliMyion.run(MyAopDemo.class, args);
    }

    @PostConstruct
    public void aopTest() {
        someService.hello("abc");
    }
}

in this example In , we define an annotation MyMonitor, which is a method annotation. Our expectation is that when the method with this annotation is called, the specified aspect logic needs to be executed, that is, the MyAopAdviseDefine.logMethodInvokeParam method is executed.

In the SomeService class, the method test() is annotated by MyMonitor, so when the test() method is called, the logMethodInvokeParam method should be called. However, we need to note that in the MyAopDemo test example, we did not directly call SomeService The .test() method, but the SomeService.hello() method is called. In the hello method, the SomeService.test() method inside the same class is called. It stands to reason that when the test() method is called, it will trigger AOP logic, but in this example, we did not see the MyAopAdviseDefine.logMethodInvokeParam method call as expected, why is this?

This is due to the limitations of Spring AOP (including dynamic proxy and CGLIB's AOP). Spring AOP is not Extends a class (target object), but uses a proxy object to wrap the target object and intercepts the method call of the target object. The impact of such an implementation is: when calling a method implemented inside its own class in the target object , these calls are not forwarded to the proxy object, and the proxy object is not even aware of the existence of the call.

That is, considering the above code, we call someService.hello("abc") in MyAopDemo.aopTest(), where the someService bean is actually a proxy object automatically instantiated by Spring AOP, when hello() is called method, first enter the method of the same name of the proxy object, and then execute the AOP logic in the proxy object (because the hello method does not inject AOP cross-cutting logic, so no additional things will happen when calling it), when the proxy object After the cross-cutting logic is executed, the call request is forwarded to the hello() method of the target object. Therefore, when the code is executed inside the hello() method, the this at this time is actually not the proxy object, but the target object. Therefore, calling SomeService.test() again will naturally have no AOP effect.

In short, the someService bean seen in MyAopDemo and this in the internal context of the SomeService.hello() method actually represent not the same object ( You can verify by printing the hashCode of the two respectively), the former is the proxy object generated by Spring AOP, and the latter is the real target object (SomeService instance).
Solution

After understanding the above analysis, it is very simple to solve this problem Since the reason why the test() method call does not trigger the AOP logic is because we call it as the target object, the key to the solution is to call test() as the proxy object. method.
So for the above example, we can make the following modifications:

@Service
public class SomeService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private SomeService self;

    public void hello(String someParam) {
        logger.info("---SomeService: hello invoked, param: {}---", someParam );
        self.test();
    }

    @CatMonitor
    public void test() {
        logger.info("---SomeService: test invoked---");
    }
}

In the code shown above, we use a very subtle The method is to inject SomeService bean into the self field (it is emphasized again that SomeService bean is actually a proxy object, which is not the same object as the object pointed to by this reference), so in the hello method call, Use the self.test() method to call the test() method, which will trigger the AOP logic.
The problem that @Transactional does not take effect due to Spring AOP

This problem also affects the use of the @Transactional annotation, because the @Transactional annotation is also essentially implemented by AOP.

For example I saw a similar question on stackoverflow: Spring @Transaction method call by the method within the same class, does not work?
This is also recorded here for reference.

The problem that guy encountered is as follows:

public class UserService {

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
            // call DAO layer and adds to database .
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                    .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    }
}

He uses @Transactional on the addUser method to use the transaction function, and then in the external service, he adds users in batches by calling the addUsers method. After the above analysis, now We can know that adding annotations here will not start the transaction function, because the AOP logic does not take effect at all.

There are two ways to solve this problem, one is to use the transaction implementation of AspectJ mode:

<tx:annotation-driven mode ="aspectj"/>The

other is the same solution we just did in the example above:

public class UserService {
    private UserService self;

    public void setSelf(UserService self) {
        this.self = self;
    }

    @Transactional
    public boolean addUser (String userName, String password) {
        try {
        // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            self.addUser(user.getUserName, user.getPassword);
        }
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326471308&siteId=291194637