Spring基本原理学习笔记
普通类 -> Bean的整个过程
Spring把一个普通类放入IOC容器的整个过程(默认为单例Bean):
1、构造方法
2、依赖注入
3、初始化前
4、初始化
5、初始化后
6、对象代理(如果有AOP)
7、对象放入单例池Map
构造方法
-
Spring默认会调用无参构造
-
只有一个有参构造会调用这个有参构造,参数的值会从IOC中按类型(byType)寻找
- 找到:赋值给参数
- IOC中找到一个,直接赋值(这个过程可能会产生循环依赖)
- 找到多个,按形参名匹配(byName),匹配不上则报错
- 没找到:创建对象,赋值为参数
- 找到:赋值给参数
-
多个参数,需要告诉Spring使用哪一个,使用@Autowire指定,不指定会报错
依赖注入
Spring会遍历类的所有字段,发现有注解@Autowire,按按类型(byType)到Map寻找,没找到再按名字(ByName)找,再没找到就不管了,业务代码使用的时候回报NPE(空指针异常)
初始化前
如果想在Bean的初始化前做处理的话,可以随便写一个返回值为Void的方法,加上注解@PostConstruct
在初始化前这一阶段,Spring会遍历方法,发现方法有该注解,会执行该方法
初始化
在初始化阶段做处理的话,需要实现 InitializingBean 接口
在afterPropertiesSet方法中写初始化的业务
初始化后
AOP是在初始化后这个阶段执行的。SpringBoot默认使用的是cglib代理(基于继承),Spring默认使用的是JDK代理(基于接口)
下面是cglib的代理实现
target是目标类,位置在IOC容器
事务失效原理
1、普通对象调用事务方法会导致事务失效,只有代理对象调用事务方法才会生效
2、数据库配置类没有加@Configuration,导致JdbcTemplate 和 事务管理器 用的不是同一个DataSource对象,事务管理器 在JdbcTemplate 执行SQL时 会创建新的Connection , 而保证事务正常运行,需要所有SQL运行在同一个Connection;
加上@Configuration的话,JdbcTemplate 和 事务管理器用的是同一个DataSource,这时事务管理器创建连接存到ThreadLocal<Map<DataSource,Connection>> 中,JdbcTemplate 执行时通过自身的DataSource直接从Map中拿到连接,这个连接和事务管理器是同一个连接,从而实现事务功能。
上面的代码a方法的事务失效,传播属性NEVER作用:有事务的方法执行a方法时抛异常,都是实际并没有抛异常,也就是说事务失效了。
原因:调用a方法的对象并不是代理对象而是普通对象即UserService而不是UserServiceProxy,故并没有开启事务,所以不会抛异常
为什么test()方法事务没失效:因为test方法执行的是 UserServiceProxy里面重写的test方法
怎么执行的a方法: target.test().a() 而target类型是UserService,所以a方法执行之前并没有关闭自动提交,自然事务没起作用
整个过程: 开启事务 -> Test() -> a() -> 关闭事务
设想:开启事务A -> Test() ->开启事务B -> a() ->关闭事务B -> 关闭事务A
解决方案1:把a方法拆到另外一个Bean中
解决方案2:自己注入自己(也是一种循环依赖)
解决方案3:拿到当前代理对象
@Configuration实现原理
@Configuration 、 AOP 、 @Lazy 都是基于动态代理,是平级的。
加上@Configuration表示这个类是代理类。
@Configuration是如何完成jdbcTemplate 和 事务管理器拿到同一个DataSource的?
按顺序执行:
1、执行JdbcTemplate 调用 dataSource 方法
2、dataSource 方法会先去IOC看一下有没有DataSource对象,没有则创建DataSource对象到IOC容器中
3、执行事务管理器,调用dataSource 方法,去IOC看一下有没有DataSource对象,有则直接拿来用,不dataSource 方法里面的创建动作
执行方法之前去IOC容器看一下有没有这个动作在哪做的?
因为@Configuration是一个代理类,所以说这个动作是代理类做的,在执行 super.dataSource() 方法之前做的判断。
Spring循环依赖
什么是循环依赖现象:A依赖B,B依赖A -> 循环依赖 、 A 依赖 A 、A 依赖 B ,B依赖C ,C依赖 A 、或者更长的一个闭环等。
Spring解决循环依赖:1、三级缓存 2、@Lazy注解
怎么判断出现了循环依赖
1、AService这个Bean创建的时候会把AService的名字放入一个creatingSet中
2、AService依赖BService
3、创建Bservice,发现依赖AService
4、BService去IOC容器找,没有 ,看一下是不是在创建中,是,出现循环依赖
三级缓存
第一级缓存:单例池 singletonObjects,存放经过完整生命周期的Bean
第二级缓存:earlySingletonObjects,存放代理对象,作用:保证代理对象单例,只有出现循环依赖时才需要用到,这个代理对象的target属性还没有经过完整的生命周期
第三级缓存: singletonFactories:保存实例化之后的普通对象(实际上存的是一个lambda,执行lambda返回代代理对象),供AOP使用,作用:打破循环
如果BService需要AOP时,会从earlySingletonObjects 二级缓存看有没有,没有的话进行AOP放入二级缓存,这时其他Bean依赖BService时,可以直接从 二级缓存拿到代理对象,就不会导致创建多个Bean的情况
怎么打破循环
2.2步骤的AOP是需要拿到AService普通对象的,因为代理对象的target属性需要赋值。这时就需要 第三级缓存 来保存普通对象,让代理对象拿,拿到之后从三级缓存移除,返回的代理对象添加到二级缓存。
@Lazy为什么能解决循环依赖
@Lazy为什么能解决循环依赖:因为BService会被赋值一个@Lazy产生的代理对象,所以AServic能正常创建到单例池,用到BService时,再从单例池找到AService注入BService,这样就解决了循环依赖。
下面的BService通过构造注入,AService也是构造注入的话,这时产生的循环依赖Spring解决不了的,因为在实例化的时候产生的循环依赖,此时普通对象都还没创建出来,三级缓存还没起作用。需要使用@Lazy注解才能解决。
加上@Lazy注解,BService会赋值一个代理对象,自然能正常创建Bean
Spring整合Mybatis底层原理
如何创建Mapper代理对象放入IOC?
Service层使用Mapper接口,需要注入Mapper,但是Mapper是接口,是不能创建成Bean的,只能通过动态代理的方式,生成代理对象放入IOC容器,供Service层使用。
如何创建代理对象呢?
可以通过FactoryBean接口实现,实现FactoryBean接口的类,Spring会为它创建两个Bean,一个是FactoryBean对象、一个是getObject方法返回的对象。
getObject方法会返回代理对象。
这时实现FactoryBean的类需要加@component注解,FactoryBean类其实可以用其他方法使用的。
比如说BeanDefinition
BeanDefinition位置需要写在Spring容器下面,非常的麻烦,可以用其他的方法简化
创建一个类实现ImportBeanDefinitionRegister·接口,在这个类里面注册BeanDefinition
使用的时候直接通过@Import注解导入即可
这时一个BeanDefinition对应一个Mapper,怎么动态的获取Mapper并注册呢?
Mapper扫描自己怎么实现?
实现ClassPathBeanDefinitionScanner接口,因为Spring扫描会默认忽略接口,需要重写isCandidateComponent方法,设置为只扫描接口。还需要重写DoScan,因为扫描之后为Mapper接口,但是我们需要的是BeanDefinition,所以需要把Mapper转换为BeanDefinition
怎么用呢?
通过注解拿到扫描路径,创建扫描器传入Spring容器,添加扫描过滤器。
@LockUp
这里调用test方法会返回三个不同的orderService,如果不加@LockUp,UserService里面调用无数次test方法,使用的是同一个orderService,这不符合orderService原型的定义,加上@LockUp注解就能每调用一次test返回一个新的orderService。
Spring怎么扫描的
笔记参考B站视频
https://www.bilibili.com/video/BV1tR4y1F75R/?vd_source=587abd5e4b9054694d34a59f50f4e3c6