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

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


目录
相关文章
|
15天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
45 2
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
24天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
39 1
Spring高手之路24——事务类型及传播行为实战指南
|
17天前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
30 3
|
21天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
45 9
|
2月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
120 5
|
2月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
2月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
47 0
|
7月前
|
安全 Java 应用服务中间件
阿里技术官架构使用总结:Spring+MyBatis源码+Tomcat架构解析等
分享Java技术文以及学习经验也有一段时间了,实际上作为程序员,我们都清楚学习的重要性,毕竟时代在发展,互联网之下,稍有一些落后可能就会被淘汰掉,因此我们需要不断去审视自己,通过学习来让自己得到相应的提升。
|
7月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
98 1