Spring框架(一) 底层核心原理解析
本系列文章以spring-framework-5.3.10为例 , 本篇文章的目的就是使各位读者能在使用Spring的基础上对Spring的一些比较核心的内容有一个大概的认识,并不是特别全面,会在后续的文章中一一讲解,不仅仅是停留在Spring简单的使用 , 而是方便后面源码的阅读以及实现方式的理解 , 文章仅是作者自己在学习Spring过程中的案例演示以及知识总结 , 如果表达不当 , 还请及时
感兴趣的话大家可以关注一下公众号 : 猿人刘先生 , 欢迎大家一起学习 , 一起进步 , 一起来交流吧!
说明
本系列文章以spring-framework-5.3.10为例 , 本篇文章的目的就是使各位读者能在使用Spring的基础上对Spring的一些比较核心的内容有一个大概的认识,并不是特别全面,会在后续的文章中一一讲解,不仅仅是停留在Spring简单的使用 , 而是方便后面源码的阅读以及实现方式的理解 , 文章仅是作者自己在学习Spring过程中的案例演示以及知识总结 , 如果表达不当 , 还请及时指教
Spring的IOC(控制反转)和DI(依赖注入)
首先我们来看一段代码
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("Spring.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
System.out.println("userService..." + userService);
}
APPConfig代码
@ComponentScan("com.lyh")
public class AppConfig{
}
从代码实现方式不难看出 , ClassPathXmlApplicationContext是用来加载xml配置文件的Context,而AnnotationConfigApplicationContext是用来加载注解配置的Context。虽然直接父类不同,但是都有一个共同的祖先类AbstractApplicationContext。以及同样有一个对象BeanFactory提供容器作用。
但是用ClassPathXmlApplicationContext其实已经过时了,在新版的Spring MVC和Spring Boot的底层主要用的都是AnnotationConfigApplicationContext
Spring容器创建完之后 , 接下来就是从Spring容器中getBean() , 那么这个bean一定是Spring容器帮我们创建的 , 这也就是Spring容器的一个核心: IOC(控制反转)
IOC(控制反转) : 将创建对象及生命周期的管理交给Spring容器 , 在开发中 , 我们不需要关注对象的创建以及生命周期的管理 , 而是由Spring来提供 , 这个由Spring来管理创建对象以及生命周期的机制就称为控制反转 , 也就是IOC
那么我们getBean()得到的对象和我们直接new UserService()得到的对象有什么不同呢?我们用代码举个例子
@Component
public class OrderService {
@Autowired
private UserService userService;
}
@Component
public class UserService {
}
现在我们有两个类 , 一个是OrderServer , 一个是UserServer , 通过@Autowired将UserServer 注入到OrderServer 中, 那么通过getBean()拿到OrderServer 这个对象 , 它的UserServer属性一定是有值的 , 而通过new OrderServer()的方式拿到的对象 , UserServer的值一定是空的 , 所以区别也就显而易见了
通过getBean()拿到的对象 , 它的属性是是有值的 , 而直接new出来的对象, 它的属性就是空的
而给属性赋值的过程就可以称为DI(依赖注入)
Spring是如何创建这个对象的
其实通过上面的代码示例可以稍微进行推测 , 其实不管是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,目前,我们都可以简单的将它们理解为就是用来创建Java对象的容器,比如调用getBean()就可能去创建对象 , 但是也有可能不创建 , 在Java语言中,肯定是根据某个类来创建一个对象的
当我们调用context.getBean(“userService”)时,就会去创建一个对象,但是getBean方法内部怎么知道"userService"对应的是UserService类呢?
我们可以分析出来当new AnnotationConfigApplicationContext(AppConfig.class)的时候, 他就会大概进行一些操作
- 解析AppConfig , 然后得到扫描路径
- 遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String, Class>。(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap,后续文章会有说明)
- Spring会根据某个规则生成当前类对应的beanName,作为key存入Map,当前类作为value
然后我们getBean()的时候就可以根据"userService"找到UserService类,从而就可以去创建对象了
Bean的创建过程
通过以上示例代码我们大概可以看出一个简单的过程就是这样
1.找到需要创建的类(扫描)
2.通过默认的无参构造方法得到一个对象(但是如何一个类中有多个构造方法,Spring则会进行选择,这个叫做推断构造方法)
3. 给加了@Autowired等注解的属性进行赋值(依赖注入)
4. …
5. 然后得到一个bean
一个比较完整的流程大致如下
1.找到需要创建的类(扫描)
2.通过默认的无参构造方法得到一个对象(但是如何一个类中有多个构造方法,Spring则会进行选择,这个叫做推断构造方法)
3.给加了@Autowired等注解的属性进行赋值(依赖注入)
4.依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调)5.Aware回调后,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring会调用当前对象的此方法(初始化前)
6.紧接着,Spring会判断该对象是否实现了InitializingBean接口,如果实现了,就表示当前对象必须实现该接口中的afterPropertiesSet()方法,那Spring就会调用当前对象中的afterPropertiesSet()方法(初始化)
7.最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化后)
那么Bean对象创建出来之后
1.如果当前Bean是单例Bean,那么会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象。这样下次getBean时就可以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就是单例池)
2.如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次getBean时会再次执行上述创建过程,得到一个新的Bean对象
如图所示
上述Bean生命周期并不是完整的 , 它中间还有好多 , 后面的文章再慢慢详解, 接下来把上述步骤做一个说明
推断构造方法
什么是推断构造方法?
Spring需要根据某一个类的构造方法得到一个普通对象 , 它首先得判断它使用哪个构造方法以及需要什么参数 , 下面用一些例子来进行演示
例1:如果有一个无参构造和一个有参构造 , 那么Spring会使用哪个构造方法?
代码如下:
@Component
public class UserService {
@Autowired
private OrderService orderService;
public UserService() {
System.out.println("无参构造");
}
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("有参构造");
}
}
运行之后可以发现 , Spring使用的是无参构造方法
例2:如果有两个有参构造呢?
@Component
public class UserService {
@Autowired
private OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("有参构造1");
}
public UserService(OrderService orderService , OrderService orderService1) {
this.orderService = orderService;
System.out.println("有参构造2");
}
}
直接就是运行报错 , 报错信息如下 , 那么为什么会运行报错呢?不妨把有参构造注释掉一个
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.lyh.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.lyh.service.UserService.()
@Component
public class UserService {
@Autowired
private OrderService orderService;
// public UserService(OrderService orderService) {
// this.orderService = orderService;
// System.out.println("有参构造1");
// }
public UserService(OrderService orderService , OrderService orderService1) {
this.orderService = orderService;
System.out.println("有参构造2");
}
}
运行之后发现 , 没有报错 , 首先证明了一点, 这样写是没有问题的 , 那么想一下 , Spring要通过构造方法去创建对象 , 但是现在有两个 , 那么到底用哪一个呢?Spring是不知道的 , 所以就抛了异常
接下来我们再分析报错信息 : No default constructor found , 没有找到一个默认的构造方法 , 现在我们是因为写了多个有参的构造从而报的错 , 那么为什么会报一个没有默认的构造方法的错呢?
其实Spring在有多个有参构造时会去找无参的构造方法 ,因为无参的构造也有一种默认的意义, 而我们又没有默认的构造方法 , 所以就抛了这个异常
如果非要有两个有参构造 , 也不是不行 , 既然它不知道用哪一个 , 那么就加一个@Autowired告诉它用哪一个 , 例如这样:
@Component
public class UserService {
@Autowired
private OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("有参构造1");
}
@Autowired
public UserService(OrderService orderService , OrderService orderService1) {
this.orderService = orderService;
System.out.println("有参构造2");
}
}
例3:只有一个有参构造方法的情况下 ,这个方法的入参会不会有值?
首先说明 , 如果只有一个有参构造 , 那么就会覆盖整个默认无参的构造方法 , 如果还需要有无参的 , 就需要自己把它定义出来
@Component
public class UserService {
@Autowired
private OrderService orderService;
public UserService(OrderService orderService) {
System.out.println("orderService : " + orderService);
this.orderService = orderService;
System.out.println("有参构造1");
}
}
运行之后发现是有值的 , 那么这个值会从哪里来呢?
首先在创建UserService 这个类的时候 , 会使用这个唯一的有参构造 ,Spring就会找一个OrderService的Bean来赋值 , 前提是OrderService必须是一个Bean
那么根据什么找呢?
通常我们说一个Bean , 这个Bean肯定有一个类型的 , 还有一个名称 ,那无非就是入参的类型:OrderService, 以及参数名称: orderService
可能有人会想到 , 在Bean创建完成之后 , 会把该Bean对象存入一个Map<String, Object>,Map的key为beanName,value为Bean对象 , 也就是单例池 , 那么把参数名称orderService当作key , 来这个map获取 , 这种方式可行吗?
可行是可行 , 但是有一个问题,类型是不对应的 , 很有可能注入的时候是这样注入的:
@Autowired
private OrderService memberService;
那么这样直接通过名称过去出来的类型就是不对应的 , 由此可见 , 这个名称其实不是那么的重要 ,重要的是类型
所以最保险的方式就是根据类型去找 , 但是通过类型会找到多个 , 比如代码是这样写的情况:
@ComponentScan("com.lyh")
public class AppConfig{
@Bean
public OrderService orderService1(){
return new OrderService();
}
@Bean
public OrderService orderService2(){
return new OrderService();
}
}
可以思考一个问题 ,现在Spring容器中有几个OrderService 类型的Bean?
AppConfig中定义了两个 , 还有我们通过@Component来声明的一个 , 那么就是三个
我们可以运行代码看看结果
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
Object orderService = applicationContext.getBean("orderService");
System.out.println("orderService : " + orderService);
Object orderService1 = applicationContext.getBean("orderService1");
System.out.println("orderService1 : " + orderService1);
Object orderService2 = applicationContext.getBean("orderService2");
System.out.println("orderService2 : " + orderService2);
}
运行结果:
orderService : com.lyh.service.OrderService@49b0b76 orderService1 :
com.lyh.service.OrderService@769f71a9 orderService2 :
com.lyh.service.OrderService@4c9f8c13
很显然 , 都是有值的 , 那么通过类型找到三个 , 不可能把这三个都传进来 , 需要确定一个 , 怎么去确定其中一个呢?
我们可以通过有参构造入参的参数名称orderService 去找 , 这样是不是就可以找到一个 , 然后赋值 , 这个名字是不会重名的,如果有重名,可能会直接覆盖 , 因为他是存在map中的 , 而map的key是不允许重复的
例4:只有一个有参构造方法的情况下 ,参数名称改变 , 会抛出异常?还是正常执行
@Component
public class UserService {
@Autowired
private OrderService orderService;
public UserService(OrderService orderService3) {
this.orderService = orderService3;
System.out.println("有参构造1");
}
}
运行之后肯定是报错的 , 报错信息如下:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.lyh.service.OrderService’ available: expected single matching bean but found 3: orderService,orderService1,orderService2
大概意思就是找到了三个Bean : orderService,orderService1,orderService2 , 但是没有符合条件的bean,这个条件就是参数名称是orderService3的
例5:只有一个有参同时只定义一个OrderService类型的bean, 然后参数名称改变
对上面的代码进行简单改造 , 使OrderService类型的bean只有一个 , 注掉AppConfig类的相关代码
@ComponentScan("com.lyh")
public class AppConfig{
//@Bean
//public OrderService orderService1(){
// return new OrderService();
//}
//@Bean
//public OrderService orderService2(){
// return new OrderService();
//}
}
UserService
@Component
public class UserService {
@Autowired
private OrderService orderService;
public UserService(OrderService orderService3) {
this.orderService = orderService3;
System.out.println("有参构造 : " + orderService3);
}
}
OrderService
//@Component
public class OrderService {
public OrderService() {
}
}
运行之后,程序正常
有参构造 : com.zhouyu.service.OrderService@d83da2e
因为它通过类型就从Spring容器中找到了一个 , 所以直接赋值
例6:那么取消定义OrderService这个Bean呢
//@Component
public class OrderService {
public OrderService() {
}
}
运行之后发现报错了 , 报错类型如下
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.lyh.service.OrderService’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
大概就是说预计有一个OrderService的bean , 但是没有找到这个bean , 因为他想从Spring容器中找一个OrderService类型的bean来注入 ,但是现在没有这个bean , 所以就报了这个错误
构造方法推断完成之后就得到了一个普通的对象 , 那么Spring拿到整个对象之后就可以为属性去赋值了 , 那么也就是接下来要说的DI(依赖注入)
DI(依赖注入)
例1: Spring给属性赋值 , 赋的是什么值呢?去哪里找这个值呢?
AppConfig
@ComponentScan("com.lyh")
public class AppConfig{
}
UserService
@Component
public class UserService {
@Autowired
private OrderService orderService;// 赋的是什么值呢?
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("有参构造 : " + orderService);
}
}
OrderService
@Component
public class OrderService {
public OrderService() {
}
}
先根据类型 , 再根据名称 , 也就是先byType , 再byName , 结合上面的推断构造方法是不是就对依赖注入也通畅了一点
初始化前
初始化前也就是执行@PostConstruct注解的方法 , 通过反射去判断那些方法加了@PostConstruct , 然后执行
初始化
初始化也就是执行实现了InitializingBean接口的afterPropertiesSet()方法
初始化后
初始化后也就是我们的AOP ,那么我们还是通过例子来说明
例1:使某个类成为代理对象 , 那么代理对象中的属性会不会有值
@Component
public class UserService {
@Autowired
private OrderService orderService;
public void test(){
System.out.println("UserService test 方法执行");
}
}
定义切面类
@Aspect
@Component
public class LyhAspect {
@Before("execution(public void com.lyh.service.UserService.test())")
public void lyhBefore(JoinPoint joinPoint) {
System.out.println("zhouyuBefore");
}
}
开启代理
@ComponentScan("com.lyh")
@EnableAspectJAutoProxy
public class AppConfig{
}
测试
public class TestSpring {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
}
}
我们可以现在userService.test();这行代码打个断点
可以发现 ,他确实是一个代理对象 , 同时也可以看到orderService = null , 表示它确实没有值 , 为什么呢?难道开了AOP之后依赖注入不生效了吗?其实不是这样的 , 通过阅读上面的文章我们发现 , 再bean初始化之后并没有依赖注入这个步骤 , 也就是说Spring就没有这么设计
然后我们在UserService这个类中的test()方法中的 System.out.println(“UserService test 方法执行”); 打个断点 , 然后继续往下走 , 你会发现 , 一旦进入到test方法 , 它orderService属性就有值了
也就是说@Autowired注解还是生效的 , 并且在test方法中也可以使用
那么为什么代码在进入到test()方法之后orderService属性才有值?
这个时候我们首先要明白Cglib的工作原理 , 他的原理大概是这样: 动态生成被代理类的子类 , 对于上面的例子而言 , 也就是说Cglib会生成一个userService的子类 , 并且继承userService , 伪代码如下
public class UserServiceProxyCGlib extends UserService {
@Override
public void test() {
// @Before切面逻辑
super.test();
// @after切面逻辑...等等
}
}
那么我们在调用test()方法的时候 , 其实执行的是UserServiceProxyCGlib的test();然后再执行super.test(); , 这个时候肯定没有值 , 因为他只是一个方法的调用而已 , 但是我们又希望他有值 . 那么怎么做呢? 上面提到过 , 推断完构造方法之后会得到一个普通对象 , 然后给这个普通对象进行依赖注入 , 那么我们是不是就可以使用这个普通对象,代码原理代码实现如下所示
public class UserServiceProxyCGlib extends UserService {
UserService target;
@Override
public void test() {
// @Before切面逻辑
target.test();
// @after切面逻辑...等等
}
}
这样的话 , 等你这正执行test()方法的时候 , 已经是经过依赖注入的对象 , 那么就肯定是有值的, 通过debug也可以很清楚的看到 , 因为从Srping容器获取到的就是一个代理对象 , 那么执行代理对象的test()方法 , orderService的值肯定是空的 , 但是target是有值的 , 所以通过执行target.test();orderService也就是有值的
Spring它怎么知道这个Bean要进行AOP呢?
首先他肯定会从容器中拿到所有的切面Bean , 然后去遍历拿出来的这些切面Bean , 然后再遍历这些切面Bean中的方法来判断它有没有@Before之类的注解 , 如果有那么就判断这个注解里面定义的表达式是否和我当前创建的Bean匹配 , 如果匹配 , 难么Spring就知道当前类是需要AOP的 , 然后Spring会把所有和当前创建类匹配的所有的方法给缓存起来 , 存到一个map中 ,等真正要执行切面逻辑的时候 , 再从缓存获取 , 然后直接执行
这个过程还是非常重要的 ,在这里先简单讲一下原理 ,等后面会用源码来验证
自此 , Bean生命周期几个比较重要的流程也就大概过了一遍 , 后面的文章还会源码的分析来一步步看到这些过程
Spring事务
例1: 开始事务 , 然后执行sql并抛异常 , 数据库会不会有数据?
@ComponentScan("com.lyh")
@EnableTransactionManagement
public class AppConfig{
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/world?characterEncoding=utf-8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
return sessionFactoryBean.getObject();
}
}
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void test(){
jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('2', '2')");
throw new NullPointerException();
}
}
然后我们来运行
public class TestSpring {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
}
}
通过观察数据库的数据 , 发现数据确实是写到数据库了 , 但是为什么呢?不是跑了异常吗?不应该回滚吗?
其实我们的代码都没有问题 , 但是我们少加了一个@Configuration的注解 , 我们把它加到AppConfig试一试 ,再次操作 , 发现依然报错 , 但是数据是没有写进去了 , 这是为什么呢?
首先我们需要明白的一点就是 , 当我们加了@Transactional注解之后 , Spring在创建这个类时会创建一个代理对象 , 所以我们从Spring容器拿出来的也是一个代理对象 , 现在它就不是AOP的代理对象 , 他是Spring事务所产生的代理对象 , 那么他的逻辑就是这样的
public class UserServiceProxyCGlib extends UserService {
UserService target;
@Override
public void test() {
// 判断有没有加@Transactional
// 如果有 , 那么由Spring事务管理器创建一个数据库连接
// 设置autocommit属性为false , 这个属性默认为true ,就是自动提交的意思 , 如果每执行一个sql提交一次, 那么@Transactional的意义何在呢?如果前面的sql执行完了 , 后面抛异常了 , 那我还回滚什么东西呢?
target.test();
// 执行完之后调用commit()方法提交
// 如果抛异常 , 那么调用rollback()方法
}
}
这是Spring事务他大体的一个工作原理
我们来看transactionManager()和jdbcTemplate()方法都是在调用dataSource() , 调用dataSource()方法就会产生一个DriverManagerDataSource对象 , 所以调用两次就会产生两个不同的DriverManagerDataSource对象 , 所以问题也就出来了 : 我们执行jdbcTemplate.execute()时的datasource对象是两个不同的对象 , 也就是说Spring事务管理器创建的一个数据库连接根本没有用 , 但是如果加了@Configuration ,(当然还有其他一些逻辑) 那么就能保证它的datasource就是同一个 , 但是前提是datasource为同一个 , 那么为什么会是同一个呢?这里也和代理模式是有关系 , 加了@Configuration之后AppConfig也会成为一个代理对象 , 那么代理逻辑就是当我们执行datasource()方法的时候 , 它会先去Spring容器看看有没有 , 如果有那么就直接返回 , 如果没有那就真正执行datasource()方法 , 但是如果是这样写的就有问题
@ComponentScan("com.lyh")
@EnableTransactionManagement
public class AppConfig{
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(**dataSource1()**);
return transactionManager;
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/world?characterEncoding=utf-8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public DataSource dataSource1() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/world?characterEncoding=utf-8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
return sessionFactoryBean.getObject();
}
}
配了两个dataSource , 那么还是会出现问题的
例2: 事务失效场景之一
改在一个UserService类代码如下 , 并且把传播度等级设置为NEVER , NEVER就表示 , 如果有一个事务的话 , 就会抛异常
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void test(){
jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('3', '3')");
this.test1();
}
@Transactional(propagation = Propagation.NEVER)
public void test1(){
}
}
按理说他这个如果运行是要报错的 , 但是真正运行发现没有报错 , 那么是为什呢?
我们可以仔细理解一下 , 这个test1()方法到底是谁在调用?他的执行逻辑还是相同
public class UserServiceProxyCGlib extends UserService {
UserService target;
@Override
public void test() {
// 判断有没有加@Transactional
// 如果有 , 那么由Spring事务管理器创建一个数据库连接
// 设置autocommit属性为false , 这个属性默认为true ,就是自动提交的意思 , 如果每执行一个sql提交一次, 那么@Transactional的意义何在呢?如果前面的sql执行完了 , 后面抛异常了 , 那我还回滚什么东西呢?
target.test();
// 执行完之后调用commit()方法提交
// 如果抛异常 , 那么调用rollback()方法
}
}
但是是由target调用的test()方法 , target不就是UserService吗 , 所以test1()方法的调用者也是target , 那么target是一个执行这个test1()方法 , 这个@Transactional注解有什么用呢?
这就是我们经常会遇到的一个事务失效的场景 , 那么怎么解决呢?
首先我们需要根据业务理清楚加了@Transactional注解的方法到底是谁在调用 , 在调用的时候是不是代理对象在调这个test1()方法 , 只要是代理对象在调用 , 那么@Transactional注解就是生效的
或许有人是这样解决的
新增一个类 :
@Component
public class UserServiceBase {
@Transactional(propagation = Propagation.NEVER)
public void test1(){
}
}
然后这样调用
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserServiceBase userServiceBase;
@Transactional
public void test(){
jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('3', '3')");
userServiceBase.test1();
}
}
然后我们可以运行 , 会发现报错了
Exception in thread “main” org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’
这个才是我们想要看的结果 ,我们可以简单分析一下 , userServiceBase的test1()方法也是有事务存在的 , 同时userServiceBase也是一个Bean , 它最终也会产生一个代理对象去当做一个Bean , 碎玉UserService而言 , 我要给userServiceBase这个属性去赋值 , 那么他肯定要从Spring容器中找到一个userServiceBase的一个Bean来赋值 , 所以他找到的就是Spring事务所产生的userServiceBase的代理对象 , 所以这个注解就是有用的
其实如果明白了上面的逻辑 , 那么还有一种简单的办法 , 就是自己注入自己
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Transactional
public void test(){
jdbcTemplate.execute("INSERT INTO `world`.`user`(`name`, `username`) VALUES ('3', '3')");
userService.test1();
}
@Transactional(propagation = Propagation.NEVER)
public void test1(){
}
}
最终还是要给这个userService属性去赋值 , 那么就会从Spring容器中去找 , 找到的就是一个userService的代理对象
以上就是对Spring比较重要的几个过程 , 可以稍微了解一下 , 方便对后面的源码的阅读
更多推荐
所有评论(0)