Spring事务管理---中

2022-06-12 14:17:36 浏览数 (1)

  • import导入配置
  • AutoProxyRegistrar
  • InfrastructureAdvisorAutoProxyCreator
  • TransactionAttributeSourceAdvisor
  • 小结

本系列文章:

Spring事务管理—上

Spring事务王国概览


使用Spring 2.x的声明事务配置方式

上面我们介绍完了三种XML元数据驱动的声明式事务的使用方式,下面我们介绍最后一种基于Spring 2.x的声明事务配置方式。

Spring 2.x后提供的基于XML Schema的配置方式,专门为事务管理提供了一个单独的命名空间用于简化配置,结合新的TX命名空间,现在的声明式事务管理看起来如下:

具体使用方式有以下几个步骤:

  • 引入相关aop和tx命名空间
代码语言:javascript复制
<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,业务对象准备
代码语言:javascript复制
<!--数据源准备-->
<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命名空间配置自动代理创建器
代码语言:javascript复制
    <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进行自动代理。

如果各位对aop命名空间开头的标签解析过程感兴趣的话,可以去看看AopNamespaceHandler类的源码,因为Spirng对于非默认命令空间的解析,对应都会去寻找对应的命名空间解析器进行解析。

代码语言:javascript复制
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的映射关系呢? —>利用自动代理创建器,这个具体下面会讲
  • 需要一个开关–>这个比较简单,我们可以自定义一个事务开启的注解,当需要开启事务的时候,我们就将上面四个组件导入到容器中。

思路也有了,解决办法也有了,下面就开始实战吧:

  • 开关最简单,我们这里先准备好
代码语言:javascript复制
@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 来完成
代码语言:javascript复制
/**
 * 注册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注解扫描不到的地方
代码语言:javascript复制
@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
代码语言:javascript复制
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();
    }


}
  • 测试
代码语言:javascript复制
//关闭事务的自动配置
@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是负责注册自动代理创建器的,那么他是怎么注册的呢?
代码语言:javascript复制
public class MyTransactionManagementConfigurationSelector implements ImportSelector {

    /**
     * 返回的是需要导入的bean的全类名
     * 这里我们需要导入一个配置类和一个自动代理创建器
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
            MyTransactionConfiguration.class.getName(),
                AutoProxyRegistrar.class.getName()
        };
    }
}

AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,因此它的registerBeanDefinitions方法会被调用

上面说过配置类的解析都是会递归解析的,即我导入的bean,也可能是一个配置类,或者也

代码语言:javascript复制
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提供的事务支持到底是怎么实现的。


0 人点赞