How is the transaction method of Spring transaction source code analysis intercepted by AOP proxy?

Posted by gotry on Thu, 03 Feb 2022 03:24:44 +0100

1. spring declarative transaction overview

Transaction management is very important for enterprise applications. When an exception occurs, it can ensure the consistency of data. spring supports programmatic transaction management and declarative transaction management.

First, let's take a look at the thing abstraction of the spring framework. The transaction policy of spring is defined by the TransactionManager interface. The PlatformTransactionManager interface and ReactiveTransactionManager interface inherit the TransactionManager interface. Most of our programs use the PlatformTransactionManager interface.

After Spring 5.0, the reactive web framework weblux was introduced. webmvc is at the same level as weblux. Weblux is a completely responsive and non blocking web framework. Therefore, after spring 5.2, Spring also provides a transaction management abstraction for the reactive web framework, that is, the ReactiveTransactionManager interface. Next, we mainly talk about the transaction management abstraction of platform transaction manager.

The following is the source code of the PlatformTransactionManager interface:

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    void commit(TransactionStatus var1) throws TransactionException;
    void rollback(TransactionStatus var1) throws TransactionException;
}
  • getTransaction method obtains a TransactionStatus object by passing in a parameter of TransactionDefinition type. If a matching transaction already exists in the current call stack, TransactionStatus represents the existing transaction, otherwise TransactionStatus represents a new transaction.
  • The TransactionStatus interface provides some methods for transaction codes to control transaction execution and query transaction status.
  • TransactionDefinition is an interface in which there are some default methods. The return value of these default methods is to declare the necessary attributes of a transaction, such as getPropagationBehavior(), getIsolationLevel(), getTimeout(), isReadOnly(). getPropagationBehavior method refers to the propagation behavior of obtaining transactions, getIsolationLevel method refers to the isolation level of obtaining transactions, getTimeout method refers to the timeout time of obtaining transactions, and isReadOnly method refers to whether it is a read-only transaction, that is, a transaction with only read operations but no write operations. Because TransactionDefinition is an interface, it needs to be implemented before it can be used. The implementation class can override the default method of the interface or not. If not, the default value will be used.
  • The commit method is used to commit the transaction and the rollback method is used to rollback the transaction.

Declarative transactions generally use @ Transactional annotation, and it is enough to use @ EnableTransactionManagement to start transaction management, but next we will talk about the working principle behind it.

Declarative transaction is based on AOP. Firstly, our application will provide metadata through XML or annotation. AOP and transaction metadata combine to produce an agent. When executing the target party

The interceptor TransactionInterceptor will intercept the target method and then call the agent before and after the target method. Its essence is to create or join a transaction before the target method starts, and after the target method is executed

Method and then commit or roll back the transaction according to the execution.

The interceptor transaction interceptor detects which transaction management style it is by checking the return type of the method. If you return a method of a responsive type (such as Publisher), it meets the requirements of responsive transaction management

If other return types include void, use platform transaction manager.

@Transactional annotation is an annotation based declarative transaction. Of course, it can also be configured based on XMl, but because it is used in Spring Boot applications, annotation based declarative transactions are always used.

@Transactional can act on interface definitions, interface methods, class definitions and public methods of classes. If it acts on private methods or methods visible to the package, it will not cause errors, but it will not cause errors

Some behaviors of living affairs. In addition, it is better to use @ Transactional on a class than on an interface. Let me explain why. There are two patterns in the Spring AOP framework: proxy based and

AspectJ based and agent-based are divided into two types, one is interface based and the other is class based. If it is class based agent or AspectJ based, the action of @ Transactional on the interface will not play a role

Any role.

As mentioned above, transactions are based on AOP, so how to make transactions support multiple modes@ The EnableTransactionManagement annotation provides relevant support. The source code of the @ EnableTransactionManagement annotation is as follows:

public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default 2147483647;
}

You can see that the default mode of transaction management is agent-based, that is, mode = advicemode Proxy, and the default proxy method is interface based, because proxyTargetClass=false.

Let's take a look at the attributes of the @ Transactional annotation:

attributetypedescribe
valueStringSpecify transaction manager
propagationenum: PropagationTransaction propagation behavior
isolationenum: IsolationTransaction isolation level
timeoutInt (in seconds)Transaction timeout
readOnlybooleanRead only
rollbackForClass[]Array of exception classes causing rollback
rollbackForClassNameString[]Array of exception class names causing rollback
noRollbackForClass[]Array of exception classes that will not cause rollback
noRollbackForClassNameString[]Array of exception class names that will not cause rollback

Whether you choose declarative or programmatic transaction management in Spring, it is absolutely necessary to define the correct implementation of TransactionManager. The implementation of TransactionManager is usually related to their working environment: JDBC, JTA, Hibernate, etc.

If it is pure JDBC, use DataSourceTransactionManager, as shown in the following code.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

If it is Hibernate, use Hibernate transaction manager. (Hibernate has one more layer of sessionFactory than pure JDBC)

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

2. How is the transaction method of the local transaction source code intercepted by the AOP agent?

1. Phenomenon (single data source, multi DB)

As shown in the figure below, due to test_1 and test_2 these two DB are under the same data source, so they are local transactions, and spring transactions can be supported directly.

@Service
@Transactional
public class MultiDBServiceImpl implements IMultiDBService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void save() {
        String sql1 = "insert into test_2.t_class(Fname,Fnum) values(\"303 class\",30);";
        String sql2 = "insert into test_1.t_student(Fname,Fage,Fclass) values(\"Cao Cao\",30,3);";
        jdbcTemplate.execute(sql1);
        jdbcTemplate.execute(sql2);

        // RollBACK 
        throw new RuntimeException();
    }
}
CREATE TABLE t_student  (
  Fid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  Fname varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  Fage int(255) NULL DEFAULT NULL,
  Fclass int(255) NULL DEFAULT NULL,
  PRIMARY KEY (Fid) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE t_class  (
  Fid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  Fname varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  Fnum int(11) NULL DEFAULT NULL,
  PRIMARY KEY (Fid) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

If the @ Transactional annotation is not marked, enter the target method directly; otherwise, enter the proxy class generated by cglib, as shown in the following figure.

The generated proxy is cg.lib bobo. springbootdemo. service. impl. MultiDBServiceImpl E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB2afb0e3b type,

It also implements three interfaces (among which the Advised interface is very important), which are inherited from the target class, com bobo. springbootdemo. service. impl. MultiDBServiceImpl.

2. How to generate AOP proxy class

AOP proxy objects are generated in the Bean initialization method. There are some BeanPostProcessor objects. One of them is AnnotationAwareAspectJAutoProxyCreator, which is specially used to create proxy objects. AnnotationAwareAspectJAutoProxyCreator is a subclass of AbstractAutoProxyCreator and AbstractAutoProxyCreator is a subclass of BeanPostProcessor, AbstractAutoProxyCreator implements the postProcessAfterInitialization method, as shown below.

@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

The core code of wrapIfNecessary method is shown in the figure below.

This is described in two steps:

  • How do I get the Interceptors of this bean?
  • How do I create proxy objects?

1. How do I get the Interceptors of this bean?

Click the getAdvicesAndAdvisorsForBean method to find the following method.

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		// Find candidate Advisors
		List<Advisor> candidateAdvisors = findCandidateAdvisors(); 
		// Find the qualified Advisors from the candidate Advisors and judge whether they meet the conditions. As described earlier, there are transaction annotations
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); 
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

Here's how to find candidate Advisors.

After tracing all the way, we finally came to the findAdvisorBeans method of BeanFactoryAdvisorRetrievalHelper class.

The search method for candidate Advisors is actually equivalent to finding all Advisor type beans from the bean factory.

As shown in the figure above, you will find a beanName of org. From BeanFactory springframework. transaction. config. Internaltransactionadvisor, bean BeanFactoryTransactionAttributeSourceAdvisor of Advisor type, which injects advice attribute of advice type through dependency, and TransactionInterceptor is a subclass of advice.

From the global search, we can know that the configurations of TransactionInterceptor and bean factorytransactionattributesourceadvisor are in the ProxyTransactionManagementConfiguration class, as shown in the following figure.

BeanFactoryTransactionAttributeSourceAdvisor indirectly inherits from PointcutAdvisor and implements the getPointcut method, as shown in the following figure.

TransactionAttributeSource is just an interface. What is its implementation and how to find the entry point?

The implementation of TransactionAttributeSource has been given in the previous figure. It is AnnotationTransactionAttributeSource. Let's see how this class finds the transaction method.

There are two important methods in this class. The source code is as follows.

@Override
	@Nullable
	protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
		return determineTransactionAttribute(clazz);
	}
	@Override
	@Nullable
	protected TransactionAttribute findTransactionAttribute(Method method) {
		return determineTransactionAttribute(method);
	}

You can see that the parameter can be of Class type or Method type, which indicates that the @ Transactional annotation can act on both classes and methods.

Click the findTransactionAttribute method, as shown below.

@Nullable
	protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
		for (TransactionAnnotationParser parser : this.annotationParsers) {
			TransactionAttribute attr = parser.parseTransactionAnnotation(element);
			if (attr != null) {
				return attr;
			}
		}
		return null;
	}

The implementation of TransactionAnnotationParser includes Ejb3TransactionAnnotationParser, jtatatransactionannotationparser and SpringTransactionAnnotationParser. Since this is a Spring transaction, click on SpringTransactionAnnotationParser and the source code is as follows.

@Override
	@Nullable
	public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				element, Transactional.class, false, false);
		if (attributes != null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null;
		}
	}

You can see that it actually depends on whether there is a Transactional annotation. If so, enter the parseTransactionAnnotation method, which is used to resolve various attributes of the Transactional annotation.

Let's verify it through debug.

Through debug, you can see that after typing the Transactional annotation on the MultiDBServiceImpl, you really go to the findtransactionattribute (class <? > clazz) method, as shown in the following figure.

2. How do I create proxy objects?

AbstractAutoProxyCreator. The createproxy method is shown in the following figure.

proxyFactory. The getproxy method is as follows.

public Object getProxy(@Nullable ClassLoader classLoader) {
	return createAopProxy().getProxy(classLoader);
}
// createAopProxy method
protected final synchronized AopProxy createAopProxy() {
    // At present, there is only one implementation of AopProxyFactory interface: DefaultAopProxyFactory
	return getAopProxyFactory().createAopProxy(this);
}
// createAopProxy method of DefaultAopProxyFactory
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
            // The config here is actually a ProxyFactory object, which is a subclass of ProxyConfig
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

Objenesis CglibAopProxy is a subclass of CglibAopProxy, CglibAopProxy The getproxy method is shown in the following figure.

The main code of getProxy method is as follows.

Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
		
			// Get callbacks, including the internal class dynamicadisaidedinterceptor of CglibAopProxy
			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Create Class objects and instances of proxy classes
			return createProxyClassAndInstance(enhancer, callbacks);

3. The execution of the target method was blocked by CglibAopProxy

After clicking in, it is found that it is intercepted by CglibAopProxy, as shown in the figure below.

4. Summary

When @ Autowired is used to inject IMultiDBService Bean and call its save, the following process will be followed:

  1. Bean instantiation
  2. Bean initialization
  3. Execute the postProcessAfterInitialization method of BeanPostProcessor (AbstractAutoProxyCreator)
  4. Get the Advisors of the bean (get the candidate Advisors (pre configured))
  5. Get Advisors that meet the conditions
  6. Using Enhancer and callbacks (dynamic advised interceptor) to generate dynamic proxy objects
  7. Method to execute proxy object (IMultiDBService.save)
  8. Intercepted by dynamic advised interceptor (Callback)
  9. Get Advisors chain
  10. Execute the advisor chain (TransactionInterceptor)

The following is the logic of TransactionInterceptor, which will not be introduced in this tutorial.

Topics: Spring Transaction