Custom Spring AOP Around + @Transactional

2020-03-01 19:37发布

问题:

I have a custom Around implemented to match on a custom Annotation. I want the custom around to execute WITHIN the outer @Transactional. Unfortunately, this doesn't appear to work. (The AOP is working. I see stacktraces that show it).

The stack traces show my AOP executing before (a logger), the MyBatis Session starting a transaction, MyBatis closing the Transactions, Spring closing the transaction and then my AOP completing.

I thought having my AOP implement Ordered would help. I set the value returned to 1. I used . This didn't work. I think it's because I misread how Spring orders.

Advice ordering

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.

When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.

So I took out the order attribute. This should make @Transactional return Integer.MIN_VALUE. So it should, if I understood the quote above, run last. When I redeployed, it still executed backward. My AOP, Spring TX, MyBatis, Close MyBatis, Close SPring Tx, Close My AOP.

What am I doing wrong?

回答1:

If the order attribute is not configured for @Transactional annotation, then the Advisor which is responsible for transaction attribute - AbstractPointcutAdvisor (in fact, one of the subclasses of it) will return Ordered.LOWEST_PRECEDENCE, which is defined as Integer.MAX_VALUE.

The Advisor which is responsible for custom AOP advice, a subclass of the same AbstractPointcutAdvisor, will look whether the actual Advice implements Ordered interface, and if it does, the provided value will be used during the sorting. If custom AOP advice does not implement Ordered interface, the Advisor returns the same default Ordered.LOWEST_PRECEDENCE and the result of the sorting becomes slightly unpredictable.

So, configuring the order attribute for @Transactional annotation e.g. like this

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-3.1.xsd>
           .......

           <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="[Order for @Transactional]">
<beans/>    

and your custom AOP advice implementation looks like this

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import org.springframework.core.Ordered;

@Aspect
public class CustomAspect implements Ordered {

    @Around(value = "@annotation(CustomAnnotation)")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    ... 
    }
    ....

    @Override
    public int getOrder() { 
        return [Order for @CustomAnnotation];
    }

    ....

}

then you probably have all the freedom (yet, statically) with the ordering throughout your application.

Under the hood, it's AspectJAwareAdvisorAutoProxyCreator who upon a Proxy initialization sorts the Advisors using the comparator org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator which delegates the sorting to OrderComparator. Later on, upon the actual execution, a concrete implementation of AopProxy holds for a specific method an array of the advices, whom it calls interceptors, and this might be used for dynamic reordering, I guess, but none of these things seems to me easily accessible and/or configurable.

My environment is Spring Beans, TX, AOP - all version 4.0.3. I also have two custom Transaction Managers, one is Hibernate-bound and one is JDBC DataSource-bound, but I don't think it matters here



回答2:

After a little experimenting it turns out that simply removing the order attribute does not make this work. I find this odd as the @Transactional default order is Integer.MIN_VALUE. Apparently if you want to enable ordering you have to explicitly set the order to the smallest of all the AOP orders.



回答3:

From page https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html , it says that transaction's default order is Ordered.LOWEST_PRECEDENCE, which value equal to Integer.MAX_VALUE