感兴趣的话大家可以关注一下公众号 : 猿人刘先生 , 欢迎大家一起学习 , 一起进步 , 一起来交流吧!

说明

本系列文章以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 {@Autowiredprivate 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)的时候, 他就会大概进行一些操作

  1. 解析AppConfig , 然后得到扫描路径
  2. 遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String, Class>。(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap,后续文章会有说明)
  3. 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 {@Autowiredprivate OrderService orderService;public UserService() {System.out.println("无参构造");}public UserService(OrderService orderService) {this.orderService = orderService;System.out.println("有参构造");}}

运行之后可以发现 , Spring使用的是无参构造方法

例2:如果有两个有参构造呢?

@Component
public class UserService {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate OrderService orderService;public UserService(OrderService orderService) {this.orderService = orderService;System.out.println("有参构造1");}@Autowiredpublic UserService(OrderService orderService , OrderService orderService1) {this.orderService = orderService;System.out.println("有参构造2");}}

例3:只有一个有参构造方法的情况下 ,这个方法的入参会不会有值?

首先说明 , 如果只有一个有参构造 , 那么就会覆盖整个默认无参的构造方法 , 如果还需要有无参的 , 就需要自己把它定义出来

@Component
public class UserService {@Autowiredprivate 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{@Beanpublic OrderService orderService1(){return new OrderService();}@Beanpublic 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Overridepublic void test() {// @Before切面逻辑super.test();// @after切面逻辑...等等}
}

那么我们在调用test()方法的时候 , 其实执行的是UserServiceProxyCGlib的test();然后再执行super.test(); , 这个时候肯定没有值 , 因为他只是一个方法的调用而已 , 但是我们又希望他有值 . 那么怎么做呢? 上面提到过 , 推断完构造方法之后会得到一个普通对象 , 然后给这个普通对象进行依赖注入 , 那么我们是不是就可以使用这个普通对象,代码原理代码实现如下所示

public class UserServiceProxyCGlib extends UserService {UserService target;@Overridepublic 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{@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(dataSource());}@Beanpublic PlatformTransactionManager transactionManager() {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource());return transactionManager;}@Beanpublic 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;}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();sessionFactoryBean.setDataSource(dataSource());return sessionFactoryBean.getObject();}}
@Component
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic 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;@Overridepublic 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{@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(dataSource());}@Beanpublic PlatformTransactionManager transactionManager() {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(**dataSource1()**);return transactionManager;}@Beanpublic 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;}@Beanpublic 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;}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();sessionFactoryBean.setDataSource(dataSource());return sessionFactoryBean.getObject();}}

配了两个dataSource , 那么还是会出现问题的

例2: 事务失效场景之一

改在一个UserService类代码如下 , 并且把传播度等级设置为NEVER , NEVER就表示 , 如果有一个事务的话 , 就会抛异常

@Component
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic 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;@Overridepublic 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 {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate UserServiceBase userServiceBase;@Transactionalpublic 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 {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate UserService userService;@Transactionalpublic 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比较重要的几个过程 , 可以稍微了解一下 , 方便对后面的源码的阅读

Spring框架(一) 底层核心原理解析相关推荐

  1. Spring学习篇底层核心原理解析

    说明 本系列文章以spring-framework-5.3.10为例 ,本篇文章的目的就是使各位读者能在使用Spring的基础上对Spring的一些比较核心的内容有一个大概的认识,并不是特别全面,会在 ...

  2. 【Spring】Spring底层核心原理解析

    本文内容索引: 1.Bean的生命周期底层原理 2.依赖注入底层原理 3.初始化底层原理 4.推断构造方法底层原理 5.AOP底层原理 6.Spring事务底层原理 ​但都只是大致流程,后续会针对每个 ...

  3. Spring源码学习(一)--Spring底层核心原理解析

    目录 Spring中是如何创建一个对象? Bean的创建过程 推断构造方法 AOP大致流程 Spring事务 最近在跟视频学习spring源码,将每节课记录下来,以后好来复习. 首先把Spring中核 ...

  4. Spring框架两大核心特征的基本理解

    Spring框架的两大核心特征:AOP和IoC IoC(控制反转)是Spring的一个容器,他不是一种技术,而是一种思想,依旧是基于面向对象编程的.它能指导我们怎么样设计出松耦合.更优良的程序. 简单 ...

  5. spring源码分析01-(前期准备)spring核心原理解析和手写简易spring

    1.本文主要介绍内容 本文会把Spring中核心知识点大概解释下.可以对Spring的底层有一个整体的大致了解.主要内容包括: 手写简易spring框架,帮助更好理解spring. 代码点击链接自取 ...

  6. 【Spring】1.核心原理解析

      目录 概况 Bean的生命周期 推断构造方法 AOP流程 事务 概况 核心知识点串讲,对Spring有整体的了解 比如: 1. Bean的生命周期原理 2. 依赖注入原理 3. 初始化原理 4. ...

  7. 微服务架构具体实现工具框架:Spring Boot概览与核心原理

    目录 一.Spring Boot概述 1.回顾使用Spring开发WEB应用程序过程 2.新一代开发框架的诞生Spring Boot 编码方面 配置方面 部署方面 监控方面 3.SpringBoot核 ...

  8. react 流程图框架_【赠书】Preact(React)核心原理详解Preact(React) 核心原理解析...

    豆皮粉儿们,又见面了,今天这一期,由字节跳动数据平台的"winge(宝丁)",带大家见识见识前端"轮子"之一Preact框架. 提到Preact,你肯定会先想到 ...

  9. 深入浅出Spring源码:IOC原理解析(一)

    IOC(Inversion of Control),即控制反转,意思是将对象的创建和依赖关系交给第三方容器处理,我们要用的时候告诉容器我们需要什么然后直接去拿就行了.举个例子,我们有一个工厂,它生产各 ...

最新文章

  1. Linux系统学习笔记:文件描述符标志
  2. Java静态初始化,实例初始化以及构造方法
  3. GTD时间管理---非行动性
  4. 飞控所有PID参数都无法消除振荡问题
  5. Java执行系统命令策略
  6. 什么叫做形态学图像处理_图像形态学处理中的膨胀与腐蚀介绍
  7. pycharm汉化版安装
  8. 在线PPT—Sway初级教程
  9. EXCEL数据比对常用函数
  10. 腾讯会议中用PPT放视频,视频没有声音
  11. mysql 执行存储过程
  12. 网站ICP备案是什么呢?
  13. html鼠标悬浮更换图片,Vue.js鼠标悬浮更换图片功能
  14. HTML怎么跟随页面缩放,如何让网页跟着 浏览器全比例缩小(示例代码)
  15. PathMeasure 轨迹动画神器 路径动画
  16. 计算机网络——计算机网络中的安全
  17. 磨金石教育分享:43款字体设计工具大集合,超赞!
  18. redux和react-redux
  19. camera基础知识(1)
  20. 实时获取用户所在城市(管理设备位置信息)

热门文章

  1. 等保的五个等级具体区别有哪些呢?
  2. 2022-2027年中国微型传动系统行业发展监测及投资战略研究报告
  3. qq个性签名,超级强悍
  4. AI上推荐 之 MMOE(多任务yyds)
  5. 费雷尔卓德服务器位置,LOL:各个地域地位最高的英雄,弗雷尔卓德“三分天下”...
  6. 智力题+数量关系+判断推理(6)
  7. NC6 二叉树的最大路径和
  8. 【论文翻译】AFFormer:Head-Free Lightweight Semantic Segmentation with Linear Transformer
  9. UBUNTU 入门全程导用
  10. 宽带连接错误提示678和691的原因,解决方法