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的事务
我们在日常生产项目中,项目由 Controller
、Serivce
、Dao
三层进行构建。
我们从上图中可以了解到:
对于 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
实现的,我们之前已经很详细的讲过 IOC
和 AOP
的源码实现了,这次带大家一起过一遍事务即可。
因为从博主本身而言,我感觉 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的功能,了解过切面编程
本文前置知识:
- Spring-IOC源码剖析(必看)
- Spring-代理源码剖析(必看)
如果你都已经满足,那么跟我一起了解一下抽象的
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"/>
将
Advice
和Pointcut
结合起来使用我们看下源码的形式:
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>
的配置,最终生成AspectJPointcutAdvisor
的BeanDefinition
注册至DefaultListableBeanFactory
的BeanDefinitionMap
中
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 的缓存池机制嘛