初步走入 Spring 事务底层核心前置源码(下)

简介: 初步走入 Spring 事务底层核心前置源码(下)

基于注解方式事务处理

@EnableTransactionManagement 注解:import 一个 selector 类->TransactionManagementConfigurationSelector

protected String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
        case PROXY:
            return new String[] {AutoProxyRegistrar.class.getName(),
                             ProxyTransactionManagementConfiguration.class.getName()};
        case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
        default:
            return null;
    }
}

adviceMode 默认为 AdviceMode.PROXY,所以会创建 AutoProxyRegistrar、ProxyTransactionManagementConfiguration 两个类

  • AutoProxyRegistrar:主要是注册 InfrastructureAdvisorAutoProxyCreator 类型的 bean 定义信息
  • ProxyTransactionManagementConfiguration:主要是为了创建事务管理器所需要的 advisor、事务属性对象、事务拦截器对象,本身是一个被 @Configuration 注解的修饰的类,其下有三个 @Bean 修饰的实例,都被标识为 Spring 内部使用的 bean 对象,创建的优先级为:事务属性—>事务拦截器—>advisor,事务拦截器对象依赖于事务属性对象,advisor 依赖于事务属性对象和事务拦截器对象.
  • 在执行 refresh 方法时,会调用 invokeBeanFactoryPostProcessors,先实例化 spring 内部的 BFPP 后执行相关的处理方法

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry「扫描 @ComponentScan、@Configuration、@Import 注解,注册相关 bean 定义信息」

ConfigurationClassPostProcessor#postProcessBeanFactory:enhanceConfigurationClasses 增强所有 @Configuration 修饰的配置类,生成代理类,为了确保其下的 @Bean 方法是能够保持单例的、添加一个新的 ImportAwareBeanPostProcessor,BPP 后置处理类

  • 调用 refresh#registerBeanPostProcessors 时,将所有后续要用到的 BPP 全部先进行实例化,如:InfrastructureAdvisorAutoProxyCreator
  • 最终是实例化整个 Bean 过程:在创建真正需要事务增强的代理对象时会进行验证匹配类或方法上是否有 @Transactional 注解,有则解析方法上相关的 @Transcation 注解,解析完后创建事务属性对象 RuleBasedTransactionAttribute

验证类只需要判断当前的类或者注解是否以 java. 开头,如果是则无法匹配

验证方法解析方法头上是否有 @Transactional 注解 修饰,如果没有则无法匹配

XML、注解方式对比

上图是 XML 和注解方式对象的对比图,两种 advisor 都是 AbstractPointcutAdvisor 子类,两种 AttributeSource 都属于 TransactionAttributeSource 接口的实现类,最后区分使用动态代理创建器的不同场景

  • XML 配置使用的创建器为 AspectJAwareAdvisorAutoProxyCreator
  • XML 配置自动扫描注解时使用的创建器为 AnnotationAwareAspectJAutoProxyCreator
  • @EnableTransactionManagement 注解使用的创建器为 InfrastructureAdvisorAutoProxyCreator,Spring 中事务默认的创建代理对象的类

代码

jdbc.username=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/demo
jdbc.driverClassName=com.mysql.jdbc.Driver
public class BookDao {
    // @Autowired
    JdbcTemplate jdbcTemplate;
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    /**
     * 减库存,减去某本书的库存
     * @param id
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateStock(int id){
        String sql = "update book_stock set stock=stock-1 where id=?";
        jdbcTemplate.update(sql,id);
        for (int i = 1 ;i>=0 ;i--)
            System.out.println(10/i);
    }
}
public class BookService {
    @Autowired
    BookDao bookDao;
    public BookDao getBookDao() {
        return bookDao;
    }
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    /**
     * 结账:传入哪个用户买了哪本书
     * @param username
     * @param id
     */
//    @Transactional(propagation = Propagation.REQUIRED)
    public void checkout(String username,int id){
        bookDao.updateStock(id);
        int price = bookDao.getPrice(id);
        bookDao.updateBalance(username,price);
//        try{
//        for (int i = 1 ;i>=0 ;i--)
//            System.out.println(10/i);
//        }catch (Exception e){
//            System.out.println("...............");
//        }
    }
}
@Configuration
@PropertySource("classpath:dbconfig.properties")
@EnableTransactionManagement
public class TransactionConfig {
    @Value("${jdbc.driverClassName}")
    private String driverClassname;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource() {
        DruidDataSource data = new DruidDataSource();
        data.setDriverClassName(driverClassname);
        data.setUrl(url);
        data.setUsername(username);
        data.setPassword(password);
        return data;
    }
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    @Bean
    public BookDao bookDao() {
        return new BookDao();
    }
    @Bean
    public BookService bookService() {
        bookDao();
        return new BookService();
    }
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
public class TransactionTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(TransactionConfig.class);
        applicationContext.refresh();
//        BookService bean = applicationContext.getBean(BookService.class);
//        bean.checkout("zhangsan",1);
        BookDao bean = applicationContext.getBean(BookDao.class);
        bean.test();
    }
}

@Configuration、@Bean、@Component 之间的关系

public class A {
}
public class B {
}
@Component
public class TestComponent {
    @Bean
    public A a() {
        return new A();
    }
    @Bean
    public B b() {
        A a1 = a();
        A a2 = a();
        System.out.println("test is equals:" + (a1 == a2));//false
        return new B();
    }
}
@Configuration
public class TestConfiguration {
    @Bean
    public A a() {
        return new A();
    }
    @Bean
    public B b() {
        A a1 = a();
        A a2 = a();
        System.out.println("test is equals:" + (a1 == a2));//true
        return new B();
    }
}
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(TestConfiguration.class);
        applicationContext.refresh();
    }
}

@Bean 配合 @Component 注解一起使用的话,@Bean 标注的方法 bean 不会是单例的,因为它没有经过任何的拦截处理或者说没有什么特殊的方式可以让它直接从一级缓存中去取用对象

@Bean 配合 @Configuration 注解一起使用的话,@Bean 标注的 bean 在每次进行调用时都会是单例的对象,因为在执行 BFPP 方法时对所有的 @Configuration 类都生成了 CGLIB 代理子类,并且为其设置了两个相关的 Callback 回调拦截器

  • BeanFactoryAwareMethodInterceptor:针对 @Configuration 标注的类实现了 BeanFactoryAware 接口类进行拦截
  • BeanMethodInterceptor:针对 @Configuration 标注的类下的 @Bean 方法获取进行拦截
  • 在每次调用 @Configuration 标注类下的 @Bean 方法时都会调用 BeanMethodInterceptor#intercept 进行处理
// BeanMethodInterceptor 拦截方法
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,MethodProxy cglibMethodProxy) throws Throwable {
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
    // Determine whether this bean is a scoped-proxy
    if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
        }
    }
    // 首先检查当前 @Bean 是否为 factoryBean,如果是则创建一个代理子类,截取调用子类对象并返回任何缓存 bean 实例
    if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
        factoryContainsBean(beanFactory, beanName)) {
        Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
        if (factoryBean instanceof ScopedProxyFactoryBean) {
            // Scoped proxy factory beans are a special case and should not be further proxied
        }
        else {
            // It is a candidate FactoryBean - go ahead with enhancement
            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
        }
    }
  // 如果正在创建的方法等于当前的方法
    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
        if (logger.isInfoEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
        }
        // 实际调用方法的超级实现来创建 bean 实例
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    }
  // 调用 beanFactory.getBean(beanName) 找到对应的 bean 实例 
    return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

为了确保 @Bean 标注的实例是单例的,一般配合 @Configuration 注解一起使用

总结

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


vnjohn
+关注
目录
打赏
0
0
0
0
242
分享
相关文章
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
40 0
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
90 0
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
27 0
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
167 70
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
161 7
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
62 9
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等