一、前言
利用SpringBoot实现事务非常简单,主要有以下两步骤:
1.在启动类上加注解@EnableTransactionManagement
2.在需要事务管理的方法上添加注解@Transactional
简单的代码后面却体现了SpringAOP的思想和精髓,接下来我们先思考一下可能的实现方式。
二、思考
要对一个方法进行事务管理,肯定要对该方法进行增强,那就有以下几个问题:
1.如何增强
我们知道动态代理和切面都是是可以的。那么假如用代理,代理的逻辑在哪里,即开启事务,提交事务的逻辑在哪里。
2.何时对该方法进行增强
在博文Spring---ApplicationContext的Refresh分析+hook函数分析我们了解到Spring在Bean创建的过程中提供了很多钩子函数。那是否可以在Bean初始化完成之后利用钩子函数对Bean进行代理呢。
三、分析
3.1 @EnableTransactionManagement解析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
在前面几篇Spring系列文章中,我们了解到Spring启动时主要借助ConfigurationClassPostProcessor扫描程序中所有定义的Bean添加到BeanDefinitionMap之中。我们再来看下该类是怎么做的。
3.1.1 将所有标注有@Component、@Configuration等注解的类添加到BeanDefinitionMap并解析为ConfigurationClass
对于ConfigurationClass类我们有必要了解一下:
final class ConfigurationClass {
//该类的注解信息
private final AnnotationMetadata metadata;
//该类.class文件路径
private final Resource resource;
//该类代表的bean名称
@Nullable
private String beanName;
//该类是被哪个类通过@Import注解导入进来的
private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
//该Configuration类中具有@Bean注解的方法(这些方法在后续会被解析为Bean)
private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
//该类要导入的资源(一般是.xml文件描述的bean)
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<>();
//该类要直接注册的BeanDefinition,后面就会看到@EnableTransationManagement怎么使用它的
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
new LinkedHashMap<>();
final Set<String> skippedBeanMethods = new HashSet<>();
ok,步入正题。
ConfigurationClassPostProcessor在解析我们的启动类时,最终会调用org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法进行解析,此处不了解的可以看博文Spring---组件扫描过程。该类会依次解析@PropertyResource注解、@ComponentScan注解、@Import注解等等。本文的主角就是@Import注解。
@Import注解收集
//org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
// Process any @Import annotations 296行
processImports(configClass, sourceClass, getImports(sourceClass), true);
首先获取通过getImports方法手机@import注解要导入的所有类
//org.springframework.context.annotation.ConfigurationClassParser#getImports
//sourceClass即我们的启动类BootApplication
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
//org.springframework.context.annotation.ConfigurationClassParser#collectImports
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
//递归扫描启动类的所有注解
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
//如果注解为@Import,则将其Value收集
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
通过上面分析,我们再来看@EnableTransactionManagement注解,其上的TransactionManagementConfigurationSelector类将会被收集进行分析。接下来,我们看下如何处理收集到的这个注解。
@Import注解处理
//org.springframework.context.annotation.ConfigurationClassParser#processImports
//configClass和currentSourceClass目前都是BootApplication
//importCandidates是我们刚收集到的@Import注解的value值
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
//循环处理我们收集到的@Import信息,其主要分三类进行处理,
//1.ImportSelector
//2.ImportBeanDefinitionRegistrar
//3.其他的作为@Configuration类处理
for (SourceClass candidate : importCandidates) {
//处理逻辑请看下文
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class ["