Spring注解驱动开发(四)

注:此笔记为尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)学习笔记,并同时参考[https://blog.csdn.net/xjhqre/article/details/123264069]博主文章,其中包含个人的笔记和理解,仅做学习笔记之用。

14、声明式事务

Spring声明式事务通过AOP(面向切面编程)来实现的,它允许开发者将事务管理的代码从业务逻辑中分离出来,以提高代码的模块化和可维护性。在Spring中,声明式事务是通过注解或XML配置来定义的,而不是直接在代码中编写事务管理的逻辑

核心概念
事务(Transaction)事务是一系列的操作,要么全部成功执行,要么全部失败回滚。在数据库中,事务通常包括一组SQL语句。

事务管理器(Transaction Manager)事务管理器负责协调事务的开始、提交和回滚。Spring支持多种事务管理器,包括JDBC事务管理器、Hibernate事务管理器、JTA事务管理器等。

切点(Pointcut)切点定义了在哪里开始事务的范围。通常,切点定义在业务层方法上,以确定哪些方法需要事务支持。

通知(Advice)通知是在切点上执行的代码,包括"前置通知"、“后置通知”、"异常通知"等。在声明式事务中,通知包括开启事务、提交事务、回滚事务等。

事务属性(Transaction Attributes)事务属性定义了事务的属性,例如隔离级别、传播行为、超时时间等。这些属性可以通过注解或XML配置来指定。

原理
事务代理Spring使用AOP在运行时生成代理对象,这些代理对象包装了被事务管理的目标对象。当目标对象的方法被调用时,代理对象将负责管理事务。

切面切面是一组通知和切点的组合。在声明式事务中,切面包含了开启事务、提交事务、回滚事务等通知,并定义了切点,确定在哪些方法上应用这些通知。

事务管理器声明式事务需要与事务管理器协同工作。在Spring中,可以配置不同的事务管理器来支持不同的数据源和事务管理策略。

事务通知在切点匹配时,相应的事务通知将被执行。通常有三种主要的事务通知:前置通知(开启事务)、后置通知(提交事务)、异常通知(回滚事务)。

14.1、环境搭建

  1. 导入相关依赖:数据源、数据库驱动、Spring-jdbc模块
  2. 配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据
14.1.1、创建数据库
DROP TABLE IF EXISTS `tbl_user`;

CREATE TABLE `tbl_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
14.1.2、编写UserDao
@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insert() {
        String sql = "INSERT INTO `tbl_user`(username,age) VALUES(?,?)";
        String username = UUID.randomUUID().toString().substring(0, 5);
        jdbcTemplate.update(sql, username, 19);
    }
}

14.1.3、编写UserService
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void insertUser() {
        userDao.insert();
        System.out.println("插入完成...");
    }
}

14.1.4、编写配置类
@Configuration
@ComponentScan("com.kdz.tx")
@PropertySource("classpath:/db.properties")
public class TxConfig implements EmbeddedValueResolverAware {

    private StringValueResolver valueResolver;

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(valueResolver.resolveStringValue("${jdbc.username}"));
        dataSource.setPassword(valueResolver.resolveStringValue("${jdbc.password}"));
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springtx?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
        dataSource.setDriverClass(valueResolver.resolveStringValue("${jdbc.driverClassName}"));
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception{
        //Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器中找组件
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
    }
}
14.1.5、测试
@Test
public void test13() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = applicationContext.getBean(UserService.class);
    userService.insertUser();
}

14.2、事务测试

14.2.1、给方法标注@Transactional

给方法上标注 @Transactional 表示当前方法是一个事务方法;

并在方法中设置异常

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional
    public void insertUser() {
        userDao.insert();
        int i = 1 / 0;
        System.out.println("插入完成...");
    }
}

只有@Transactional,不行

Spring注解驱动开发(四)_第1张图片

14.2.2、开启注解事务管理功能

在配置类标注**@EnableTransactionManagement** 开启基于注解的事务管理功能;

@Configuration // 告诉Spring这是一个配置类
@EnableTransactionManagement
@ComponentScan("com.kdz.tx")
@PropertySource("classpath:/db.properties")
public class TxConfig implements EmbeddedValueResolverAware {
    // ...
}

14.2.3、配置事务管理器

在配置类中加入事务管理器bean

//注册事务管理器在容器中
@Bean
public PlatformTransactionManager transactionManager() throws Exception{
    return new DataSourceTransactionManager(dataSource());
}
14.2.4、测试
@Test
public void test13() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = applicationContext.getBean(UserService.class);
    userService.insertUser();
}

事务测试成功

测试结果:

控制台打印除 0 异常,数据库未增加新记录

Spring注解驱动开发(四)_第2张图片

事务使用步骤

第三步:在对应需要执行事务的方法上添加事务注解@Transactional

14.3、事务源码分析

事务执行原理:

1.标注**@EnableTransactionManagement**,spring利用TransactionManagementConfigurationSelector给容器 中会导入两个组件:AutoProxyRegistrarProxyTransactionManagementConfiguration

2.AutoProxyRegistrar功能:
1)给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;该组件利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;

3.ProxyTransactionManagementConfiguration功能:
给容器中注册事务增强器:
1)事务增强器要用事务注解的信息,利用AnnotationTransactionAttributeSource解析事务注解
2)事务拦截器:TransactionInterceptor;保存了事务属性信息,事务管理器;他是一个 MethodInterceptor,在目标方法执行的时候执行拦截器链:
①先获取事务相关的属性
②再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger,最终会从容器中按照类型获取一个PlatformTransactionManager
③执行目标方法,如果异常,获取到事务管理器,利用事务管理回滚操作;如果正常,利用事务管理器,提交事务

Spring注解驱动开发(四)_第3张图片

AutoProxyRegistrar

Spring注解驱动开发(四)_第4张图片

ProxyTransactionManagementConfiguration

给容器中注册事务增强器

​ 1)事务增强器要用事务注解的信息,利用**AnnotationTransactionAttributeSource ** 解析事务注解

Spring注解驱动开发(四)_第5张图片

​ 2)事务拦截器:TransactionInterceptor保存了事务属性信息,事务管理器

Spring注解驱动开发(四)_第6张图片

他是一个 MethodInterceptor,在目标方法执行的时候执行拦截器链:

Spring注解驱动开发(四)_第7张图片

Spring注解驱动开发(四)_第8张图片

Spring注解驱动开发(四)_第9张图片

Spring注解驱动开发(四)_第10张图片

3.执行目标方法,如果异常,获取到事务管理器,利用事务管理回滚操作;如果正常,利用事务管理器,提交事务

15、扩展原理

15.1、BeanFactoryPostProcessor

BeanPostProcessor:bean后置处理器,bean创建对象初始化前后进行拦截工作的

BeanFactoryPostProcessor:beanFactory的后置处理器:

1.在BeanFactory标准初始化之后调用来定制和修改BeanFactory的内容
2.所有的bean定义已经保存加载到beanFactory但是bean的实例还未创建

15.1.1、编写Dog类
public class Dog {

    public Dog() {
        System.out.println("创建狗对象");
    }

    @PostConstruct
    public void init() {
        System.out.println("初始化狗对象");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("销毁狗对象");
    }
}
15.1.2、编写MyBeanFactoryPostProcessor

编写MyBeanFactoryPostProcessor方法,实现接口BeanFactoryPostProcessor,重写postProcessBeanFactory方法

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("执行postProcessBeanFactory");
        int count = beanFactory.getBeanDefinitionCount();
        String[] names = beanFactory.getBeanDefinitionNames();
        System.out.println("当前BeanFactory中有" + count + " 个Bean");
        System.out.println(Arrays.asList(names));
    }
}
15.1.3、编写配置类
@ComponentScan("com.kdz.ext")
@Configuration
public class ExtConfig {

    @Bean
    public Dog dog() {
        return new Dog();
    }
}
15.1.4、测试
@Test
public void test14() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
    applicationContext.close();
}

测试结果:

执行postProcessBeanFactory
当前BeanFactory中有8 个Bean
创建狗对象
初始化狗对象
销毁狗对象

debug分析

Spring注解驱动开发(四)_第11张图片

1.debug入口

Spring注解驱动开发(四)_第12张图片

Spring注解驱动开发(四)_第13张图片

2.具体过程

Spring注解驱动开发(四)_第14张图片

Spring注解驱动开发(四)_第15张图片

3.BeanFactoryPostProcessor原理

1.ioc容器创建对象
2.执行refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory);
3.直接在BeanFactory中找到所有类型是BeanFactoryPostProcessor的组件,并执行他们的方法
4.在初始化创建其他组件前面执行

15.2、BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor 继承BeanFactoryPostProcessor

其优先于BeanFactoryPostProcessor执行在所有bean定义信息将要被加载,bean实例还未创建的时候执行

利用BeanDefinitionRegistryPostProcessor给容器中再额外添加一些组件

BeanDefinitionRegistryPostProcessor执行流程

1.ioc创建对象
2.refresh() --> invokeBeanFactoryPostProcessors(beanFactory);
3.从容器中获取到所有的BeanDefinitionRegistryPostProcessor组件
1)依次触发所有的postProcessBeanDefinitionRegistry()方法
2)再来触发postProcessBeanFactory()方法
4.再来从容器中找到BeanFactoryPostProcessor组件;然后依次触发postProcessBeanFactory()方法

15.2.1、编写MyBeanDefinitionRegistryPostProcessor

其他配置类和测试类同上

Spring注解驱动开发(四)_第16张图片

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("执行MyBeanDefinitionRegistryPostProcessor...bean的数量:" + beanFactory.getBeanDefinitionCount());
    }

    //BeanDefinitionRegistry Bean定义信息的保存中心,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实例;
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println("执行postProcessBeanDefinitionRegistry...bean的数量:" + registry.getBeanDefinitionCount());
        // 手动注册bean对象
        // 方法一:RootBeanDefinition beanDefinition = new RootBeanDefinition(Cat.class);
        // 方法二:
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
        registry.registerBeanDefinition("car", beanDefinition);
   }
}
15.2.2、编写Cat类
public class Cat implements InitializingBean, DisposableBean {

    public Cat() {
        System.out.println("创建猫对象");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("销毁猫对象");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化猫对象");
    }
}
15.2.3、测试

测试结果:

执行postProcessBeanDefinitionRegistry…bean的数量:9
执行MyBeanDefinitionRegistryPostProcessor…bean的数量:10
执行postProcessBeanFactory
当前BeanFactory中有10 个Bean
创建狗对象
初始化狗对象
创建猫对象
初始化猫对象
销毁猫对象
销毁狗对象

结果分析:

先执行MyBeanDefinitionRegistryPostProcessor里的postProcessBeanDefinitionRegistry,在输出bean对象的数量后又创建了一个Cat类的bean
在执行MyBeanDefinitionRegistryPostProcessor里的postProcessBeanFactory方法。输出的bean数量加1
然后执行MyBeanFactoryPostProcessor里的postProcessBeanFactory方法
以上方法都执行完成后开始创建bean对象

注意方法的执行顺序

Spring注解驱动开发(四)_第17张图片

debug分析

为什么BeanDefinitionRegistryPostProcessor会比BeanFactoryPostProcessor先执行???

1.debug入口

Spring注解驱动开发(四)_第18张图片

Spring注解驱动开发(四)_第19张图片

Spring注解驱动开发(四)_第20张图片

Spring注解驱动开发(四)_第21张图片

Spring注解驱动开发(四)_第22张图片

16、事件监听

ApplicationListener:监听容器中发布的事件。事件驱动模型开发;

我们需要自己写一个监听器,该监听器必须实现ApplicationListener接口,用于监听ApplicationEvent 及其下面的子事件;

步骤

1.写一个监听器(ApplicationListener实现类)来监听某个事件(ApplicationEvent及其子类)
2.把监听器加入到容器
3.只要容器中有相关事件的发布,我们就能监听到这个事件;
1)ContextRefreshedEvent:容器刷新完成(所有bean都完全创建)会发布这个事件;
2)ContextClosedEvent:关闭容器会发布这个事件;
4.发布一个事件
applicationContext.publishEvent()

16.1、编写监听器

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {

    //当容器中发布此事件以后,方法触发
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // TODO Auto-generated method stub
        System.out.println("收到事件:" + event);
   }
}

16.2、测试

@Test
public void test14() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
    applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
    });
    applicationContext.close();
}

测试结果:

收到事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:04:16 CST 2022]
收到事件:SpringTest$1[source=我发布的事件]
收到事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:04:16 CST 2022]

Spring注解驱动开发(四)_第23张图片

16.3、源码分析

上面结果分析:

Spring注解驱动开发(四)_第24张图片

debug分析
1.debug入口:

Spring注解驱动开发(四)_第25张图片

事件发布顺序
  1. ContextRefreshedEvent事件
    1. 容器创建对象refresh()
    2. finishRefresh();容器刷新完成会发布ContextRefreshedEvent事件
  2. 自己发布的事件执行
  3. 容器关闭会发布ContextClosedEvent
事件发布流程

1.容器创建对象:refresh()
2.finishRefresh();
3.publishEvent(new ContextRefreshedEvent(this));
1)获取事件的多播器(派发器):getApplicationEventMulticaster()
2)multicastEvent派发事件
3)获取到所有的ApplicationListener;for (final ApplicationListener listener : getApplicationListeners(event, type))
①如果有Executor,可以支持使用Executor进行异步派发;
②否则,同步的方式直接执行listener方法;invokeListener(listener, event);拿到listener回调onApplicationEvent方法;

事件多播器(派发器)创建流程

1.容器创建对象:refresh();
2.initApplicationEventMulticaster();初始化ApplicationEventMulticaster;
1.先去容器中找有没有id=“applicationEventMulticaster”的组件;
2.如果没有则直接创建this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);并且加入到容器中,我们就可以在其他组件要派发事件自动注入到派发器applicationEventMulticaster中

监听器创建流程

1.容器创建对象:refresh();
2**.registerListeners()**;从容器中拿到所有的监听器,把他们注册到applicationEventMulticaster中;
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
将监听器listener注册到派发器ApplicationEventMulticaster中:getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);

16.4、@EventListener

通过@EventListener注解,我们也可以用来监听事件

16.4.1、编写UserService类
@Service
public class UserService {

    @EventListener(classes = {ApplicationEvent.class})
    public void listen(ApplicationEvent event) {
        System.out.println("UserService。。监听到的事件:" + event);
    }
}
16.4.2、测试
@Test
public void test14() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
    applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
    });
    applicationContext.close();
}

测试结果:

UserService监听到的事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:37:36 CST 2022]
UserService监听到的事件:SpringTest$1[source=我发布的事件]
UserService监听到的事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:37:36 CST 2022]

Spring注解驱动开发(四)_第26张图片

16.4.3、源码分析

spring使用EventListenerMethodProcessor处理器来解析方法上的@EventListener;其实现了SmartInitializingSingleton接口

SmartInitializingSingleton原理

​ 1.ioc容器创建对象并refresh();
​ 2.finishBeanFactoryInitialization(beanFactory);初始化剩下的单实例bean;
​ 1.先创建所有的单实例bean:getBean();
​ 2.获取所有创建好的单实例bean,判断是否是SmartInitializingSingleton类型的;如果是就调用afterSingletonsInstantiated();

Spring注解驱动开发(四)_第27张图片

Spring注解驱动开发(四)的学习笔记到此完结,笔者归纳、创作不易,大佬们给个3连再起飞吧

你可能感兴趣的:(Spring,spring,数据库,java)