Spring source code spring declarative transaction

To understand the complete content of the column, click jump:
Spring Framework column navigation page

gitee annotated source code


Flow chart corresponding to this article

Declarative transaction complete process
Transaction Advisor resolution
Transaction annotation matching

web development is inseparable from dealing with relational databases, the most common is mysql. At present, the default storage engine of MySQL is innodb, which supports transactions (solving multiple database operation ACID). How does Spring gracefully implement MySQL transactions?

To implement a transaction, we simply obtain the database connection in our method, close the automatic transaction submission, and roll back when the method is executed and committed manually or abnormally. If programmers worry about this series of code that has nothing to do with business, there will be a lot of redundant code. What should we do? AOP, right!

Spring also implements transaction AOP based on dynamic agent, and also defines seven transaction propagation behaviors, which makes transaction nesting, new creation, suspension and integration more convenient.


Transaction propagation behavior

Transaction propagation behavior typeexplain
PROPAGATION_REQUIREDIf there is no current transaction, create a new transaction. If there is already a transaction, join it. This is the most common choice.
PROPAGATION_SUPPORTSThe current transaction is supported. If there is no current transaction, it will be executed in a non transactional manner.
PROPAGATION_MANDATORYUse the current transaction. If there is no current transaction, throw an exception.
PROPAGATION_REQUIRES_NEWCreate a new transaction. If there is a current transaction, suspend the current transaction.
PROPAGATION_NOT_SUPPORTEDThe operation is performed in a non transactional manner. If there is a current transaction, the current transaction is suspended.
PROPAGATION_NEVERExecute in a non transactional manner. If a transaction currently exists, an exception will be thrown.
PROPAGATION_NESTEDIf a transaction currently exists, it is executed within a nested transaction. If there is no current transaction, execute the same as the deployment_ Required similar operations.

Transaction pending

Transaction suspension is to save some information of the current transaction temporarily and resume the current transaction after the new transaction is committed or the current sql is executed.


Advisor parsing

The Advisor in Spring is a special Aspect, which contains advice and PointCut. When using AspectJ for custom AOP processing, we need to write our own enhancement code. Do we still need to write the enhancement code corresponding to transaction annotation @ Transactional?

Naturally, it is unnecessary. After the transaction function is enabled, Bean definitions have been injected into Spring, which will be created directly when the advisor is parsed later.

@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true) // You don't have to
@ComponentScan(basePackages = {"com.pt"})
public class MainConfig {
	// Omitted here
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class) // Selector imported
public @interface EnableTransactionManagement {

	boolean proxyTargetClass() default false;

	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;

}
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {AutoProxyRegistrar.class.getName(), // Post processor of transaction (aop)
						ProxyTransactionManagementConfiguration.class.getName()}; // Configuration class
			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);
	}

}

Focus on the imported configuration class

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

	/**
	 * The transaction advisor is imported 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;
	}

	/**
	 * Transaction attribute source object: used to obtain the transaction attribute object
	 * Used to resolve @ Transactional
	 * @return
	 */
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}

	/**
	 * Method execution for intercepting transactions
	 */
	@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;
	}

}

See here, combined with the Bean's life cycle, you should know a general loading process:

  • Spring container initialization
  • Load the bean definition of our configuration class MainClass into the container
  • Refresh(). Invokebeanfactoryprocessors() parses our configuration class
  • All methods decorated with @ Bean in ProxyTransactionManagementConfiguration are resolved to beanMethods
  • When parsing the configuration class, register the beanMethods of the @ bean tag into the bean definition map through this.reader.loadBeanDefinitions(configClasses) to create beans later
  • When creating a bean, it will go through a parsing process. Object bean = resolvebeforeinstance (beanname, mbdtouse), in which the bean of BeanFactoryTransactionAttributeSourceAdvisor will be created
  • Finally, after bean initialization, wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName) calls the dynamic agent post processor for PointCut matching, finds PointCut, and finally creates a dynamic agent.

Advisor matching

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// important!
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		//It has been processed (targetSourcedBeans appear when parsing the facet) to implement the creation of dynamic proxy logic
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		//No enhancement required
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		//Whether the basic bean is a duplicate judgment that needs to be skipped (because circular dependency can change the bean. What if the bean is changed to an advisor)
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Find a matching advisor based on the current bean
		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		// The current bean matches the advisor
		if (specificInterceptors != DO_NOT_PROXY) {
			// Mark as processed
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// Create our real proxy object
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}
	@Override
	@Nullable
	protected Object[] getAdvicesAndAdvisorsForBean(
			Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
		// go in
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		// The candidate advisor transaction and aspectj advisor will be resolved
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// De matching
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		// Add an additional advisor
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
	protected List<Advisor> findAdvisorsThatCanApply(
			List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

		//Used to record the name of the proxy object currently being created
		ProxyCreationContext.setCurrentProxiedBeanName(beanName);
		try {
			 // Find the advisors associated with the current Bean from the candidate notifiers
			return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
		}
		finally {
			//Clear the proxy object name of the beanName currently being created from the thread local variable
			ProxyCreationContext.setCurrentProxiedBeanName(null);
		}
	}
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		// If there is no direct return
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}
		//Define a matching enhancer collection object
		List<Advisor> eligibleAdvisors = new ArrayList<>();
		for (Advisor candidate : candidateAdvisors) {
			//Judge whether our enhancer object implements the introduction Advisor (obviously, our transaction is not implemented, so we won't follow the following logic)
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor) {
				// already processed
				continue;
			}
			// Really judge whether our transaction enhancer is suitable for us
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
		if (advisor instanceof IntroductionAdvisor) {
			return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
		}
		//Judge whether our transaction enhancer BeanFactoryTransactionAttributeSourceAdvisor implements PointcutAdvisor
		else if (advisor instanceof PointcutAdvisor) {
			//Convert to PointcutAdvisor type
			PointcutAdvisor pca = (PointcutAdvisor) advisor;
			//Find a really useful enhancer
			return canApply(pca.getPointcut(), targetClass, hasIntroductions);
		}
		else {
			// It doesn't have a pointcut so we assume it applies.
			return true;
		}
	}
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		Assert.notNull(pc, "Pointcut must not be null");
		// Coarse screen grade filtration
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}

		// Method level filtering
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
			// No need to iterate the methods if we're matching any method anyway...
			return true;
		}

		// Determine whether the matcher is an IntroductionAwareMethodMatcher. Only AspectJExpressionPointCut can implement this interface
		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		//Create a collection of class objects to hold targetClass
		Set<Class<?>> classes = new LinkedHashSet<>();
		//Judge whether the current class is a proxy class object
		if (!Proxy.isProxyClass(targetClass)) {
			//Join the collection
			classes.add(ClassUtils.getUserClass(targetClass));
		}
		//Get the class object of the interface implemented by targetClass, and then add it to the collection
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

		// Fine screen
		for (Class<?> clazz : classes) {
			//Get all the methods through class
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
				//Match our methods through methodMatcher.matches
				if (introductionAwareMethodMatcher != null ?
						// Match aspectj by pointcut expression
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						// The matching of transactions goes here through the method matcher matching interface mode AbstractFallbackTransactionAttributeSource
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

Pay attention to the fine screen here

@Override
	@Nullable
	public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {

		// Judge whether the class of method is of Object type
		if (method.getDeclaringClass() == Object.class) {
			return null;
		}

		// Build our cache key
		// First, see if we have a cached value.
		Object cacheKey = getCacheKey(method, targetClass);

		// Get it from our cache first
		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.
			//Determine whether the object in the cache is an object with an empty transaction attribute
			if (cached == NULL_TRANSACTION_ATTRIBUTE) {
				return null;
			}
			else {
				return cached;
			}
		}
		else {

			// Follow in! We need to find our transaction annotations
			// We need to work it out.
			TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);

			// If the resolved transaction annotation attribute is null
			// Put it in the cache.
			if (txAttr == null) {
				//Put the empty transaction annotation attribute into the cache
				this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
			}
			else {

				// We execute the descriptor of the method, full class name + method name
				String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);

				//Set the method description to the transaction attribute
				if (txAttr instanceof DefaultTransactionAttribute) {
					DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr;
					dta.setDescriptor(methodIdentification);
					dta.resolveAttributeStrings(this.embeddedValueResolver);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
				}
				//Add to cache
				this.attributeCache.put(cacheKey, txAttr);
			}
			return txAttr;
		}
	}

Tags: Database MySQL Spring

Posted on Thu, 14 Oct 2021 20:36:30 -0400 by vijayanand