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、创建数据库
sql 复制代码
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
java 复制代码
@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
java 复制代码
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void insertUser() {
        userDao.insert();
        System.out.println("插入完成...");
    }
}
14.1.4、编写配置类
java 复制代码
@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、测试
java 复制代码
@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 表示当前方法是一个事务方法;

并在方法中设置异常

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

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

只有@Transactional,不行

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

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

java 复制代码
@Configuration // 告诉Spring这是一个配置类
@EnableTransactionManagement
@ComponentScan("com.kdz.tx")
@PropertySource("classpath:/db.properties")
public class TxConfig implements EmbeddedValueResolverAware {
    // ...
}
14.2.3、配置事务管理器

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

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

事务测试成功

测试结果:

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

事务使用步骤

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

14.3、事务源码分析

事务执行原理:

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

2.AutoProxyRegistrar功能:

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

3.ProxyTransactionManagementConfiguration功能:

给容器中注册事务增强器:

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

2)事务拦截器:TransactionInterceptor ;保存了事务属性信息,事务管理器;他是一个 MethodInterceptor ,在目标方法执行的时候执行拦截器链:

①先获取事务相关的属性

②再获取PlatformTransactionManager ,如果事先没有添加指定任何transactionmanger ,最终会从容器中按照类型获取一个PlatformTransactionManager

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

AutoProxyRegistrar

ProxyTransactionManagementConfiguration

给容器中注册事务增强器

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

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

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

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

15、扩展原理

15.1、BeanFactoryPostProcessor

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

BeanFactoryPostProcessor:beanFactory的后置处理器:

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

15.1.1、编写Dog类
java 复制代码
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方法

java 复制代码
@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、编写配置类
java 复制代码
@ComponentScan("com.kdz.ext")
@Configuration
public class ExtConfig {

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

测试结果:

执行postProcessBeanFactory

当前BeanFactory中有8 个Bean

创建狗对象

初始化狗对象

销毁狗对象

debug分析

1.debug入口

2.具体过程

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

其他配置类和测试类同上

java 复制代码
@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类
java 复制代码
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对象

注意方法的执行顺序

debug分析

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

1.debug入口

16、事件监听

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

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

步骤

1.写一个监听器(ApplicationListener实现类)来监听某个事件(ApplicationEvent及其子类)

2.把监听器加入到容器

3.只要容器中有相关事件的发布,我们就能监听 到这个事件;

1)ContextRefreshedEvent :容器刷新完成(所有bean都完全创建)会发布这个事件;

2)ContextClosedEvent :关闭容器会发布这个事件;

4.发布一个事件
applicationContext.publishEvent()

16.1、编写监听器

java 复制代码
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {

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

16.2、测试

java 复制代码
@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]

16.3、源码分析

上面结果分析:

debug分析
1.debug入口:
事件发布顺序
  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类
java 复制代码
@Service
public class UserService {

    @EventListener(classes = {ApplicationEvent.class})
    public void listen(ApplicationEvent event) {
        System.out.println("UserService。。监听到的事件:" + event);
    }
}
16.4.2、测试
java 复制代码
@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]

16.4.3、源码分析

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

SmartInitializingSingleton原理

​ 1.ioc容器创建对象并refresh();

​ 2.finishBeanFactoryInitialization(beanFactory);初始化剩下的单实例bean;

​ 1.先创建所有的单实例bean:getBean();

​ 2.获取所有创建好的单实例bean,判断是否是SmartInitializingSingleton类型的;如果是就调用afterSingletonsInstantiated();

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

相关推荐
jnrjian几秒前
export rman 备份会占用buff/cache 导致内存压力
数据库·oracle
蜗牛^^O^28 分钟前
Docker和K8S
java·docker·kubernetes
从心归零1 小时前
sshj使用代理连接服务器
java·服务器·sshj
isNotNullX1 小时前
一文解读OLAP的工具和应用软件
大数据·数据库·etl
一个诺诺前行的后端程序员1 小时前
springcloud微服务实战<1>
spring·spring cloud·微服务
IT毕设梦工厂2 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
小诸葛的博客2 小时前
pg入门1——使用容器启动一个pg
数据库
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe3 小时前
分布式系统实战经验
java·分布式