- import导入配置
- AutoProxyRegistrar
- InfrastructureAdvisorAutoProxyCreator
- TransactionAttributeSourceAdvisor
- 小结
本系列文章:
Spring事务管理—上
Spring事务王国概览
使用Spring 2.x的声明事务配置方式
上面我们介绍完了三种XML元数据驱动的声明式事务的使用方式,下面我们介绍最后一种基于Spring 2.x的声明事务配置方式。
Spring 2.x后提供的基于XML Schema的配置方式,专门为事务管理提供了一个单独的命名空间用于简化配置,结合新的TX命名空间,现在的声明式事务管理看起来如下:
具体使用方式有以下几个步骤:
- 引入相关aop和tx命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-beans.xsd">
- dataSource,transactioManager,业务对象准备
<!--数据源准备-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
...
</bean>
<!-- JdbcTemplate准备 -->
<bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器准备 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 业务对象准备 -->
<bean id="testService" class="org.transaction.TestService">
<constructor-arg ref="jt"/>
</bean>
- 使用专有的advice命名空间声明相关advice,aop命名空间配置自动代理创建器
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* org.transaction.TestService.update())"/>
<aop:advisor advice-ref="txAdivce" pointcut-ref="pointCut"/>
</aop:config>
<tx:advice id="txAdivce" transaction-manager="tm">
<tx:attributes>
<tx:method name="update" propagation="REQUIRED" read-only="true" timeout="20"/>
</tx:attributes>
</tx:advice>
< tx:advice >是专门为声明事务Adivce而设置的配置元素,底层还是TransactionInterceptor,其transaction-manager指明拦截器需要使用的事务管理器是哪个,如果容器中事务管理器的beanName恰好就是transactionManager,那么可以不明确指定。
< tx:attributes >提供声明式事务所需要的元数据映射信息,对应着拦截器中之前配置的TransactionAttributeSource。
< tx:method >的name属性必须指定,其他事务定义相关属性,如果不指定,采用默认的配置,即DefaultTransactionDefioiton。
tx:method可设置属性列表如下:
< tx:adivce > 指定的是拦截器的配置,那么需要有AOP的支持才能织入到具体的业务对象中去,所以剩下的工作实际上是AOP的配置。
和之前编码过程一样,还是通过自动代理创建器来创建代理对象,而aop:config底层就是依赖自动代理机制的,但是具体解析源码我就不带大家看了。
代码语言:javascript复制 <aop:config>
<aop:pointcut id="pointCut" expression="execution(* org.transaction.TestService.update())"/>
<aop:advisor advice-ref="txAdivce" pointcut-ref="pointCut"/>
</aop:config>
aop:config标签底层会由aop命名空间解析器进行解析,然后自动代理实现类会根据该元素内部对应的point,advisor及aspect的子元素取得必要的织入信息,然后为容器内注册的bean进行自动代理。
代码语言:javascript复制
如果各位对aop命名空间开头的标签解析过程感兴趣的话,可以去看看AopNamespaceHandler类的源码,因为Spirng对于非默认命令空间的解析,对应都会去寻找对应的命名空间解析器进行解析。
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// In 2.0 XSD as well as in 2.5 XSDs
//config标签由ConfigBeanDefinitionParser负责解析--感兴趣可以自行研究一下,这里篇幅有限,不展开看了
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
//通过标签方式开启aop自动代理的方式--对应AspectJAutoProxyBeanDefinitionParser解析器
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace in 2.5
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
注解元数据驱动的声明式事务
上面,我们介绍完了XML元数据驱动的声明式事务,下面来看看注解元数据驱动的声明式事务
注解元数据驱动,我相信大家都猜到了,就是我们最常使用的Transactional注解,注解元数据驱动的声明式事务基本原理就是,将业务方法的事务元数据,直接通过注解标注到业务方法或者业务方法所在的对象上,然后再业务方法执行期间,通过反射读取标注在业务方法上的注解所包含的元数据信息,最终根据读取的信息为业务方法构建事务管理的支持。
Transactional注解用于标注业务方法所对应的事务元数据信息,通过该注解可以指定与< te:method />标签几乎相同的信息。
当然,现在不需要指定方法名称了,因为注解直接标注到了业务方法或者业务方法所在的对象定义上。
代码语言:javascript复制@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
基于注解的驱动式事务基本使用如下:
代码语言:javascript复制@Transactional
@Component
@RequiredArgsConstructor
public class TestService {
private final JdbcTemplate jt;
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true,timeout = 20)
public void update() throws Exception {
throw new RuntimeException("我是来捣乱的");
}
}
有下面几点需要注意:
- @Transactional标注为对象级别的话,该注解标注的事务管理信息会应用到该类所有方法上。通过将相同的事务管理行为提取到对象级别的@Transactional,可以有效减少标注的数量。
- 如果只标注在了指定方法上,那么只会应用在指定方法上。
- 如果不为@Transactional注解指定一些自定义事务配置,那么它会像< tx:method />一样采用与DefaultTransactionDefinition一样的事务定义内容。
如果仅仅只通过@Transactional标注业务对象和对象中的业务方法,并不会给相应的业务方法执行提供任何事务管理信息的支持,该注解的作用类似TransactionAttributeSource中保存的一条映射关系,即通过该注解我们只是知道了当前方法需要什么样的事务支持,但是我们只有在方法执行时通过反射读取这些事务信息,才能去构建事务,从而使得事务生效。
模拟解析注解
首先,我们需要弄清楚解析步骤:
- 判断哪些bean上标注了@Transactional注解,然后解析得到TransactionAttribute
- 将当前方法和TransactionAttribute的映射关系加入TransactionAttributeSource中
- 加入TransactionAttributeSource的前提是我们已经准备好了一个TransactionAttributeSource,当然还有TransactionInterceptor和TransactionManager,还有最重要的自动代理创建器
- 但是事务并不是任何情况下都需要的,即TI,TM,TAS和自动代理创建器并不是什么任何情况下都需要创建的
- 因此,我们需要准备一个开关,需要的时候打开开关,创建上面四个组件,别的时候就不管
分析完上面的需求后,我们大概知道了具体需要怎么做,那么到底怎么完成上面的需求呢?
- 判断哪些bean上标注了@Transactional注解—>需要对每一个bean都进行检查,这不就想到了bean的后置处理器吗,而且还需要为每一个需要事务支持的bean生成代理对象,那么同时兼具这两个功能的,不就是自动代理创建器吗?
- 在什么时候获取当前方法和对应TransactionAttribute的映射关系呢? —>利用自动代理创建器,这个具体下面会讲
- 需要一个开关–>这个比较简单,我们可以自定义一个事务开启的注解,当需要开启事务的时候,我们就将上面四个组件导入到容器中。
思路也有了,解决办法也有了,下面就开始实战吧:
- 开关最简单,我们这里先准备好
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//导入一个类
@Import(MyTransactionManagementConfigurationSelector.class)
public @interface MyEnableTransactionManagement {
/**
* 是否默认采用cglib进行代理
*/
boolean proxyTargetClass() default false;
/**
* 代理模式--是jdk,cglib代理还是aspectj代理
*/
AdviceMode mode() default AdviceMode.PROXY;
}
- 开关打开后,我们就需要导入上面说的那些组件了,而导入的工作就交给了MyTransactionManagementConfigurationSelector 来完成
/**
* 注册TM,拦截器和自动代理创建器
*/
public class MyTransactionManagementConfigurationSelector implements ImportSelector {
/**
* 返回的是需要导入的bean的全类名
* 这里我们需要导入一个配置类和一个自动代理创建器
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
//注册TM,拦截器等组件
MyTransactionConfiguration.class.getName(),
//注册自动代理创建器到容器
AutoProxyRegistrar.class.getName()
};
}
}
ImportSelector的原理涉及配置类的解析过程,我下面会稍微提一嘴,该接口的作用就是可以向容器中放入某些bean集合
- 先来看看TM,拦截器的注册,就是MyTransactionConfiguration就是导入一个配置类,但是注意这个配置类必须放在启动类或者自己加的@CompoentScan注解扫描不到的地方
@Configuration
public class MyTransactionConfiguration {
//注册TM
@Bean
//标注角色为基础设施类,该bean被解析后,其BeanDefinition中会标记当前bean的role为ROLE_INFRASTRUCTURE
//ROLE_INFRASTRUCTURE与我们待会要注入的自动代理创建器有关,暂时先不用深究
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionManager transactionManager(DataSource dataSource){
return new JdbcTransactionManager(dataSource);
}
//注册transactionAttributeSource---这里放入的是我们自定义的MyTransactionAttributeSource
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource(){
return new MyTransactionAttributeSource();
}
//注册拦截器
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionManager transactionManager,TransactionAttributeSource transactionAttributeSource){
return new TransactionInterceptor(transactionManager,transactionAttributeSource);
}
/**
* transactionAttributeSourceAdvisor增强器最大的作用在于
* 给我们提供了一个TransactionAttributeSourcePointcut
* 该pointcut可以帮助我们进行类级别的过滤和方法级别的过滤
* 方法级别的过滤是当前方法在TransactionAttributeSource中能否找到对应的一个TransactionAttribute
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSourceAdvisor transactionAttributeSourceAdvisor(TransactionInterceptor transactionInterceptor){
return new TransactionAttributeSourceAdvisor(transactionInterceptor);
}
}
- 自动代理创建器导入的是InfrastructureAdvisorAutoProxyCreator,通过AutoProxyRegistrar导入的,因为他实现了ImportBeanDefinitionRegistrar接口,该接口实现后,也可以向容器中导入bean,和ImportSelector功能一致
- 在来看看我们自定义的transactionAttributeSource
public class MyTransactionAttributeSource implements TransactionAttributeSource {
/**
* 负责解析@Transactional注解的
*/
private TransactionAnnotationParser parser = new SpringTransactionAnnotationParser();
/**
* 方法名和方法名关联的TransactionAttribute
*/
private final Map<String, TransactionAttribute> nameMap = new HashMap<>();
/**
* 存放已经解析过的方法
*/
private final Set<String> parsedMethod=new HashSet<>();
/**
* 类过滤信息缓存--key是类名,value表示当前类是否需要事务支持: 1表示需要,其他数字表示不需要
*/
private final Map<String,Integer> classFilter=new HashMap<>();
private final Integer NEED_TRANSACTION=1;
/**
* 用于pointcut的类过滤---判断类和类的每个方法上是否标注了@Transacion注解
*/
@Override
public boolean isCandidateClass(Class<?> targetClass) {
//查询缓存
Integer res = classFilter.get(targetClass.getName());
if(res!=null){
return res.equals(NEED_TRANSACTION);
}
//当前类是否需要事务,即当前类上,方法上是否存在@Transactional注解
if(parser.isCandidateClass(targetClass)){
classFilter.put(targetClass.getName(),NEED_TRANSACTION);
return true;
}
return false;
}
/**
* 该方法在TransactionAttributeSourceAdvisor增强器的point中,用于方法级别过滤
* 如果第一次来,缓存没有会进行解析,第二次来直接走缓存即可
*/
@Override
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
//先去缓存中寻找--保存已经解析过
String cacheKey = getCacheKey(method, targetClass);
if(parsedMethod.contains(cacheKey)){
return nameMap.get(cacheKey);
}
//如果当前类上,类的方法上都没有标注该注解,那么就跳过
if(!parser.isCandidateClass(targetClass)){
parsedMethod.add(cacheKey);
return null;
}
//解析方法上标注的@Transactional注解
TransactionAttribute attr = parser.parseTransactionAnnotation(method);
if(attr==null){
//如果方法上没标注,再尝试解析类上的注解
attr=parser.parseTransactionAnnotation(targetClass);
}
nameMap.put(cacheKey,attr);
parsedMethod.add(cacheKey);
return attr;
}
private String getCacheKey(Method method, Class<?> targetClass) {
return targetClass.getName() method.getName();
}
}
- 测试
//关闭事务的自动配置
@SpringBootApplication(exclude = {TransactionAutoConfiguration.class})
//开启我们手动的事务配置
@MyEnableTransactionManagement
public class TransactionMain {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ConfigurableApplicationContext app = SpringApplication.run(TransactionMain.class, args);
TestService testService = app.getBean(TestService.class);
try {
testService.update();
} catch (Exception e) {
e.printStackTrace();
}
}
}
SpringBoot会自动开启事务的相关配置,因此我们需要先关闭一下,替换为我们自定义的事务管理实现
模拟流程的原理解析
如果能看懂上面模拟解析的每个步骤,那么说明你对Spring源码研究的还不错,如果没看懂,没关系,下面我一点点带领大家来分析:
import导入配置
就先从最简单的开始吧:
代码语言:javascript复制@Import(MyTransactionManagementConfigurationSelector.class)
代码语言:javascript复制public class MyTransactionManagementConfigurationSelector implements ImportSelector {
/**
* 返回的是需要导入的bean的全类名
* 这里我们需要导入一个配置类和一个自动代理创建器
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
MyTransactionConfiguration.class.getName(),
AutoProxyRegistrar.class.getName()
};
}
}
因为Import注解,ImportSelector相关接口的解析都涉及到了@Configuration标注的配置类解析过程,所以这里先来大致浏览一下配置类解析是个怎么样的流程:
配置类是由ConfigurationClassPostProcessor工厂后置处理器解析的,工厂后置处理器相关接口都会在refresh方法中被调用:
代码语言:javascript复制@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//如果使用xmlContext,那么这一步会去扫描xml文件,然后得到里面所有定义的bean
//如果是注解Context,就将配置类放进去进行解析
//如果是Springboot启动的话,那么是GenericApplicationContext,啥也没解析
//目前容器中只有一些springboot提前准备的内部类和主启动类--但是主启动类也是配置类
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
...
//触发工厂bean后置处理器---如果是SpringBoot的话,在容器启动时,就会放入一个ConfigurationClassPostProcessor
//该工厂后置处理器用来解析配置类
invokeBeanFactoryPostProcessors(beanFactory);
//注册bean的相关后置处理器
registerBeanPostProcessors(beanFactory);
....
//实例化剩余单例
finishBeanFactoryInitialization(beanFactory);
...
ConfigurationClassPostProcessor是关键,这里我们直接来简单看看他的解析过程(配置类解析很复杂,后面会专门出一篇文章讲解):
processConfigBeanDefinitions是该工厂后置处理器的核心方法:
代码语言:javascript复制public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
//第一步: 在当前的beanDefintions集合中,寻找是配置类的bean有哪些
//找到之后会加入candidateNames候选集合中
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
//怎么找的,这里就挨个判断呗
....
}
// Return immediately if no @Configuration classes were found
//如果没找到,就返回---记住Spirngboot启动类也是配置类,因此这里第一次进来肯定不为空
//但是由于存在配置类的递归解析,因此这里可能会进来多次
if (configCandidates.isEmpty()) {
return;
}
....
// Parse each @Configuration class
//配置类的解析器---核心工具类之一
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
//parse方法负责解析配置类,例如:springboot的主启动类,上面标注了@ComponentScan注解,因此是在这一步,才会去进行各种包扫描的
//Springboot的obtainFreshBeanFactory()方法底层调用的是GenericApplicationContext的方法,该类没有做相关bean解析工作,因此obtainFreshBeanFactory()方法并没有进行宝包扫描的工作和相关注解解析--这一点要注意了
//@Import注解就是在这里被解析的,还有ImportSelector接口也是在这里被调用的
parser.parse(candidates);
....
Parse方法执行前:
Parse方法执行后:
还只是处于解析得到bean定义阶段,并没有进行实例化,即没有调用相关bean的getBean方法:
代码语言:javascript复制 parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//负责从配置类中读取bean定义信息的读取器,这是第二重要的核心工具类
//这里configClasses是上面配置类解析得到的所有bean,包括他自己也在这个集合中
//configClasses不代表这个集合中只存放了配置类,而是当前配置类解析得到的所有bean和他自己
this.reader.loadBeanDefinitions(configClasses);
代码语言:javascript复制 public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
//遍历,挨个去加载bean信息
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
真正从类中加载bean信息的方法:
代码语言:javascript复制 private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//是否要跳过解析---主要涉及到@Conditional注解的判断,这里不管
if (trackedConditionEvaluator.shouldSkip(configClass)) {
...
}
//当前类是被导入的
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//当前类中有@Bean注解---这里其实可以看出@Bean注解可以不标注在配置类中,但是标注在配置类中和不标注在配置类中
//还是存在区别的,这里不展开讲
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//加载@importResouces注解
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//这里是调用当前配置类中有的所有实现了ImportBeanDefinitionRegistrar接口的bean
//然后挨个调用他们的registerBeanDefinitions方法
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
小总结:
- @Import注解是在parse方法中被解析的,会根据其导入的类是否实现了ImportBeanDefinitionRegistrar和ImportSelector接口进行不同的处理
- 如果实现了ImportSelector接口的,会在parse调用过程中被processImports方法解析处理并回调相关接口‘
- 而如果实现了’ImportBeanDefinitionRegistrar接口,则会在解析完后的loadBeanDefinitionsForConfigurationClass方法中被回调
AutoProxyRegistrar
- 导入这一步分析完了,下面该分析一下我们导入的组件都有什么作用了,MyTransactionConfiguration就是一个配置类不用多说,AutoProxyRegistrar是负责注册自动代理创建器的,那么他是怎么注册的呢?
public class MyTransactionManagementConfigurationSelector implements ImportSelector {
/**
* 返回的是需要导入的bean的全类名
* 这里我们需要导入一个配置类和一个自动代理创建器
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
MyTransactionConfiguration.class.getName(),
AutoProxyRegistrar.class.getName()
};
}
}
AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,因此它的registerBeanDefinitions方法会被调用
代码语言:javascript复制
上面说过配置类的解析都是会递归解析的,即我导入的bean,也可能是一个配置类,或者也
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
private final Log logger = LogFactory.getLog(getClass());
@Override
public void registerBeanDefinitions(
//当前类上的相关注解元数据信息
AnnotationMetadata importingClassMetadata,
//bean的注册中心
BeanDefinitionRegistry registry) {
boolean candidateFound = false;
//拿到当前类上标注的所有注解的名字
Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
//挨个注解解析
for (String annType : annTypes) {
//拿到当前注解上的属性
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (candidate == null) {
continue;
}
//判断当前注解上是否标注了mode和proxyTargetClass属性
//mode就是代理模式,有jdk代理和cglib代理两种方式
//proxyTargetClass被设置后,就会采用cglib代理
//这就是为什么我们自定义注解中存在两个属性的原因,如果不进行指定的话,那么这里AutoProxyRegistrar不会帮我们去注册
//自动代理创建器
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;
//AdviceMode还有一种选择是ASPECTJ,即采用aspectj的工具来实现动态代理
//proxy表示采用jdk或者cglib完成动态代理
if (mode == AdviceMode.PROXY) {
//先注册一个自动代理创建器
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
//强迫采用cglib完成动态代理,如果满足条件的话
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
if (!candidateFound && logger.isInfoEnabled()) {
String name = getClass().getSimpleName();
logger.info(String.format("%s was imported but no annotations were found "
"having both 'mode' and 'proxyTargetClass' attributes of type "
"AdviceMode and boolean respectively. This means that auto proxy "
"creator registration and configuration may not have occurred as "
"intended, and components may not be proxied as expected. Check to "
"ensure that %s has been @Import'ed on the same class where these "
"annotations are declared; otherwise remove the import of %s "
"altogether.", name, name, name));
}
}
}
registerAutoProxyCreatorIfNecessary是如何注册自动代理创建器的,请看下面这篇文章,因为篇幅有限就不展开了
Spring读源码系列之AOP–07—aop自动代理创建器(拿下AOP的最后一击)
这里来看看forceAutoProxyCreatorToUseClassProxying方法:
代码语言:javascript复制 public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
//取出刚才注册进去的那个自动代理创建器
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
//设置其proxyTargetClass属性为true,别忘了自动代理创建器都继承了ProxyConfig类,并且在创建代理前
//会通过ProxyFactory复制一份当前自动代理创建器的ProxyConfig配置
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
InfrastructureAdvisorAutoProxyCreator
通过上面的源码追踪,我们知道了AutoProxyRegistrar最终注册到容器中的自动代理创建器是InfrastructureAdvisorAutoProxyCreator,那么这个自动代理创建器有什么特殊之处呢?
如果想完整了解aop自动代理创建器实现的话,推荐看看下面这篇文章
Spring读源码系列之AOP–07—aop自动代理创建器(拿下AOP的最后一击)
代码语言:javascript复制public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
@Nullable
private ConfigurableListableBeanFactory beanFactory;
@Override
protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.initBeanFactory(beanFactory);
this.beanFactory = beanFactory;
}
//特殊之处在于他的isEligibleAdvisorBean方法,该方法作用下面讲
@Override
protected boolean isEligibleAdvisorBean(String beanName) {
//该方法主要是用来筛选掉一批不符合规范的advisor增强器的--筛选标准就是当前advisor是否是一个BeanDefinition.ROLE_INFRASTRUCTURE
//这也是为什么我们导入的配置类中会给增强器标注@Role注解的原因所在了,为了让其能够被当前这个自动代理创建器筛选出来
return (this.beanFactory != null && this.beanFactory.containsBeanDefinition(beanName) &&
this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE);
}
}
isEligibleAdvisorBean方法会在BeanFactoryAdvisorRetrievalHelper的findAdvisorBeans方法中被调用:
代码语言:javascript复制public List<Advisor> findAdvisorBeans() {
....
//拿到容器中所有Advisor
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
....
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
//提前筛选掉一批不符合当前自动代理创建器要求的bean
if (isEligibleBean(name)) {
//满足条件的增强器bean还会判断一下是否正处于创建状态,如果是的话,也跳过
//否则加入候选集合
...
}
}
return advisors;
}
findAdvisorBeans又会在findCandidateAdvisors方法中被调用:
代码语言:javascript复制 protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//返回容器中所有类型为Advisor的bean集合--这里已经通过isEligibleBean方法筛选掉了一批
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//判断候选增强器集合中,哪些能应用在当前bean上
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
findAdvisorsThatCanApply主要做了下面几件事:
- 遍历每个增强器,然后通过CanApply方法判断当前增强器是否能够应用到当前bean上
- CanApply方法会判断当前增强器的类型,如果是IntroductionAdvisor,那么就只进行ClassFilter级别的校验
- 如果是PointcutAdvisor,那么首先进行ClassFilter基本的校验,如果通过了,再获取当前类中所有方法,包括私有,挨个方法判断methodMatcher方法是否符合,如果其中任意一个满足,就返回true
- 如果是其他增强器类型,那么默认返回true
findEligibleAdvisors拿到了能应用在当前类上的增强器集合后返回到getAdvicesAndAdvisorsForBean方法中
代码语言:javascript复制 protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
//集合为空,表名当前类不需要被代理
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
getAdvicesAndAdvisorsForBean会在AbstractAutoProxyCreator的wrapIfNecessary方法中被调用,用来获取能应用到当前类上的特定拦截器有哪些
代码语言:javascript复制 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
...
// Create proxy if we have advice.
//获取能应用到当前类上的特定拦截器有哪些
//getAdvicesAndAdvisorsForBean是抽象方法,不同子类实现不一样
//我们这里的InfrastructureAdvisorAutoProxyCreator继承父类AbstractAdvisorAutoProxyCreator
//父类默认会去容器中搜索全部的增强器实现,进行挨个匹配
//如果是BeanNameAutoProxyCreator那么他如果决定要对当前类进行代理,返回的拦截器列表是空
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
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;
}
AbstractAutoProxyCreator类的源码解析在上一篇中已经详细介绍过了,这里不展开了
TransactionAttributeSourceAdvisor
既然我们知道了AbstractAdvisorAutoProxyCreator会自动搜集容器中的Advisor实现,那么我们只需要往容器中放入Advisor即可。
TransactionAttributeSourceAdvisor之前也讲过,他最大的好处在于提供了与事务过滤相关的pointcut,即TransactionAttributeSourcePointcut.
和普通的Advisor一样,内部拥有一个advice和一个pointcut,特殊就特殊在他的pointcut的类过滤和方法过滤。
代码语言:javascript复制 @Nullable
private TransactionInterceptor transactionInterceptor;
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
@Nullable
protected TransactionAttributeSource getTransactionAttributeSource() {
return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);
}
};
TransactionAttributeSourcePointcut的类过滤:
代码语言:javascript复制 private class TransactionAttributeSourceClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
//如果当前类是这三个类型,那么不需要事务支持
if (TransactionalProxy.class.isAssignableFrom(clazz) ||
TransactionManager.class.isAssignableFrom(clazz) ||
PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
return false;
}
//TransactionAttributeSource为null,默认返回true
//一般都不为空,如果不为空,则通过TransactionAttributeSource的isCandidateClass返回结果
//作为类过滤的判定结果--即决定是否要对当前类提供事务支持
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.isCandidateClass(clazz));
}
}
方法过滤如下:
代码语言:javascript复制 @Override
public boolean matches(Method method, Class<?> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
//能否从TransactionAttributeSource中获取到TransactionAttrbuite是是否对当前方法提供事务支持的条件
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
此时各位再回头去看看我之前自定义的MyTransactionAttributeSource,就知道为什么要那么写了
小结
到此,我们模拟注解元数据驱动的声明式事务也就完成了,不知道大家有没有理解上面的运行过程,实际上我是上面给出的模拟流程基本和Spring一致,只有看懂了上面的实现思路,才能看懂spring提供的事务支持到底是怎么实现的。