To understand the complete content of the column, click jump:
Spring Framework column navigation page
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 type | explain |
---|---|
PROPAGATION_REQUIRED | If there is no current transaction, create a new transaction. If there is already a transaction, join it. This is the most common choice. |
PROPAGATION_SUPPORTS | The current transaction is supported. If there is no current transaction, it will be executed in a non transactional manner. |
PROPAGATION_MANDATORY | Use the current transaction. If there is no current transaction, throw an exception. |
PROPAGATION_REQUIRES_NEW | Create a new transaction. If there is a current transaction, suspend the current transaction. |
PROPAGATION_NOT_SUPPORTED | The operation is performed in a non transactional manner. If there is a current transaction, the current transaction is suspended. |
PROPAGATION_NEVER | Execute in a non transactional manner. If a transaction currently exists, an exception will be thrown. |
PROPAGATION_NESTED | If 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; } }