Spring transaction Chapter 3 - spring transaction source code analysis how does spring implement the enhancement of transaction methods?

catalogue

preface

I have to mention @ EnableTransactionManagement

TransactionManagementConfigurationSelector

AutoProxyRegistrar        

ProxyTransactionManagementConfiguration        

TransactionAttributeSource

BeanFactoryTransactionAttributeSourceAdvisor

TransactionInterceptor

summary

         Main contents of this paper

         Transaction enhanced process  

         Is the database connection obtained by transaction the same as that obtained by JDBC template?

        Remaining problems

preface

        The previous article has introduced how to connect to the database, how to operate the database and the basic functions of the transaction manager. So how does Spring integrate the transaction manager to manage transactions? This is what this article wants to explore.

I have to mention @ EnableTransactionManagement

        In fact, transactions are consistent with the principle of AOP. They are an enhancement to the original object. It can be understood that when the original object executes a method, the transaction will monitor it, or add a layer of try catch to the transaction method (i.e. connection point). If an exception occurs, it will be rolled back in the exception handling part.

        To use a transaction, you must first add it in the configuration class  @ The EnableTransactionManagement annotation indicates that the project has started the transaction, and then the @ Transactional annotation is added to the method to enhance the method. So first, let's take a look at what @ enable transaction management does.         

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

	// Specify a proxy class instead of using an interface
	boolean proxyTargetClass() default false;

	// By default, transactions are implemented through dynamic agents
	AdviceMode mode() default AdviceMode.PROXY;

	// The default sort value is the smallest, but the priority is the highest
	int order() default Ordered.LOWEST_PRECEDENCE;

}

        We can see that there are several attributes here. The first one is very similar to the AOP mentioned earlier, that is, the agent specifies the implementation class rather than the interface. The second one indicates that the transaction is implemented through the agent, that is, the enhancement. The third one is used for sorting, indicating the highest priority. In addition, we also saw the @ Import annotation, which can be said to be the same as AOP. Let's guess   The transaction management configuration selector is also the main function to implement Spring transactions. It is introduced through this annotation and saved by the Spring container. The introduction of @ Import bean s has been described in detail in the previous article. If you are not familiar with it, you can jump there first and then come back to continue reading, @Usage and principle analysis of Import annotation

TransactionManagementConfigurationSelector

        First, let's take a look at the methods of this class

    public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

	/**
	 * Returns {@link ProxyTransactionManagementConfiguration} or
	 * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY}
	 * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()},
	 * respectively.
	 */
	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}

	private String determineTransactionAspectClass() {
		return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
				TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
				TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
	}

}

        We saw the familiar selectImports method. I believe the students who read the @ Import article already know when this method will be called. That is, when parsing the configuration class, this method will be called to obtain the bean definition, but it seems a little different. Let's take a look at the place where the parsing configuration class calls it.

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);

        Through comparison, it can be found that although the method names are different, the parameters are different. Where are the parameters of the current method passed from? We guess that it is likely that its parent class is called when parsing the configuration class, and then its parent class calls it again. This is also true for many of the source code analyzed earlier. If you read the previous articles, you will already have this feeling. So let's take a look at its parent class   AdviceModeImportSelector.

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {

	public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";

	protected String getAdviceModeAttributeName() {
		return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
	}

	
	@Override
	public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //Get the type of the instance imported into the current import, and the transaction is EnableTransactionManagement
		Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
		Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
        //Resolve the properties of EnableTransactionManagement, proxyTargetClass, mode and order. Because we generally do not configure them, they are all default values
		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
		if (attributes == null) {
			throw new IllegalArgumentException(String.format(
					"@%s is not present on importing class '%s' as expected",
					annType.getSimpleName(), importingClassMetadata.getClassName()));
		}
        //From the above, we can see that getAdviceModeAttributeName returns mode, so this is to obtain the attribute value of mode configuration. Because mode is not configured, the default attribute value is AdviceMode.PROXY, so we will know the result of adviceMode
		AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
        //Call the method of the subclass, and the parameter is AdviceMode.PROXY
		String[] imports = selectImports(adviceMode);
		if (imports == null) {
			throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
		}
		return imports;
	}

	
	@Nullable
	protected abstract String[] selectImports(AdviceMode adviceMode);

}

        With this dependency, we find that, as we just analyzed, it is called when parsing the configuration class   AdviceModeImportSelector, and then call the selectImports method of TransactionManagementConfigurationSelector through AdviceModeImportSelector. The parameter is AdviceMode.PROXY. After the analysis of the parent class, we will return to the selectImports method of the child class

    protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}

        Now the result is obvious. It will be introduced   Autoproxyregister and   Two types of bean instances, ProxyTransactionManagementConfiguration, are managed by the Spring container. So far, the task of @ EnableTransactionManagement has been completed. It helps the Spring container introduce two beans. As for the magic power of these two objects, let's analyze them one by one.

AutoProxyRegistrar        

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		for (String annType : annTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				if (mode == AdviceMode.PROXY) {
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		if (!candidateFound && logger.isInfoEnabled()) {
			String name = getClass().getSimpleName();
			logger.info(String.format("%s was imported but no annotations were found " +
					"having both 'mode' and 'proxyTargetClass' attributes of type " +
					"AdviceMode and boolean respectively. This means that auto proxy " +
					"creator registration and configuration may not have occurred as " +
					"intended, and components may not be proxied as expected. Check to " +
					"ensure that %s has been @Import'ed on the same class where these " +
					"annotations are declared; otherwise remove the import of %s " +
					"altogether.", name, name, name));
		}
	}

}

        If you read the previous article, you must feel familiar with it again. It implements importbeandefinitionregister and rewrites it   registerBeanDefinitions method to register some beans you want to register. The above code is actually very clear. It will call aopconfigutils. Registerautoproxycreator ifnecessary (Registry); To register the instance bean, which is the same as the method of registering the bean post processor in AOP.

    public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
		return registerAutoProxyCreatorIfNecessary(registry, null);
	}

    public static BeanDefinition registerAutoProxyCreatorIfNecessary(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
	}

    private static BeanDefinition registerOrEscalateApcAsRequired(
			Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        // If AOP and transaction are started at the same time, because they have the same name, only one will be registered here, and the one with higher priority will be selected
		if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
			BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
			if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
				int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
				int requiredPriority = findPriorityForClass(cls);
				if (currentPriority < requiredPriority) {
					apcDefinition.setBeanClassName(cls.getName());
				}
			}
			return null;
		}
        
        //Register bean definitions
		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
		beanDefinition.setSource(source);
		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
		return beanDefinition;
	}
    

        It can be seen from the above that we need to register a name as   org.springframework.aop.config.internalAutoProxyCreator type is   You may have found that the name of the bean of infrastructure advisor autoproxycreator is the same as that of the bean registered with Spring AOP, but the type is different. What should we do if transaction and AOP are enabled at the same time? Register one or two? In fact, the answer is in the above code. If you find that a bean instance named org.springframework.aop.config.internalAutoProxyCreator has been registered, you need to select one with higher priority according to the sorting rules. Who has higher priority? You need to look at the findPriorityForClass method

    private static int findPriorityForClass(@Nullable String className) {
        //You can see that this is to traverse the array to find the className, which means that which class is later in the array has higher priority
		for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) {
			Class<?> clazz = APC_PRIORITY_LIST.get(i);
			if (clazz.getName().equals(className)) {
				return i;
			}
		}
		throw new IllegalArgumentException(
				"Class name [" + className + "] is not a known auto-proxy creator class");
	}

    private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);

	static {
		// Set up the escalation list...
        //You can see that AOP has a higher priority than transactions
		APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
		APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
		APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
	}

        To sum up, when both transaction and AOP are configured, only the name of   org.springframework.aop.config.internalAutoProxyCreator the postprocessor of a bean of type AnnotationAwareAspectJAutoProxyCreator (AOP). This is just an extension. We will talk about what to do if we configure it at the same time. For the time being, it is considered that we only configure transactions, so a transaction is introduced in this way   A post processor of type infrastructureasuggestorautoproxycreator. So far, the task of autoproxyregister has been completed.

ProxyTransactionManagementConfiguration        

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    
    //Since the tangent point is enhanced, Advisor is naturally needed, which is introduced here
	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
        // Instantiate the advisor object
		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        //This configuration class registers the second instance bean
		advisor.setTransactionAttributeSource(transactionAttributeSource);
        //This configuration class registers the third instance bean
		advisor.setAdvice(transactionInterceptor);
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

    //This is to manage transaction properties
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}

    // Interceptor required by Advisor
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource);
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}

}

        We can see that this is a configuration class, which introduces three instance beans, including an Advisor instance. We know that it is very important to enhance the pointCut method, and the Advisor should include pointCut and advice, and then we can find the third one   transactionInterceptor is the Advisor of this Advisor, but pointCut doesn't know how to get it. The second bean instantiates an AnnotationTransactionAttributeSource object, which literally means the transaction attribute source, and both interceptor and Advisor use it. It is speculated that it may be related to the attributes of transaction configuration, It may be necessary to obtain some configurations or properties of the transaction during the execution of the transaction. The role of ProxyTransactionManagementConfiguration is now over. It introduces three beans. Next, we will analyze these three objects in turn.

TransactionAttributeSource

public interface TransactionAttributeSource {

	/**
	 * Determine whether the given class is a candidate for transaction attributes
	 * in the metadata format of this {@code TransactionAttributeSource}.
	 * <p>If this method returns {@code false}, the methods on the given class
	 * will not get traversed for {@link #getTransactionAttribute} introspection.
	 * Returning {@code false} is therefore an optimization for non-affected
	 * classes, whereas {@code true} simply means that the class needs to get
	 * fully introspected for each method on the given class individually.
	 * @param targetClass the class to introspect
	 * @return {@code false} if the class is known to have no transaction
	 * attributes at class or method level; {@code true} otherwise. The default
	 * implementation returns {@code true}, leading to regular introspection.
	 * @since 5.2
	 */
	default boolean isCandidateClass(Class<?> targetClass) {
		return true;
	}

	/**
	 * Return the transaction attribute for the given method,
	 * or {@code null} if the method is non-transactional.
	 * @param method the method to introspect
	 * @param targetClass the target class (may be {@code null},
	 * in which case the declaring class of the method must be used)
	 * @return the matching transaction attribute, or {@code null} if none found
	 */
	@Nullable
	TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass);

}

        You can see that there are only two interface methods for this interface. You can see from the comments that the first method is to judge whether the current class needs transaction enhancement, which is a bit similar to the classFilter when we talked about AOP earlier, that is, to judge whether the class needs enhancement. The second method is to obtain the transaction attributes on the method, that is, some transaction information configured through @ Transactional. If there is no @ Transactional annotation on the method, null will be returned. These two methods perfectly benchmark the classFilter and methodMatcher of AOP. This shows that even if it is not a pointCut, it has something to do with pointCut. After analyzing the interface, you need to analyze its implementation class   AnnotationTransactionAttributeSource

    //A parameterless constructor calls a parameterless constructor
    public AnnotationTransactionAttributeSource() {
		this(true);
	}
    
    //Parametric construction method
    public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
		this.publicMethodsOnly = publicMethodsOnly;
		if (jta12Present || ejb3Present) {
			this.annotationParsers = new LinkedHashSet<>(4);
			this.annotationParsers.add(new SpringTransactionAnnotationParser());
			if (jta12Present) {
				this.annotationParsers.add(new JtaTransactionAnnotationParser());
			}
			if (ejb3Present) {
				this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
			}
		}
		else {
            //Start using the logic of the transaction through annotations, and add an annotation parser
			this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
		}
	}

        After analyzing the construction method, we can see that it adds an annotation parser spring transaction annotation parser, but what is it used for? We can see if we can find clues in other methods in this class. The first thing I think of is the two abstract methods of the parent interface    isCandidateClass and getTransactionAttribute.

    public boolean isCandidateClass(Class<?> targetClass) {
        //It can be seen that the annotationParsers array just added and the spring transactionannotationparser have been added here. Therefore, we need to look at the isCandidateClass(targetClass) method of the spring transactionannotationparser
		for (TransactionAnnotationParser parser : this.annotationParsers) {
			if (parser.isCandidateClass(targetClass)) {
				return true;
			}
		}
		return false;
	}

    public boolean isCandidateClass(Class<?> targetClass) {
        //This method determines whether there is a @ Transactional annotation on the class
		return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);
	}

        It's confirmed here   isCandidateClass is really to judge whether a class is annotated with @ Transactional annotation. If we continue to analyze getTransactionAttribute, we will find that this class does not have this method. If this class does not have a parent class, it must have it, so go to the parent class to find it

    
    public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
		if (method.getDeclaringClass() == Object.class) {
			return null;
		}

		// First, see if we have a cached value.
        // First, it will try to get from the cache. If it is obtained, it will be returned directly, which improves the efficiency
		Object cacheKey = getCacheKey(method, targetClass);
		TransactionAttribute cached = this.attributeCache.get(cacheKey);
		if (cached != null) {
			// Value will either be canonical value indicating there is no transaction attribute,
			// or an actual transaction attribute.
			if (cached == NULL_TRANSACTION_ATTRIBUTE) {
				return null;
			}
			else {
				return cached;
			}
		}
		else {
			// We need to work it out.
            // Analyze the attribute information of things
			TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
			// Put it in the cache.
			if (txAttr == null) {
                //If the attribute information is empty, it should also be added to the cache
				this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
			}
			else {
                // Get method id
				String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
				if (txAttr instanceof DefaultTransactionAttribute) {
                    // Set method id
					((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
				}
                //Add cache
				this.attributeCache.put(cacheKey, txAttr);
			}
			return txAttr;
		}
	}

    // Parsing object attribute information
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
		// Don't allow no-public methods as required.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
        //Get method information
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		// First try is the method in the target class.
        //First, try to get the transaction attribute information from the method
		TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
		if (txAttr != null) {
			return txAttr;
		}

		// Second try is the transaction attribute on the target class.
        //If there is no @ Transactional annotation on the method, try to get the transaction attribute information from the class
		txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
		if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
			return txAttr;
		}
        // If it is currently an interface method, you need to find the configured transaction attributes on the method and implementation class of the implementation class
		if (specificMethod != method) {
			// Fallback is to look at the original method.
			txAttr = findTransactionAttribute(method);
			if (txAttr != null) {
				return txAttr;
			}
			// Last fallback is the class of the original method.
			txAttr = findTransactionAttribute(method.getDeclaringClass());
			if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
				return txAttr;
			}
		}

		return null;
	}
    

        From the above code, we can see that when parsing the transaction configuration, we will first parse the method. If the method cannot be obtained, we will analyze whether there is a configured transaction on the class. Therefore, when the method and class are configured with @ Transactional at the same time, the method shall prevail. Here, let's take a look at how findTransactionAttribute gets the transaction attribute

    protected TransactionAttribute findTransactionAttribute(Method method) {
		return determineTransactionAttribute(method);
	}

    protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
        //It can be seen that it is also parsed by the transaction annotation parser introduced earlier
		for (TransactionAnnotationParser parser : this.annotationParsers) {
			TransactionAttribute attr = parser.parseTransactionAnnotation(element);
			if (attr != null) {
				return attr;
			}
		}
		return null;
	}

    //Method for parsing annotation properties of spring transactionannotationparser
    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        //Get attribute information of @ Transactional annotation
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				element, Transactional.class, false, false);
		if (attributes != null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null;
		}
	}
    
    //The following method is easy to understand, which is to obtain the transaction attribute information we configured
    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
		RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();

		Propagation propagation = attributes.getEnum("propagation");
		rbta.setPropagationBehavior(propagation.value());
		Isolation isolation = attributes.getEnum("isolation");
		rbta.setIsolationLevel(isolation.value());
		rbta.setTimeout(attributes.getNumber("timeout").intValue());
		rbta.setReadOnly(attributes.getBoolean("readOnly"));
		rbta.setQualifier(attributes.getString("value"));

		List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
		for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
			rollbackRules.add(new RollbackRuleAttribute(rbRule));
		}
		for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
			rollbackRules.add(new RollbackRuleAttribute(rbRule));
		}
		for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
			rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
		}
		for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
			rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
		}
		rbta.setRollbackRules(rollbackRules);

		return rbta;
	}

        In this way, the main functions of TransactionAttributeSource are analyzed. It mainly has two functions: one is to perform class matching, that is, to check whether there is @ Transactional annotation on the class; The second is to obtain the transaction attribute of the configured transaction, and if @ Transactional is configured on the method and class at the same time, the method shall prevail.

BeanFactoryTransactionAttributeSourceAdvisor

        The TransactionAttributeSource has been analyzed. Now we know its function, but we don't know where it is used. Now let's continue to analyze another instance of beanfactorytransactionattributesource advisor introduced by ProxyTransactionManagementConfiguration to see if we can find some useful information.    

    public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

	@Nullable
	private TransactionAttributeSource transactionAttributeSource;
    //Sure enough, pointCut is instantiated here, and the method of obtaining the transaction attribute source is rewritten to pass the transaction attribute source to pointCut for use.
	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		@Nullable
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};


	/**
	 * Set the transaction attribute source which is used to find transaction
	 * attributes. This should usually be identical to the source reference
	 * set on the transaction interceptor itself.
	 * @see TransactionInterceptor#setTransactionAttributeSource
	 */
	public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
		this.transactionAttributeSource = transactionAttributeSource;
	}

	/**
	 * Set the {@link ClassFilter} to use for this pointcut.
	 * Default is {@link ClassFilter#TRUE}.
	 */
	public void setClassFilter(ClassFilter classFilter) {
		this.pointcut.setClassFilter(classFilter);
	}

	@Override
	public Pointcut getPointcut() {
		return this.pointcut;
	}

}

        After reading the above code, we should feel suddenly enlightened, because when the configuration class introduces the advisor, we only see the advice, but we don't see another important pointCut. Then we instantiate a pointCut here immediately. In addition, we analyzed above   The function of TransactionAttributeSource is similar to that of pointCut. For example, it can match whether there is @ Transactionl on the class and obtain the transaction attributes on the method. It looks like fine drying and coarse screening in AOP. Now it is passed to pointCut for use. It is difficult not to guess that pointCut uses these functions for pointCut matching. So let's continue our analysis   TransactionAttributeSourcePointcut  

abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

	protected TransactionAttributeSourcePointcut() {
        //Set classFilter
		setClassFilter(new TransactionAttributeSourceClassFilter());
	}


    // Method matching, which has been mentioned in AOP, determines whether the method is a tangent point
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
        // Get the TransactionAttributeSource we just passed in 
		TransactionAttributeSource tas = getTransactionAttributeSource();
        // Use the TransactionAttributeSource method we just talked about to obtain the transaction attributes on the method. If it can be obtained, it indicates that there is a @ Transactional annotation
		return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
	}

	@Override
	public boolean equals(@Nullable Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof TransactionAttributeSourcePointcut)) {
			return false;
		}
		TransactionAttributeSourcePointcut otherPc = (TransactionAttributeSourcePointcut) other;
		return ObjectUtils.nullSafeEquals(getTransactionAttributeSource(), otherPc.getTransactionAttributeSource());
	}

	@Override
	public int hashCode() {
		return TransactionAttributeSourcePointcut.class.hashCode();
	}

	@Override
	public String toString() {
		return getClass().getName() + ": " + getTransactionAttributeSource();
	}


	/**
	 * Obtain the underlying TransactionAttributeSource (may be {@code null}).
	 * To be implemented by subclasses.
	 */
	@Nullable
	protected abstract TransactionAttributeSource getTransactionAttributeSource();


	/**
	 * {@link ClassFilter} that delegates to {@link TransactionAttributeSource#isCandidateClass}
	 * for filtering classes whose methods are not worth searching to begin with.
	 */
    // Classfiler instance
	private class TransactionAttributeSourceClassFilter implements ClassFilter {

		@Override
		public boolean matches(Class<?> clazz) {
			if (TransactionalProxy.class.isAssignableFrom(clazz) ||
					TransactionManager.class.isAssignableFrom(clazz) ||
					PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
				return false;
			}
            //This is the TransactionAttributeSource obtained through the method just overridden by the class 
			TransactionAttributeSource tas = getTransactionAttributeSource();
            //We have just explained this class matching method
			return (tas == null || tas.isCandidateClass(clazz));
		}
	}

}

        The so-called pointCut is just for you   Transactionattributesource is wrapped with a layer of skin. What really works is   TransactionAttributeSource. As we guessed before. So now there is only Advice, that is, the interceptor.

TransactionInterceptor

        We know that the most important thing of the interceptor is its invoke method, so we start with the invoke method to understand the function of the interceptor.

    public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
        // Invoking the invokeWithinTransaction method of TransactionAspectSupport
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
        //Get transaction property source
		TransactionAttributeSource tas = getTransactionAttributeSource();
        //Gets the configured transaction properties
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        // Here you will get the transaction manager data source transaction manager mentioned in the above article
		final TransactionManager tm = determineTransactionManager(txAttr);
       
		if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
			ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
				if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
					throw new TransactionUsageException(
							"Unsupported annotated transaction on suspending function detected: " + method +
							". Use TransactionalOperator.transactional extensions instead.");
				}
				ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
				if (adapter == null) {
					throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
							method.getReturnType());
				}
				return new ReactiveTransactionSupport(adapter);
			});
			return txSupport.invokeWithinTransaction(
					method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
		}
        // Convert transaction manager to PlatformTransactionManager type
		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        // Get the connection point information. Go to the descriptor to set the method information, which will be taken out here
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
        //CallbackPreferringPlatformTransactionManager inherits PlatformTransactionManager, so the second condition holds
		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
            // Get transaction information
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
                //Call target method
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// Rollback processing is performed when an exception is thrown
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
                //Clear the value of transactionInfo in the thread variable
				cleanupTransactionInfo(txInfo);
			}

			if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
				// Set rollback-only in case of Vavr failure matching our rollback rules...
				TransactionStatus status = txInfo.getTransactionStatus();
				if (status != null && txAttr != null) {
					retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
				}
			}

			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
            // The logic of programmatic transactions is omitted here
			. . . . 
        }
	}

        In addition to a few of the above codes, the more important ones are the rest   createTransactionIfNecessary and   completeTransactionAfterThrowing(txInfo, ex). First, take a look at the createransactionifnecessary method to obtain transaction information

    protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

		// If no name specified, apply method identification as transaction name.
		if (txAttr != null && txAttr.getName() == null) {
			txAttr = new DelegatingTransactionAttribute(txAttr) {
				@Override
				public String getName() {
					return joinpointIdentification;
				}
			};
		}

		TransactionStatus status = null;
		if (txAttr != null) {
			if (tm != null) {
                // This method is used to obtain the transaction status information by passing in the transaction attribute to the transaction manager
				status = tm.getTransaction(txAttr);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
							"] because no transaction manager has been configured");
				}
			}
		}
        // Packaging transaction information
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

    protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, String joinpointIdentification,
			@Nullable TransactionStatus status) {
        //Several key attributes are used to instantiate transaction information
		TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
		if (txAttr != null) {
			// We need a transaction for this method...
			if (logger.isTraceEnabled()) {
				logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
			}
			// The transaction manager will flag an error if an incompatible tx already exists.
			txInfo.newTransactionStatus(status);
		}
		else {
			// The TransactionInfo.hasTransaction() method will return false. We created it only
			// to preserve the integrity of the ThreadLocal stack maintained in this class.
			if (logger.isTraceEnabled()) {
				logger.trace("No need to create transaction for [" + joinpointIdentification +
						"]: This method is not transactional.");
			}
		}

		// We always bind the TransactionInfo to the thread, even if we didn't create
		// a new transaction here. This guarantees that the TransactionInfo stack
		// will be managed correctly even if no transaction was created by this aspect.
        //Bind the current transaction information to the current thread. The outer finally method is released to manage multiple transactions in one thread.
		txInfo.bindToThread();
		return txInfo;
	}

        It can be seen from the above that the transaction information is mainly obtained through the transaction manager as mentioned in the previous article. After obtaining the transaction state, the transaction information obtained from the outer layer is encapsulated together with the transaction state, transaction manager, transaction attributes and other information. Let's look at the rollback method   completeTransactionAfterThrowing

    protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			}
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}

        The logic of this is relatively simple. If the connection point method is an exception that needs to be caught by the transaction, it will call the rollback method of the transaction manager to roll back, otherwise it will be committed successfully. It is also the commit method that calls the transaction manager.

summary

         Main contents of this paper

                 This article mainly explains how Advisor comes from, because it is the most important part of transaction enhancement. Then it introduces Advice and pointCut respectively, and knows how to match the proxy object and how to enhance it. In fact, we guessed before that the so-called transaction enhancement is nothing more than adding a layer of try catch to the pointCut method, and rolling back if there is an error. At present, we also know that the rollback and commit of transactions are completed by the transaction manager. However, after reading it, you still have a feeling that you don't understand it. It may be easier to understand it by combing it briefly below.

         Transaction enhanced process  

                Pointcut matching is performed through TransactionAttributeSource, and transaction attributes are obtained through it, which is enhanced through TransactionInterceptor interceptor. Both instances are introduced step by step by @ EnableTransactionManagement. During the enhancement process, the transaction status information can be obtained through the transaction manager. The transaction state contains the database connection. If there is a save point, it will also be saved in the transaction state. Therefore, when the connection point method fails, the rollback method of the transaction manager will be called, and the parameter passed by calling the rollback method is the transaction state information. During rollback, you can obtain the database connection for rollback. If there is a savepoint, you can only rollback to the savepoint location.

         Is the database connection obtained by transaction the same as that obtained by JDBC template?

                We know that when the method is intercepted, the transaction will be created and the database connection will be obtained, but the business method has not been executed at this time, that is, the JDBC template has not been used. When the business method is executed later, is the database connection obtained through the JDBC template the same as the connection obtained by the business? The answer must be the same. Otherwise, how to rollback? Let's look back at how the transaction obtains the database connection. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); The JDBC template also obtains the database connection through dataSource and calls this method. If you are interested, you can read the previous explanation in the previous article. As we know, after obtaining the connection holder, the transaction will be bound to the local ThreadLocal variable, which can also be understood as cache, which also ensures that multiple transactions of the same thread will only obtain the same database connection. Similarly, because the JDBC template obtains the database connection through this method, and it has been put into the cache when creating the transaction, the database connection obtained by the JDBC template is naturally the same as the transaction.

        Remaining problems

                At present, we have inferred the transaction process and enhancement method according to some methods, but we know that transaction enhancement requires proxy objects. The invoke method we analyzed above is only executed when the interceptor chain of proxy objects is executed. So in the next chapter, we need to analyze how Spring uses some existing functions to match and generate proxy objects for the original objects. In this chapter, we talked about registering through autoproxyregister   Infrastructure advisor autoproxycreator is a bean postprocessor of type, but its content is not extended in this chapter. It is needed when generating proxy objects in the next chapter.

Tags: Java Database Spring

Posted on Wed, 22 Sep 2021 12:12:43 -0400 by lailaigogo