基于注解方式事务处理
@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,后续会有更多实战、源码、架构干货分享!
大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!