初步走入 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,后续会有更多实战、源码、架构干货分享!

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


目录
相关文章
|
3月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
9月前
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
4月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
9月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
228 0
|
9月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1159 0
|
9月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
243 0
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
514 70
|
7月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
387 10