preface

In the previous article, we introduced the low-level abstractions in Spring-TX. In this article, we will take a look at how spring-TX builds declarative transactions around these abstractions. In general, the implementation of Spring-TX-5.2.6. RELEASE is divided into two parts:

  1. PlatformTransactionManagerTransaction management details under abstraction
  2. Based on thespring-aopHow does the interceptor enhance ordinary methods to transaction methods

These two parts are independent and mutually successful, and each part has a lot of source code behind it. In this article, we’ll start with the AOP part of Spring-TX.

Everything from EnableTransactionManagement to start

EnableTransactionManagement comments we are familiar with, it is to enable the Spring annotations in the drive of transaction management function key.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
	// Use the Cglib subclassing policy or JDK Dynamic Proxy
	boolean proxyTargetClass(a) default false;
  // Using Spring AOP's runtime proxy mechanism is still aspect J's static compilation strategy
	AdviceMode mode(a) default AdviceMode.PROXY;
  // Priority of the transaction Advisor
	int order(a) default Ordered.LOWEST_PRECEDENCE;
}
Copy the code

EnableTransactionManagement annotations to the container is the main purpose of the import TransactionManagementConfigurationSelector, as for the annotation defined in several properties in the Spring AOP source code parsing detailed analysis, I won’t repeat it here.

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
        // The AutoProxyRegistrar is used to switch on the AOP related functionality
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null; }}private String determineTransactionAspectClass(a) {
		return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); }}Copy the code

Because we did not use AspectJ, nature is the import containers ProxyTransactionManagementConfiguration the configuration class.

@Configuration
public abstract class AbstractTransactionManagementConfiguration implements ImportAware {

	@Nullable
	protected AnnotationAttributes enableTx;

	@Nullable
	protected TransactionManager txManager;

	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
    / / get EnableTransactionManagement annotations defined attributes
		this.enableTx = AnnotationAttributes.fromMap(
				importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));
		if (this.enableTx == null) {
			throw new IllegalArgumentException("@EnableTransactionManagement is not present on importing class "+ importMetadata.getClassName()); }}/ / get the transaction manager through TransactionManagementConfigurer
	@Autowired(required = false)
	void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {
		if (CollectionUtils.isEmpty(configurers)) {
			return;
		}
		if (configurers.size() > 1) {
			throw new IllegalStateException("Only one TransactionManagementConfigurer may exist");
		}
		TransactionManagementConfigurer configurer = configurers.iterator().next();
		this.txManager = configurer.annotationDrivenTransactionManager();
	}
	// Support pub sub for transaction-related events
	@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public static TransactionalEventListenerFactory transactionalEventListenerFactory(a) {
		return newTransactionalEventListenerFactory(); }}@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
	// Inject a transaction Advisor into the container
	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {

		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(transactionAttributeSource);
		advisor.setAdvice(transactionInterceptor);
		if (this.enableTx ! =null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

  // An extractor for the Transactional attribute, used to parse the @Transactional annotation
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource(a) {
		return new AnnotationTransactionAttributeSource();
	}

  // transaction interceptor
	@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);
		}
		returninterceptor; }}Copy the code

This is the core of the configuration class to the container import a type for BeanFactoryTransactionAttributeSourceAdvisor Bean. It’s a PointcutAdvisor Pointcut is TransactionAttributeSourcePointcut, Advice is TransactionInterceptor.

TransactionAttributeSourcePointcut using analytical @ TransactionAttributeSource Transactional annotation ability to mark the selected @ Transactional annotation methods, The TransactionInterceptor enhances methods to Transactional methods based on the application’s requirements (derived from parsing the @Transactional annotation), Therefore BeanFactoryTransactionAttributeSourceAdvisor can identify those marked @ Transactional annotation methods, application on affairs related function for them.

How a transaction interceptor works

TransactionAttributeSource

The TransactionInterceptor can enhance a method, but it doesn’t know how. For example, should the method open a separate transaction or use an existing one? When is a rollback required and when is it not? There must be a “people” tell it how to strengthen, the “people” is TransactionAttributeSource.

The @Transactional annotation defines the basic information for a transaction. It expresses the Transactional shape that your application expects. TransactionAttributeSource is the main function of parsing @ Transactional annotation, extract its properties, packaged into TransactionAttribute, such TransactionInterceptor enhancement have the basis.

public interface TransactionAttributeSource {
	/** * test whether it is possible for a class to resolve the TransactionAttribute. For classes that cannot resolve the TransactionAttribute, there is no need to * call #getTransactionAttribute(...). To improve performance */
	default boolean isCandidateClass(Class
        targetClass) {
		return true;
	}

	/** * returns the transaction attribute of the method@TransactionalThe annotation specifies */
	@Nullable
	TransactionAttribute getTransactionAttribute(Method method, @NullableClass<? > targetClass);
}
Copy the code

We have seen, in front of the spring – tx AnnotationTransactionAttributeSource be used for a specific analytical work, The parent class AbstractFallbackTransactionAttributeSource defines the priority of the analytic TransactionAttribute, core method is computeTransactionAttribute (…). .

@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @NullableClass<? > targetClass) {
  The @Transactional annotation fails because it does not parse methods that are not public
  if(allowPublicMethodsOnly() && ! Modifier.isPublic(method.getModifiers())) {return null;
  }
  // Incoming methods may come from the interface, and methods defined in the target class have higher priority than those defined in the target class
  Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
  // Delegate to template methods
  TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
  if(txAttr ! =null) {
    return txAttr;
  }
  // Try to find on the target class
  txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
  if(txAttr ! =null && ClassUtils.isUserLevelMethod(method)) {
    return txAttr;
  }
  // The method passed in is not exactly the one defined in the target class
  if(specificMethod ! = method) {// Try to fetch on the method passed in, which is why the @Transactional annotation supports definitions in interfaces
    txAttr = findTransactionAttribute(method);
    if(txAttr ! =null) {
      return txAttr;
    }
    // Finally try to fetch it on the class that defines the method, usually the interface itself
    txAttr = findTransactionAttribute(method.getDeclaringClass());
    if(txAttr ! =null && ClassUtils.isUserLevelMethod(method)) {
      returntxAttr; }}return null;
}
Copy the code

AnnotationTransactionAttributeSource analytical public default only modified method, which is one reason leading to the invalid @ Transactional annotation, besides it also implements the parent class defined in the two template method:

  1. FindTransactionAttribute (Method Method), which gets the transaction attributes defined on the Method

  2. findTransactionAttribute(Class
    clazz) to get transaction attributes defined on the class

. At the same time in order to support the EJB javax.mail defined in the EJB. TransactionAttribute defined and JTA javax.mail., Transactional annotation, AnnotationTransactionAttributeSource choose to the extraction of actual job agent to TransactionAnnotationParser. Spring provides @ Transactional annotation by SpringTransactionAnnotationParser parsing.

public class SpringTransactionAnnotationParser implements TransactionAnnotationParser.Serializable {

	@Override
	public boolean isCandidateClass(Class
        targetClass) {
		return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);
	}

	@Override
	@Nullable
	public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
    // Use find semantics to extract the Transactional annotation
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				element, Transactional.class, false.false);
		if(attributes ! =null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null; }}// Wrap the Transactional annotation as TransactionAttribute
	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;
	}

	@Override
	public boolean equals(@Nullable Object other) {
		return (this == other || other instanceof SpringTransactionAnnotationParser);
	}

	@Override
	public int hashCode(a) {
		returnSpringTransactionAnnotationParser.class.hashCode(); }}Copy the code

SpringTransactionAnnotationParser source code is very simple, it USES AnnotatedElementUtils tool class defines the find semantics to get @ Transactional annotation information. RuleBasedTransactionAttribute rollbackOn (…). The implementation is interesting, but the rest is unremarkable.

	@Override
	public boolean rollbackOn(Throwable ex) {
		if (logger.isTraceEnabled()) {
			logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
		}

		RollbackRuleAttribute winner = null;
		int deepest = Integer.MAX_VALUE;

    // Select the closest RollbackRuleAttribute
		if (this.rollbackRules ! =null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				int depth = rule.getDepth(ex);
				if (depth >= 0&& depth < deepest) { deepest = depth; winner = rule; }}}if (logger.isTraceEnabled()) {
			logger.trace("Winning rollback rule is: " + winner);
		}
		
    RollbackRuleAttribute does not support this exception
		if (winner == null) {
      / / back to the parent class handling - > (ex instanceof RuntimeException | | the ex instanceof Error)
      // Roll back any runtime exception or Error
			logger.trace("No relevant rollback rule found: applying default rules");
			return super.rollbackOn(ex);
		}
		// A RollbackRuleAttribute that supports this exception was found
    // Make sure it is not NoRollbackRuleAttribute, which does not roll back when an exception of the type is encountered
		return! (winnerinstanceof NoRollbackRuleAttribute);
	}
Copy the code

RollbackRuleAttribute is used to determine whether a rollback should occur when a particular type of exception (or a subclass thereof) occurs, while NoRollbackRuleAttribute inherits from RollbackRuleAttribute, but expresses the opposite meaning. RollbackRuleAttribute holds the name of an exception and calculates the distance between the specified Throwable and the held exception on the inheritance chain using the getDepth(Throwable EX) algorithm.

	public int getDepth(Throwable ex) {
		return getDepth(ex.getClass(), 0);
	}

	private int getDepth(Class<? > exceptionClass,int depth) {
    // The name matches
		if (exceptionClass.getName().contains(this.exceptionName)) {
			return depth;
		}
		// Not in an inheritance system
    if (exceptionClass == Throwable.class) {
			return -1;
		}
    // Up the inheritance chain
		return getDepth(exceptionClass.getSuperclass(), depth + 1);
	}
Copy the code
TransactionInterceptor

The programmer can’t start until he gets the demand,TransactionInterceptorSame thing. Got itTransactionAttributeSourceThen you can enhance it with evidence. Look at the class diagram,TransactionInterceptorTo achieve theMethodInterceptorInterface, then naturally implement the methods in the interface:

  @Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Get the target ClassClass<? > targetClass = (invocation.getThis() ! =null ? 
                            AopUtils.getTargetClass(invocation.getThis()) 
                            : null);
		// #invokeWithinTransaction(...)
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}
Copy the code

As you can see, the TransactionInterceptor itself does not implement any logic; it is more like an adapter. With this layering, TransactionAspectSupport can theoretically support any type of Advice, not just MethodInterceptor. TransactionAspectSupport does take this into account on its implementation, as we’ll see in a moment.

	@Nullable
	protected Object invokeWithinTransaction(Method method, @NullableClass<? > targetClass,final InvocationCallback invocation) throws Throwable {
		// Parse the @Transactional annotation to get transaction attributes
    // If there is no transaction attribute, this method is not a transaction method
		TransactionAttributeSource tas = getTransactionAttributeSource();
		finalTransactionAttribute txAttr = (tas ! =null ? 
                                         tas.getTransactionAttribute(method, targetClass) : null);
    // Select a TransactionManager from the container to manage transactions
		final TransactionManager tm = determineTransactionManager(txAttr);

    / / 1. Omitting the ReactiveTransactionManager related content, do not use webflux use
    / / 2. Omitting the CallbackPreferringPlatformTransactionManager related content, in addition to based on callback, and almost
    / / PlatformTransactionManager is exactly the same, limited to length, cut off
    
		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    // The method identifier, later used as the transaction name, mostly used for debugging
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
		
    // Start transaction (if possible)
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

    Object retVal;
    try {
      // Turn to the next interceptor in the chain
      Since the TransactionInterceptor has the lowest priority by default, this is basically calling the target method
      retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
      // Transaction handling when an exception occurs -- > commits or rolls back the transaction according to rollbackFor, noRollbackFor
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
    }
    finally {
      // Perform cleanup
      cleanupTransactionInfo(txInfo);
    }
		// Commit the transaction when the method completes its normal execution
    commitTransactionAfterReturning(txInfo);
    return retVal;
	}
Copy the code

invokeWithinTransaction(…) The process is clear:

  1. To obtainTransactionManagerandTransactionAttributeOne brother decides whether to start a transaction and the other decides how to start a transaction
  2. Trying to start a transaction
  3. tryThe block wraps around the execution of the target method and processes the result — commit or rollback — accordingly

The first step has been analyzed, so let’s look at the second step.

	protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    // If the transaction name is not specified, specify it here
		if(txAttr ! =null && txAttr.getName() == null) {
			txAttr = new DelegatingTransactionAttribute(txAttr) { // Proxy mode, reassurances
				@Override
				public String getName(a) {
					returnjoinpointIdentification; }}; } TransactionStatus status =null;
    // The transaction can only be started if both txAttr and TM brothers exist
		if(txAttr ! =null) {
			if(tm ! =null) {
        // Start a transaction according to txAttr attributes
        / / here is PlatformTransactionManager affairs management core logic, we next to say again
				status = tm.getTransaction(txAttr);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
							"] because no transaction manager has been configured"); }}}Prepare TransactionInfo / /
    TransactionInfo is unnecessary if you don't consider supporting different types of Advice
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

	protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, String joinpointIdentification,
			@Nullable TransactionStatus status) {
		// Create a new txInfo
		TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    // TransactionStatus can only be obtained if txAttr exists, which means it is a transaction method
		if(txAttr ! =null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
			}
			// Record the transaction status
			txInfo.newTransactionStatus(status);
		}
		else {
			// Non-transactional methods
			if (logger.isTraceEnabled()) {
				logger.trace("No need to create transaction for [" + joinpointIdentification +
						"]: This method is not transactional."); }}// Bind to the current thread to support various types of Advice
    For example, we use BeforeAdvice and AfterAdvice to simulate MethodInterceptor
    // Both Advice inherit from TransactionAspectSupport, so TransactionStatus is passed through thread-private storage
    // TransactionAspectSupport therefore also provides the #currentTransactionStatus() method to get the currentTransactionStatus
		txInfo.bindToThread();
		return txInfo;
	}
Copy the code

TransactionInfo is a very simple class, so we won’t bother to analyze it. Move on to step 3, which involves two different operations — commit or rollback.

	protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    // If there is a transaction
		if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
			}
      // Commit using TransactionManagertxInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); }}protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    // If there is a transaction
		if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
      // Query rollbackon-related configurations, if indeed rollback is required
			if(txInfo.transactionAttribute ! =null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
      		// Roll back using TransactionManager
					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);
					throwex2; }}else {
        // No rollback is required, so commit the transaction instead
				// Call commit(...) There is no guarantee that a transaction will be committed, and there are other factors that can cause a transaction to roll back, such as
        // The transaction marked rollback-only, which we'll talk about next
        try {
          // Commit using TransactionManager
					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);
					throwex2; }}}}Copy the code

At this point, the TransactionInterceptor is no longer a secret to us.

Afterword.

This analysis on how the spring – tx with us through the spring – aop interceptors will be ordinary method for transaction methods, the next you should said said PlatformTransactionManager abstract under the transaction management details, we next good-bye ~ ~