hitechr
2022/08/30阅读:45主题:极客黑
Spring的事务管理
基于AOP实现一个自己的事务
假如有这样一个需求,在spring中实现
@Hitx
的事务支持。
AOP的代理过程
AOP会用到AnnotationAwareAspectJAutoProxyCreator类去解析方法上的注解,然后生成AspectJExpressionPointcut和方法生成Advice,然后再有Advice生成一个Advisor,在创建对象的过程中,对实例进行生成代理,添加回调。
分析
SpringAOP的三要素: Advicie、Pointcut、Advisor。
-
Advice
: 定义拦截到方法后要执行的逻辑 -
Pointcut
:拦截方法的表达式定义 -
Advisor
:spring中aop统一对外提供的增强器
有了这三要素后,还会往spring容器中注册代理类的生成器,可以使用AopConfigUtils
这个工具类来注册。
实现步骤
定义Hitx注解
首先定义一个自定义的事务注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Hitx {}
定义Advice
然后定义自己的一个Advice通知器,用于在拦截到service方法时进行事务的开启、回滚、提交的过程;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class HitxMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//TODO 待完善
return null;
}
}
定义Pointcut
有了Advice后就得开始定义一个切面拦截,这个类实现了Pointcut
、ClassFilter
、MethodMatcher
,也就是说这个实例对象即可以判断类是否满足条件、也可以判断某个方法是否满足条件。
public class HitxPointcut implements Pointcut,ClassFilter,MethodMatcher {
@Override
public ClassFilter getClassFilter() {
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
/**
* 当前类是否匹配
*/
@Override
public boolean matches(Class<?> clazz) {
for (Method method : clazz.getMethods()) {
Hitx hitx = AnnotationUtils.findAnnotation(method, Hitx.class);
if (hitx!=null) {
return true;
}
}
return false;
}
/**
* 当前方法是否匹配
* @return
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
Hitx hitx = AnnotationUtils.findAnnotation(method, Hitx.class);
if (hitx!=null) {
return true;
}
return false;
}
@Override
public boolean isRuntime() {
return true;
}
/**
* 当前类,方法,参数 是否匹配
* @return
*/
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
Hitx hitx = AnnotationUtils.findAnnotation(method, Hitx.class);
if (hitx!=null) {
return true;
}
return false;
}
}
定义Advisor
开始定义Advisor对象
public class HitxAdvisor extends AbstractPointcutAdvisor {
Advice advice;
Pointcut pointcut;
//从外部定义时获取
public HitxAdvisor(Advice advice, Pointcut pointcut) {
this.advice = advice;
this.pointcut = pointcut;
}
//返回pointcut对象
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
//返回advice对象
@Override
public Advice getAdvice() {
return this.advice;
}
//上面的这两个方法都是AbstractPointcutAdvisor中的抽象方法
}
然后把我们的三要素定义在配置中
@Configuration
public class HitxConfiguration {
@Bean
public Pointcut pointcut(){
return new HitxPointcut();
}
@Bean
public MethodInterceptor methodInterceptor(){
return new HitxMethodInterceptor();
}
@Bean
public Advisor advisor(Pointcut pointcut,MethodInterceptor methodInterceptor){
return new HitxAdvisor(methodInterceptor,pointcut);
}
}
开启自动代理
定义一个BeanFactoryProcessor来往spring窗口中注册一个扩展器,只需要在配置类中引入EnableAspectJAutoProxy
注解。
@Configuration
@EnableAspectJAutoProxy
public class HitxConfiguration {
@Bean
public Pointcut pointcut(){
return new HitxPointcut();
}
@Bean
public MethodInterceptor methodInterceptor(DataSource dataSource){
return new HitxMethodInterceptor(dataSource);
}
@Bean
public Advisor advisor(Pointcut pointcut,MethodInterceptor methodInterceptor){
return new HitxAdvisor(methodInterceptor,pointcut);
}
}
完善Advice
然后再扩展下HitxMethodInterceptor
,在这个实现里我们从连接池中拿到连接,放到线程上下文中,业务类中同样从线程上下文中获取,用完后由拦截器类统一处理。
public class HitxMethodInterceptor implements MethodInterceptor {
DataSource dataSource;
public HitxMethodInterceptor(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Connection connection = dataSource.getConnection();
try {
System.out.println("事务开启");
connection.setAutoCommit(false);
ThreadLocalContext.put(connection);
return mi.proceed();
} catch (Throwable e) {
System.out.println("事务回退");
connection.rollback();
connection.close();
}finally {
ThreadLocalContext.remove();
if (!connection.isClosed()) {
System.out.println("事务提交");
connection.commit();
connection.close();
}
}
return null;
}
}
因为spring的aop调用是基于链式调用的,当处理到自定义的拦截器类时,只需要调回链上即可mi.proceed()
线程上下文工具类
public class ThreadLocalContext {
public static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
public static void put(Connection connection){
threadLocal.set(connection);
}
public static Connection get(){
return threadLocal.get();
}
public static void remove() {
threadLocal.remove();
}
}
业务代码
service
@Component
public class BookService {
@Hitx
public void save(){
Connection connection = ThreadLocalContext.get();
try {
String s = "insert INTO `book`(`name`, `price`, `reserve`, `version`) VALUES ('设计模式', 12, 10, 0)";
PreparedStatement preparedStatement = connection.prepareStatement(s);
preparedStatement.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Hitx
public void saveRollback(){
Connection connection = ThreadLocalContext.get();
try {
String s = "insert INTO `book`(`name`, `price`, `reserve`, `version`) VALUES ('设计模式', 12, 10, 0)";
PreparedStatement preparedStatement = connection.prepareStatement(s);
preparedStatement.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
System.out.println(1/0);
}
}
配置连接池
@Configurable
@Component
public class TXConfiguration {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("*****");
dataSource.setPassword("*****");
dataSource.setUrl("jdbc:mysql://******.rds.aliyuncs.com:3306/books");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setDefaultAutoCommit(false);
return dataSource;
}
}
测试
@Test
public void testInsert(){
String basepackage="org.hitechr.spring.transaction";
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(basepackage);
BookService bookService = (BookService) context.getBean("bookService");
bookService.save();
bookService.saveRollback();
}


事务管理器

[地址](https://www.yuque.com/docs/share/95d47f4b-e5a6-458b-a0c3-01eb087e9eaf?# 《spring事务准备过程》)
当然,在实际的开发过程中,我们并不会这样自己去实现这么多的东西,只需要在项目的配置类中引入@EnableTransactionManager
即可实现事务的自动开启,spring会帮我们把上面的所有步骤都给做完。
准备阶段
此阶段主要是定义Advisor、MethodInterceptor、TransactionAttributeSource。
首先引入@EnableTransactionManager
,其实是导入TransactionManagementConfigurationSelector
,它是一个实现了ImportSelector
的类,所以再解析TransactionMangementConfigurationSelector时调用其getImports方法,返回两个配置类:AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
。
ProxyTransactionManagementConfiguration
中对Advisor
、TransactionAttributeSource
、TransactionInterceptor
进行了定义。
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
//
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
TransactionInterceptor
: 定义一个TransactionInterceptor,其实就是MethodInterceptor的实现类,与我们自己定义的HitxMethodInterceptor的功能是一样的,在这个类中对事务进行开启、回退等操作。
BeanFactoryTransactionAttributeSourceAdvisor
: 与我们定义的HitxAdvisor是一样的。
就剩一个TransactionAttributeSource我们之前没有去定义,TransactionAttributeSource定义后发现在Advisor中有使用,

我们是直接定义了Pointcut这么一个类,而在spring中他是通过TransactionAttributeSource来间接定义Pointcut的。


实例化过程
-
首先实例化的是代理的创建对象 InfrastructAdvisorAutoProxyCreator
,在spring启动过程中的registerBeanPostProcessors的方法里。

-
然后开始创建所有的单例对象,调用初始化方法里对新创建的对象进行代理的包装,当第一次调用InfrastructAdvisorAutoProxyCreator的postProcessorAfterInstation方法时对Advisor对象进行了创建。

-
所有实例化后的对象都要经过代理类的一个包装过程,在这个包装过程中会从容器中去获取所有的Advisor对象,然后再筛选出适合的Advisor。


-
在获取Advisor对象时,因为第一次调用所有的对象还没有被创建,所以会先去创建Advisor对象( BeanFactoryTransactionAttributeSouceAdvisor
),因为这个对象依赖TransactionAttributeSource
和TransactionInterceptor
所以得先去创建这两个对象。


-
BeanFactoryTransactionAttributeSourceAdvisor对象创建完后,还要调用
findAdvisorsThatCanApply
方法,筛选出适合的Advisor集合public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
//判断当前类是否满足条件
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
//判断方法是否为MethodMatcher.TRUE
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
//获取类上所有的接口
Set<Class<?>> classes = new LinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
//遍历每个接口中每个方法是否能匹配
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
筛选Advisor的过程
-
Pointcut是通过Advisor调用getPoint返回的一个内部类

private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
@Nullable
protected TransactionAttributeSource getTransactionAttributeSource() {
return transactionAttributeSource;
}
};
public Pointcut getPointcut() {
return this.pointcut;
}
-
通过pointcut获取方法匹配对象MethodMatcher,也就是当前的Pointcut,然后调用获取类的所有方法进行遍历匹配

-
是否能获取方法上的属性Transaction注解。先拿到TransactionAttributeSource对象,然后通过这个对象去获取方法上的注解。
public boolean matches(Method method, Class<?> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
} -
在getTransactionAttribute中通过computeTransactionAttribute返回方法上的注解

-
在提取方法上属性的过程中使用提交定义好的解析器进行解析的


-
这个方法会去查询方法上的@Transaction注解,然后获取属性,封装为TransactionAttribute对象进行返回

-
拿到Advisor后就可以去创建一个代理对象了,与AOP创建代理对象的过程一样。
总结
-
spring事务需要三要素对象
BeanFactoryTransactionAttributeSourceAdvisor
,TransactionAttributeSource
,TransactionInterceptor
-
还需要一个代理生成对象
InfrastructureAdvisorAutoProxyCreator
-
在生成一个对象后,对其进行代理时,在InfrastructureAdvisorAutoProxyCreator#postprocessorAfterInitialization中首先去创建事务的三要素对象
-
然后在去筛选适合的Advisor集合,判断类是否满足条件,然后再去遍历类中每个方法
-
在对方法进行匹配时,通过pointcut获取到TransactionAttributeSource对象,通过这个对象去获取方法上的@Transaction注解信息,注解信息封装为TransactionAttribute对象,如果有对象则表示匹配,只要有一个方法匹配,当前类就满足被代理的条件。
作者介绍