preface
Transactions we all know what they are, and Spring transactions use AOP to provide declarative and programmatic transactions on top of a database to help simplify development and decouple business logic from system logic. But what about Spring transactions? How are transactions propagated between methods? Why do transactions sometimes fail? Next WE will answer one by one ~ focus on the analysis of Spring transaction source code, let us thoroughly understand the principle of Spring transaction.
The body of the
Parsing of XML tags
<tx:annotation-driven transaction-manager="transactionManager"/>
Copy the code
Configured a transaction should be familiar with this configuration is Spring open transaction annotations (@ Transactional) support the configuration, and read my previous articles should know, the label with the prefix is called custom tag, I’m in the previous article also analyzed the parsing process of the custom tag, So here I go directly to the corresponding handler:
public class TxNamespaceHandler extends NamespaceHandlerSupport {
static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
static String getTransactionManagerName(Element element) {
return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
}
@Override
public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}
}
Copy the code
You can see the corresponding annotations parser is AnnotationDrivenBeanDefinitionParser class, there must be a parse method in the class:
public BeanDefinition parse(Element element, ParserContext parserContext) {
registerTransactionalEventListenerFactory(parserContext);
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerTransactionAspect(element, parserContext);
if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) {
registerJtaTransactionAspect(element, parserContext);
}
}
else {
// mode="proxy"
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
return null;
}
Copy the code
First of all get the mode attribute value judgment is the use of AspectJ generated proxy or JDK generating agent, here we basically see the proxy pattern, enter configureAutoProxyCreator method:
public static void configureAutoProxyCreator(Element element, ParserContext ParserContext) {/ / registered AOP's entrance AopNamespaceUtils registerAutoProxyCreatorIfNecessary (ParserContext, element); String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; if (! parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) { Object eleSource = parserContext.extractSource(element); / / Create the TransactionAttributeSource definition. / / @ Transactional annotation properties of encapsulation RootBeanDefinition sourceDef = new RootBeanDefinition( "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"); sourceDef.setSource(eleSource); sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); // AOP implementation chain RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class); interceptorDef.setSource(eleSource); interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); / / get a transaction - the manager attribute's value registerTransactionManager (element, interceptorDef); interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); // Create the TransactionAttributeSourceAdvisor definition. RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class); advisorDef.setSource(eleSource); advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); advisorDef.getPropertyValues().add("adviceBeanName", interceptorName); if (element.hasAttribute("order")) { advisorDef.getPropertyValues().add("order", element.getAttribute("order")); } parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName)); parserContext.registerComponent(compositeDef); }}Copy the code
The process here is longer, but the logic is simple. First, let’s see which AOP entry class is registered transaction:
public static void registerAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) {// Put the AOP entry class with the higher priority into the IOC container BeanDefinition BeanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary( parserContext.getRegistry(), parserContext.extractSource(sourceElement)); / / set the proxy generation, and whether the caching proxy class to the current thread useClassProxyingIfNecessary (parserContext. GetRegistry (), sourceElement); registerComponentIfNecessary(beanDefinition, parserContext); }Copy the code
Basically see registerAutoProxyCreatorIfNecessary method:
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"); // Determine which classes are passed in and which classes currently exist in the ICO have a higher priority. Will be higher if in the IOC (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; } // Encapsulate the AOP entry class as a beanDefinition object, and instantiate RootBeanDefinition beanDefinition = new RootBeanDefinition(CLS); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // annotate the beanName name of the AOP entry class aopConfigUtils. AUTO_PROXY_CREATOR_BEAN_NAME registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }Copy the code
First determines whether the container has been AOP entry class, if not directly create InfrastructureAdvisorAutoProxyCreator BeanDefinition object to register into the container, Class abstractautoXyCreator, an AOP entry class, is a subclass of abstractautoXyCreator.
Do you ever wonder, with all these subclasses, which one will you use? If there is already an entry class, findPriorityForClass will get the priority of the two classes, and the one with the higher priority will be used. What is their priority order?
private static final List<Class<? >> APC_PRIORITY_LIST = new ArrayList<>(3); static { // Set up the escalation list... APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class); } private static int findPriorityForClass(@nullable String className) {private static int findPriorityForClass(@nullable String className) { There will only be one transactional AOP entry class 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"); }Copy the code
As you can see, InfrastructureAdvisorAutoProxyCreator is the lowest priority, basically won’t work; AspectJAwareAdvisorAutoProxyCreator is when we configure the aop: config label when registered, also is the aop XML configuration entry class; And AnnotationAwareAspectJAutoProxyCreator is when we configure the aop: aspectj – autoproxy or using the @ EnableAspectJAutoProxy annotations when registered, Thus, most cases are using AnnotationAwareAspectJAutoProxyCreator. After registration the AOP entry class, return to configureAutoProxyCreator method:
RootBeanDefinition sourceDef = new RootBeanDefinition(
"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName =parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
Copy the code
AnnotationTransactionAttributeSource class is encapsulated transaction annotations @ Transactional attributes, here to remember its inheritance system and familiar with the class and its parent class attributes and methods, is helpful to analyze things behind section of implementation principle:
This class is a subclass of MethodInterceptor. The AOP invocation chain is used to call the invoke method of this class. It is therefore the entry point for the execution of transaction facets. If you need an Interceptor, you should also have an Advisor, which is made up of Advice and Poincut to provide a complete aspect. That’s how XML configuration AOP annotation support works. It’s very simple, but let’s look at how zero configuration is implemented.
AOP zero configuration principle
Used SpringBoot knows, if you need to open the transaction annotations support, only need an annotation can fix: @ EnableTransactionManagement, don’t have to configure the XML file, this is how to do it? Without further ado, let’s go straight to the source code:
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
Copy the code
Under the annotation using the @ TransactionManagementConfigurationSelector Import into a class, the first function of the annotation is to Import an instance of a class to the IOC container, you may say that is not on the class add @ Component annotation line? But some kind of it is not under your scanning path, but still the annotations can be imported, so I basically see TransactionManagementConfigurationSelector class did what:
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { @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); }}Copy the code
Can see in selectImports method returns the AutoProxyRegistrar and ProxyTransactionManagementConfiguration class, return can be encapsulated as BeanDefinition object, So where is this method called? This was also analyzed in a previous article, ConfigurationClassPostProcessor will call ConfigurationClassParser class in the class of the parse method resolution @ Configuration, @ Import and @ ImportSource annotations, The specific process will not be described here. We continue to look at each AutoProxyRegistrar and ProxyTransactionManagementConfiguration class:
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> annoTypes = importingClassMetadata.getAnnotationTypes(); for (String annoType : annoTypes) { AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType); 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) {/ / registered affairs AOP InfrastructureAdvisorAutoProxyCreator entrance class, in fact this has no effect of AOP entrance class AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); if ((Boolean) proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } } } public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { /* * is obviously create instances * BeanFactoryTransactionAttributeSourceAdvisor affairs section * * * / @ Bean (name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource()); // Set the advice class advisor.setadvice (transactionInterceptor()); if (this.enableTx ! = null) { advisor.setOrder(this.enableTx.<Integer>getNumber("order")); } return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } /* * create a transaction advice * TransactionInterceptor * */ @bean@role (BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor() { TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource()); If (this.txManager! = null) { interceptor.setTransactionManager(this.txManager); } return interceptor; }}Copy the code
See this was made clear, the former is registered AOP’s entrance class (the entrance to register class is still a InfrastructureAdvisorAutoProxyCreator), the latter is to create a transaction of AOP instance of a component to the IOC, here believe that it is not just for transaction zero configuration, But the entire SpringBoot zero configuration implementation principle is well understood.
conclusion
This article analyzes the principle of transaction configuration analysis based on what we have learned before, and also brings out the principle of Zero configuration implementation of SpringBoot. To truly understand Spring, we need to connect the loading, parsing, and invocation processes in our mind and grasp the whole from micro to macro.