Spring从成神到升仙系列 四从源码分析 Spring 事务的来龙去脉

Posted 爱敲代码的小黄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring从成神到升仙系列 四从源码分析 Spring 事务的来龙去脉相关的知识,希望对你有一定的参考价值。

  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,加我进群,大家一起学习,一起进步👀

文章目录

Spring 事务源码解析

一、引言

对于Java开发者而言,关于 Spring ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本期 Spring 源码解析系列文章,将带你领略 Spring 源码的奥秘

本期源码文章吸收了之前 Kafka 源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

废话不多说,发车!

本篇目录如下:

本文流程图可关注公众号:爱敲代码的小黄,回复:事务 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用

二、事务的本质

  数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

  事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。

  一个逻辑工作单元要成为事务,必须满足所谓的 ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

1、JDBC的事务

我们来看一下在 JDBC 中对事务的操作处理:

public class JDBCTransactionExample 
    public static void main(String[] args) 
        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;

        try 
            // 加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
            // 关闭自动提交,开启事务
            conn.setAutoCommit(false);

            // 创建SQL语句
            String sql1 = "UPDATE account SET balance = balance - ? WHERE id = ?";
            String sql2 = "UPDATE account SET balance = balance + ? WHERE id = ?";

            // 创建PreparedStatement对象
            pstmt1 = conn.prepareStatement(sql1);
            pstmt2 = conn.prepareStatement(sql2);

            // 设置参数
            pstmt1.setDouble(1, 1000);
            pstmt1.setInt(2, 1);
            pstmt2.setDouble(1, 1000);
            pstmt2.setInt(2, 2);

            // 执行更新操作
            int count1 = pstmt1.executeUpdate();
            int count2 = pstmt2.executeUpdate();

            if (count1 > 0 && count2 > 0) 
                System.out.println("转账成功");
                // 提交事务
                conn.commit();
             else 
                System.out.println("转账失败");
                // 回滚事务
                conn.rollback();
            
         catch (ClassNotFoundException e) 
            e.printStackTrace();
         catch (SQLException e) 
            try 
                if (conn != null) 
                    // 回滚事务
                    conn.rollback();
                
             catch (SQLException e1) 
                e1.printStackTrace();
            
            e.printStackTrace();
         finally 
            try 
                if (pstmt1 != null) 
                    pstmt1.close();
                
                if (pstmt2 != null) 
                    pstmt2.close();
                
                if (conn != null) 
                    conn.close();
                
             catch (SQLException e) 
                e.printStackTrace();
            
        
    


上面的代码,我相信大部分的人都应该接触过,这里也就不多说了

主要我们看几个重点步骤:

  • 获取连接:Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password")
  • 关闭自动提交,开启事务:conn.setAutoCommit(false)
  • 提交事务:conn.commit()
  • 回滚事务:conn.rollback()

2、Spring的事务

我们在日常生产项目中,项目由 ControllerSerivceDao 三层进行构建。

我们从上图中可以了解到:

对于 addUser 方法实际对于数据调用来说,分别调用了 insertUser()insertLog 方法,对数据库的操作为两次

我们要保证 addUser 方法是符合事务定义的。

2.1 xml配置

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	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/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
	<!-- 开启扫描 -->
	<context:component-scan base-package="com.dpb.*"></context:component-scan>

	<!-- 配置数据源 -->
	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
		<property name="username" value="pms"/>
		<property name="password" value="pms"/>
	</bean>

	<!-- 配置JdbcTemplate -->
	<bean class="org.springframework.jdbc.core.JdbcTemplate" >
		<constructor-arg name="dataSource" ref="dataSource"/>
	</bean>

	<!-- 
	Spring中,使用XML配置事务三大步骤:  
		1. 创建事务管理器  
		2. 配置事务方法  
		3. 配置AOP
	 -->
	 <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
	 	<property name="dataSource" ref="dataSource"/>
	 </bean>
	 <tx:advice id="advice" transaction-manager="transactionManager">
	 	<tx:attributes>
	 		<tx:method name="fun*" propagation="REQUIRED"/>
	 	</tx:attributes>
	 </tx:advice>
	 <!-- aop配置 -->
	 <aop:config>
		 <aop:pointcut expression="execution(* *..service.*.*(..))" id="tx"/>
	 	 <aop:advisor advice-ref="advice" pointcut-ref="tx"/>
	 </aop:config>
</beans>

2.2 注解配置

首先必须要添加 @EnableTransactionManagement 注解,保证事务注解生效

@EnableTransactionManagement
public class AnnotationMain 
    public static void main(String[] args) 
    

其次,在方法上添加 @Transactional 代表注解生效

@Transactional
public int insertUser(User user) 
    userDao.insertUser();
    userDao.insertLog();
    return 1;

上面的操作涉及两个重点:

  • 事务的传播属性

  • 事务的隔离级别

三、Spring事务源码剖析

本次剖析源码我们会尽量挑重点来讲,因为事务源码本身就是依靠 AOP 实现的,我们之前已经很详细的讲过 IOCAOP 的源码实现了,这次带大家一起过一遍事务即可。

因为从博主本身而言,我感觉 Spring 事务其实没有那么的重要,面试也不常考,所以不会花大量的时间去剖析细节源码。

1、TransactionManager

首先我们看一下这个接口的一些组成配置:

****

这里我们重点看 PlatformTransactionManager 的实现,其实现一共三个方法:

  • 获取事务:TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
  • 提交事务:void commit(TransactionStatus status)
  • 回滚事务:void rollback(TransactionStatus status)

我们分别看一下其如何实现的

1.1 获取事务

我们想一下,在获取事务这一阶段,我们会做什么功能呢?

参考上述我们 JDBC 的步骤,这个阶段应该会 创建连接并且开启事务

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    
   // PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED都需要新建事务
   if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
         def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
         def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) 
      //没有当前事务的话,REQUIRED,REQUIRES_NEW,NESTED挂起的是空事务,然后创建一个新事务
      SuspendedResourcesHolder suspendedResources = suspend(null);
      try 
         // 看这里重点:开始事务
         return startTransaction(def, transaction, debugEnabled, suspendedResources);
      
      catch (RuntimeException | Error ex) 
         // 恢复挂起的事务
         resume(null, suspendedResources);
         throw ex;
      
   


private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) 
    // 是否需要新同步
	 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
	 // 创建新的事务
	 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
	// 【重点】开启事务和连接
	doBegin(transaction, definition);
	// 新同步事务的设置,针对于当前线程的设置
	prepareSynchronization(status, definition);
	return status;


protected void doBegin(Object transaction, TransactionDefinition definition) 
    		// 判断事务对象没有数据库连接持有器
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) 
				// 【重点】通过数据源获取一个数据库连接对象
				Connection newCon = obtainDataSource().getConnection();
				// 把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去
             // 再次进来时,该 txObject 就已经有事务配置了
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			
    		
    		// 【重点】获取连接
    		con = txObject.getConnectionHolder().getConnection();
    
    
			// 为当前的事务设置隔离级别【数据库的隔离级别】
			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			// 设置先前隔离级别
			txObject.setPreviousIsolationLevel(previousIsolationLevel);
			// 设置是否只读
			txObject.setReadOnly(definition.isReadOnly());

			// 关闭自动提交
			if (con.getAutoCommit()) 
				//设置需要恢复自动提交
				txObject.setMustRestoreAutoCommit(true);
				// 【重点】关闭自动提交
				con.setAutoCommit(false);
			

			// 判断事务是否需要设置为只读事务
			prepareTransactionalConnection(con, definition);
			// 标记激活事务
			txObject.getConnectionHolder().setTransactionActive(true);

			// 设置事务超时时间
			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) 
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			

			// 绑定我们的数据源和连接到我们的同步管理器上,把数据源作为key,数据库连接作为value 设置到线程变量中
			if (txObject.isNewConnectionHolder()) 
				// 将当前获取到的连接绑定到当前线程
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			
		

到这里,我们的 获取事务 接口完成了 数据库连接的创建关闭自动提交(开启事务),将 Connection 注册到了缓存(resources)当中,便于获取。

1.2 提交事务

public final void commit(TransactionStatus status) throws TransactionException 
		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		// 如果在事务链中已经被标记回滚,那么不会尝试提交事务,直接回滚
		if (defStatus.isLocalRollbackOnly()) 
			// 不可预期的回滚
			processRollback(defStatus, false);
			return;
		

		// 设置了全局回滚
		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) 
			// 可预期的回滚,可能会报异常
			processRollback(defStatus, true);
			return;
		

		// 【重点】处理事务提交
		processCommit(defStatus);


// 处理提交,先处理保存点,然后处理新事务,如果不是新事务不会真正提交,要等外层是新事务的才提交,
// 最后根据条件执行数据清除,线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
private void processCommit(DefaultTransactionStatus status) throws TransactionException ;
     // 如果是独立的事务则直接提交
	  doCommit(status);
                                                                             //根据条件,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
	  cleanupAfterCompletion(status);

这里比较重要的有两个步骤:

  • doCommit:提交事务(直接使用 JDBC 提交即可)

    protected void doCommit(DefaultTransactionStatus status) 
       DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
       Connection con = txObject.getConnectionHolder().getConnection();
       try 
          // JDBC连接提交
          con.commit();
       
       catch (SQLException ex) 
          throw new TransactionSystemException("Could not commit JDBC transaction", ex);
       
    
    
  • cleanupAfterCompletion:数据清除,与线程中的私有资源解绑,方便释放

    // 线程同步状态清除
    TransactionSynchronizationManager.clear();
    
    // 清除同步状态【这些都是线程的缓存,使用ThreadLocal的】
    public static void clear() 
        synchronizations.remove();
        currentTransactionName.remove();
        currentTransactionReadOnly.remove();
        currentTransactionIsolationLevel.remove();
        actualTransactionActive.remove();
    
    // 如果是新事务的话,进行数据清除,线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接等
    doCleanupAfterCompletion(status.getTransaction());
    
    // 此方法做清除连接相关操作,比如重置自动提交啊,只读属性啊,解绑数据源啊,释放连接啊,清除链接持有器属性
    protected void doCleanupAfterCompletion(Object transaction) 
    		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    		// 将数据库连接从当前线程中解除绑定
    		TransactionSynchronizationManager.unbindResource(obtainDataSource());
    
    		// 释放连接
    		Connection con = txObject.getConnectionHolder().getConnection();
        
    		// 恢复数据库连接的自动提交属性
    		con.setAutoCommit(true);
          // 重置数据库连接
    	   DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
    		
    		// 如果当前事务是独立的新创建的事务则在事务完成时释

    Spring从成神到升仙系列 三2023年再不会 AOP 源码,就要被淘汰了

    • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
    • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
    • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
    • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
    • 📝联系方式:hls1793929520,和大家一起学习,一起进步👀

    文章目录

    Spring AOP源码解析

    一、引言

    对于Java开发者而言,关于 Spring ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

    但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

    本期 Spring 源码解析系列文章,将带你领略 Spring 源码的奥秘

    本期源码文章吸收了之前 Kafka 源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

    废话不多说,发车!

    本文流程图可关注公众号:爱敲代码的小黄,回复:AOP 获取
    贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用

    二、适合人群

    本文采用的 Spring 版本为:4.3.11.RELEASE

    本文适合人群:使用过Spring AOP的功能,了解过切面编程

    本文前置知识:

    如果你都已经满足,那么跟我一起了解一下抽象的 AOP 吧。

    三、Spring AOP配置

    首先创建我们的核心类:BeanA

    public class BeanA 
        public void do1() 
            System.out.println("I am a do1, I start....");
        
    
    

    我们日志配置类:LogUtil

    public class LogUtil 
    
        private void before(JoinPoint joinPoint) 
            //获取方法签名
            Signature signature = joinPoint.getSignature();
            //获取参数信息
            Object[] args = joinPoint.getArgs();
            System.out.println("log---" + signature.getName() + "I am before");
        
    
        private void after(JoinPoint joinPoint) 
            //获取方法签名
            Signature signature = joinPoint.getSignature();
            //获取参数信息
            Object[] args = joinPoint.getArgs();
            System.out.println("log---" + signature.getName() + "I am before");
        
    
    

    我们 xml 配置:application.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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"
           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-aop.xsd">
    
        <bean id="beanA" class="com.hls.aop.BeanA"/>
    
        <bean id="logUtil" class="com.hls.aop.LogUtil"/>
    
        <aop:config>
            <aop:aspect ref="logUtil">
                <aop:pointcut id="myPoint"  expression="execution(* com.hls.aop.BeanA.do*(..))"/>
                <aop:before method="before" pointcut-ref="myPoint"/>
                <aop:after method="after" pointcut-ref="myPoint"/>
            </aop:aspect>
        </aop:config>
    </beans>
    

    编写我们的测试类:AOPTest

    public class AOPTest 
        public static void main(String[] args) 
            ApplicationContext context = new GenericXmlApplicationContext("application.xml");
    
            BeanA beanA = context.getBean(BeanA.class);
    
            beanA.do1();
        
    
    

    运行输出结果:

    log---do1I am before
    I am a do1, I start....
    log---do1I am before
    

    四、Spring AOP 组件分析

    AOP 的组件在源码中比较重要的有四个:

    • Pointcut:定义切面的匹配点,主要是类和方法
    • Advice:定义切面的行为,即在匹配点执行的操作。
    • Advisor:将 Pointcut 和 Advice 组合成一个对象,表示一个完整的切面。
    • Aspect:使用注解或 XML 配置方式定义切面,通常包含多个 Advisor

    我们依次讲解这些组件是什么意思

    1、Pointcut

    一般情况下我们的 Pointcut 都是以这种形式出现:

    <aop:pointcut id="myPoint" expression="execution(* com.mashibing.hls.aop.BeanA.do*(..))"/>
    

    主要就是指明切入点的表达式,比如上述表达式:* com.hls.aop.BeanA.do*(..)

    代表 com.hls.aop 包下的 BeanA 类以 do 开头的方法都可以进行切入

    我们看下源码中的形式:

    public interface Pointcut 
    	/**
    	 * 在类级别上限定joinpoint的匹配范围
    	 *
    	 * Return the ClassFilter for this pointcut.
    	 * @return the ClassFilter (never @code null)
    	 */
    	ClassFilter getClassFilter();
    
    	/**
    	 * 在方法级别上限定joinpoint的匹配范围
    	 *
    	 * Return the MethodMatcher for this pointcut.
    	 * @return the MethodMatcher (never @code null)
    	 */
    	MethodMatcher getMethodMatcher();
    
    
    	/**
    	 * 用于匹配上的一个实例,永远返回true
    	 *
    	 * Canonical Pointcut instance that always matches.
    	 */
    	Pointcut TRUE = TruePointcut.INSTANCE;
    
    
    
    
    // Pointcut 的实现
    public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware 
        // 类的匹配
        public boolean matches(Class<?> targetClass) 
        
        // 方法的匹配 
        public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) 
    
    

    通过源码我们可以发现,这个类提供的功能主要是用来匹配类与方法。

    2、Advice

    一般情况下,我们的 Advice 以这种情况出现:

    <aop:before/>
    <aop:after/>
    

    主要的目的就是定义我们切面的行为,比如:方法前切入、方法后切入、环绕切入等

    我们看下源码的形式:

    public interface Advice 
    
    
    
    // 前置切入实现
    public interface BeforeAdvice extends Advice 
    
    // 后置切入实现
    public interface AfterAdvice extends Advice 
    

    一般我们的 Advice 都与 Pointcut 相结合使用

    3、Advisor

    一般情况下,我们的 Advisor 以这种情况出现:

    <aop:before method="before" pointcut-ref="myPoint"/>
    <aop:after method="after" pointcut-ref="myPoint"/>
    

    AdvicePointcut 结合起来使用

    我们看下源码的形式:

    public interface Advisor 
       // 返回切面对应的通知
    	Advice getAdvice();
    
    

    很明显可以看出,这个类主要包装了 Advice,后续我们会继续讲到

    4、Aspect

    一般情况下,我们的 Aspect 以这种情况出现:

    <aop:aspect ref="logUtil">
    </aop:aspect>
    

    定义一个日志配置类,其方法作为实际的切入业务逻辑

    5、总结

    从我们上述的描述得知,我们几个组件的关系如下:

    大家这里心里有个大致的了解即可,我们后面会详细讲到。

    五、Spring AOP 源码剖析

    同样和我们的 IOC 类似,我们从入口开始:

    ApplicationContext context = new GenericXmlApplicationContext("application.xml");
    

    1、beanDefinition的注册

    同样和我们 IOC 流程一样,先进行 xml 文件的解析

    我们直接找到 DefaultBeanDefinitionDocumentReader 类的 parseBeanDefinitions 方法

    这里从业务来说,做了两件事:

    • 注册了 AspectJAwareAdvisorAutoProxyCreator 的类,方便之后代理的扩展
    • 解析 xml<aop:config> 的配置,最终生成 AspectJPointcutAdvisorBeanDefinition 注册至 DefaultListableBeanFactoryBeanDefinitionMap

    1.1 AspectJAwareAdvisorAutoProxyCreator的注册

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 
    		if (delegate.isDefaultNamespace(root)) 
    			NodeList nl = root.getChildNodes();
    			for (int i = 0; i < nl.getLength(); i++) 
    				Node node = nl.item(i);
    				if (node instanceof Element) 
    					Element ele = (Element) node;
    					if (delegate.isDefaultNamespace(ele)) 
                       // 正常的标签,IOC中讲过
    						parseDefaultElement(ele, delegate);
    					
    					else 
                       // <AOP>、<dubbo> 类的标签
    						delegate.parseCustomElement(ele);
    					
    				
    			
    		
    	
    
    public BeanDefinition parseCustomElement(Element ele) 
        return parseCustomElement(ele, null);
    
    
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) 
        // 获取对应的命名空间
        String namespaceUri = getNamespaceURI(ele);
        
        // 根据命名空间找到对应的NamespaceHandlerspring
        //  因为我们这里处理的是 AOP 的标签,所以会有 AopNamespaceHandler 的执行
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        
        // 调用自定义的NamespaceHandler(AopNamespaceHandler)进行解析
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    
    
    public BeanDefinition parse(Element element, ParserContext parserContext) 
        // 获取元素的解析器
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return (parser != null ? parser.parse(element, parserContext) : null);
    
    
    
    public BeanDefinition parse(Element element, ParserContext parserContext) 
        CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    		parserContext.pushContainingComponent(compositeDef);
    		// 注册自动代理模式创建器,AspectjAwareAdvisorAutoProxyCreator
    		configureAutoProxyCreator(parserContext, element);
    
    
    
    private void configureAutoProxyCreator(ParserContext parserContext, Element element) 
        AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
    
    
    public static void registerAspectJAutoProxyCreatorIfNecessary( ParserContext parserContext, Element sourceElement) 
    		// 注册名为org.springframework.aop.config.internalAutoProxyCreator的beanDefinition,其中的class类为`AspectJAwareAdvisorAutoProxyCreator`,其也会被注册到bean工厂中
    		BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
    				parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    		// 如果指定proxy-target-class=true,则使用CGLIB代理,否则使用JDK代理
    		// 其实其为AspectJAwareAdvisorAutoProxyCreator类的proxyTargetClass属性
    		useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    		// 注册到spring的bean工厂中,再次校验是否已注册
    		registerComponentIfNecessary(beanDefinition, parserContext);
    	
    
    public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) 
        return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
    
    
    private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) 
           // cls = AspectJAwareAdvisorAutoProxyCreator
    		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    		beanDefinition.setSource(source);
    		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
          // key = org.springframework.aop.config.internalAutoProxyCreator
          // value = org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
          // 这里正式注册了 !!!
    		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    		return beanDefinition;
    	
    

    有读者可能疑惑,这里为什么要大费周折注册一个 AspectJAwareAdvisorAutoProxyCreator 这个类,他有什么特殊的地方嘛

    这里先卖个关子,后续我们会讲到

    1.2、AspectJPointcutAdvisor 的注册

    由于这一段的注册逻辑写的很绕、很晦涩难懂,这里博主带大家大致的浏览下流程

    不然,直接劝退了大部分人

    我们跟着上面这部分源码继续往下看:

    public BeanDefinition parse(Element element, ParserContext parserContext) 
        CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    		parserContext.pushContainingComponent(compositeDef);
    		// 注册自动代理模式创建器,AspectjAwareAdvisorAutoProxyCreator
    		configureAutoProxyCreator(parserContext, element);
          // 解析aop:config子节点下的aop:pointcut/aop:advice/aop:aspect
    		List<Element> childElts = DomUtils.getChildElements(element);
    		for (Element elt: childElts) 
    			String localName = parserContext.getDelegate().getLocalName(elt);
    			if (POINTCUT.equals(localName)) 
    				parsePointcut(elt, parserContext);
    			
    			else if (ADVISOR.equals(localName)) 
    				parseAdvisor(elt, parserContext);
    			
              // 因为我们上面是 <aop:aspect/> 的标签,会走这个业务逻辑
    			else if (ASPECT.equals(localName)) 
    				parseAspect(elt, parserContext);
    			
    		
    
    		parserContext.popAndRegisterContainingComponent();
    		return null;
    
    
    private void parseAspect(Element aspectElement, ParserContext parserContext) 
    		// <aop:aspect> id属性
    		String aspectId = aspectElement.getAttribute(ID);
    		// aop ref属性,必须配置。代表切面
    		String aspectName = aspectElement.getAttribute(REF);
    
    		try 
    			List<BeanDefinition> beanDefinitions = new ArrayList<>();
    			List<BeanReference> beanReferences = new ArrayList<>();
              
    			// 解析其下的advice节点
    			NodeList nodeList = aspectElement.getChildNodes();
    			boolean adviceFoundAlready = false;
    			for (int i = 0; i < nodeList.getLength(); i++) 
    				Node node = nodeList.item(i);
    				// 是否为advice:before/advice:after/advice:after-returning/advice:after-throwing/advice:around节点
                 // 这个就是 advice 的种类,不明白的小伙伴可以百度一下
    				if (isAdviceNode(node, parserContext)) 
    					// 校验aop:aspect必须有ref属性,否则无法对切入点进行观察操作
    					if (!adviceFoundAlready) 
    						adviceFoundAlready = true;
    						if (!StringUtils.hasText(aspectName)) 
    							return;
    						
    						beanReferences.add(new RuntimeBeanReference(aspectName));
    					
    					// 解析advice节点并注册到bean工厂中
    					AbstractBeanDefinition advisorDefinition = parseAdvice(
    							aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
    					beanDefinitions.add(advisorDefinition);
    				
    			
    
    			AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
    					aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
    			parserContext.pushContainingComponent(aspectComponentDefinition);
    
    			// 解析aop:point-cut节点并注册到bean工厂
    			List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
    			for (Element pointcutElement : pointcuts) 
    				parsePointcut(pointcutElement, parserContext);
    			
    
    			parserContext.popAndRegisterContainingComponent();
    		
    		finally 
    			this.parseState.pop();
    		
    	
    

    到这里我们先停一下,从上述源码中我们得到了一些信息,这部分源码的注册基本就三个方面:

    • 解析 Advice 节点并注册到 bean 工厂中
    • 解析 Advisor 节点注册到 bean 工厂中
    • 解析 Pointcut 节点并注册到 bean 工厂
    // 整体的注册方法
    AbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement, Spring从成神到升仙系列 一2023年再不会动态代理,就要被淘汰了

    Kafka从成神到升仙系列 五面试官问我 Kafka 生产者的网络架构,我直接开始从源码背起.......

    Kafka从成神到升仙系列 四你真的了解 Kafka 的缓存池机制嘛

    Netty 从成神到升仙系列 三Netty 凭什么成为国内最流行的网络通信框架?

    2023年再不会 IOC 源码,就要被淘汰了

    回首2022,展望2023

(c)2006-2024 SYSTEM All Rights Reserved IT常识