Spring底层原理深入分析
作者:PnJg?
bean生命周期
userService.class--->推断构造方法--->对象--->依赖注入--->初始化前(@postConstruct)--->初始化(@afterPropertiesSet)--->初始化后(AOP)--->放入Map(单例池)--->Bean对象
推断构造方法的底层原理
1、使用哪个构造方法
@Component public class OrderService { private UserService userService; @Override public OrderService (UserService userService) { this.userService = userService; } @Override public void test) { System.out.println(userService); } }
在上述例子中,因为写了一个有参构造方法,所以无参构造方法不能用了。
这个时候在userService属性上面没有加@Autowired注解,但是打印发现这个userService对象存在。
orderService是一个bean,spring想去创造这个bean,就要去用构造方法,发现构造方法是有参的,就回去找一个userService对象赋给这个属性。
当加上无参构造方法之后,spring就会去用无参的构造方法,这时候userService没有值;当有多个构造方法的时候,没有明确告知的情况下(告知是用@Autowired注解),spring会去找无参的构造方法,如果没有无参构造方法就直接报错。
对于第一种情况:“orderService是一个bean,spring想去创造这个bean,就要去用构造方法,发现构造方法是有参的,就回去找一个userService对象赋给这个属性。”的userService对象spring是从哪里找出来赋值给属性的呢?
spring首先会去单例池根据beanName即userService找有没有相应的bean对象,如果有就直接赋值给属性。如果没有,就去创建(前提是orderService是一个bean,但是没来得及创建,但如果是多例bean就直接去创建)。
但是如果是去创建的话就有可能出现循环依赖,考虑在userService中有orderService属性,并有一个有参构造方法:
@Component public class UserService{ private OrderService orderService ; @Override public UserService(OrderService orderService ) { this.orderService = orderService ; } @Override public void test) { System.out.println(orderService ); } }
这时候在创建orderService时需要用到有参构造方法,因为没有userService,这时候就要去创建,创建userService就要用到构造方法,可是完蛋,这是又需要orderService,可是orderService本身就在创建,也就是发生了循环依赖。
2、如果有参把哪个bean对象赋值给入参
假设单例池中有userService对象,可以直接拿出来用,但是怎么在单例池中拿到这个bean呢?因为参数名是可以随意设定的,所以不能直接拿参数名去找,所以要根据类型去单例池找,如果只有一个该类型的bean对象,直接赋值。但是有可能在单例池中存在多个同类型的对象(不同的beanName),这时候再根据参数名去匹配,如果找到了就直接赋值,匹配不上就报错。(先byType,再byName)
AOP实现原理
开启AOP动态代理之后,原本例子中的userService没有值了,因为AOP是发生在初始化之后,而初始化之后拿到的动态代理对象是不会去再去做依赖注入,直接放入了单例池,所以即使属性上面有@Autowired注解也没用。
cglib是基于父子类实现的,代理对象实质是继承了普通对象,并且代理对象中会有一个普通对象的属性、以及被增强的方法,在被增强方法中会先执行切面逻辑,再执行普通对象的方法,而普通对象中是有值的
spring事务
根据上面的AOP实现,事务是基于AOP的实现,生成的是代理类,如果有@Transactional就开启spring事务切面:
1、事务管理器会新建数据库连接,并且设置conn.autocommit = false,因为不管是mybatis还是jdbctemplate都是自动提交,这样就算出现异常,也已经提交了。在新建之后,当target即普通对象去执行test方法市,不管是mybatis还是jdbctemplate操作数据库都要拿到这个连接才能执行sql
2、如果执行完没有抛异常就执行conn.commit
3、
在a方法上的注解加了never,原本应该是要抛出异常的,但是还是顺利写进了数据库,原因是执行a方法的还是userService的普通对象(没有经过AOP增强的对象),就识别不了注解。为什么第一个test方法可以识别?因为一开始是被spring管理的bean对象userService执行,会有相应的逻辑代码去识别注解,识别到注解后生成了代理类和代理对象,然后去运行的test方法,但是执行a方法的时候相当于是 new userService,没有对应的逻辑代码去识别注解。
解决办法:把userService拆出一个新的类,把a方法写进新类
@Configuration
一开始没有加@configuration注解回滚失败。
jdbcTemplate是拿事务管理器新建的数据库连接conn。jdbcTemplate是通过ThreadLocal<Map<DataSource, conn>,线程可能会执行很多方法,可能会有执行不同的datasource,所以是一个map。
因为语法逻辑中jdbcTemplate和事务管理器中是返回新new出来的datasource对象,这样如果没有@configuration,那么jdbcTemplate和事务管理器拿到的是两个不同的datasource对象,那么jdbcTemplate去Map里面找不到对应的conn,只能自己创建新的连接,这样就不能被spring事务管理。
而如果加上了@configuration,那么AppConfig会基于动态代理产生AppConfig代理对象
AppConfig代理对象会先执行自己的代理逻辑,然后去执行普通对象的jdbcTemplate方法,进到父类的jdbcTemplate方法后会执行dataSource方法,但是都是代理对象在执行。代理对象执行dataSource方法的时候先执行代理逻辑:先去spring容器有没有dataSource这个bean,如果没有就创建,如果有就直接返回。
那么就能拿到一样的datasource对象。
循环依赖
为什么会出现循环依赖
首先上面这个例子考虑打破循环依赖。
可以添加一个map<"对象名",对象>,并把实例化AService得到的普通对象放入这个map中,这样在B填充A属性的时候就把AService普通对象注入,B就可以完成创建并放入了单例池,A也就能把单例池中的B对象注入。
但是存在的问题是如果AService在初始化后需要进行AOP,那么最终放入单例池里面的会是AService的代理对象,但是BService拿到的是AService普通对象,因为AService是单例bean,所以只能有一个对象在单例池中,又因为进行了AOP,所以只能是AService的代理对象,并且在其他地方如果依赖了AService,那么应该拿到的是AService的代理对象。
提前AOP
解决方法上述打破循环依赖出现的问题的方法是在把AOP提前,让B创建注入A属性的时候拿到的是AService的代理对象,即提前AOP。如果出现了循环依赖,那么就提前AOP,否则还是在初始化后进行AOP。
如何判断出现了循环依赖?创建一个creatingSet<beanName>,放入正在创建的bean的名称,代表该bean正在创建,后续可以在属性注入的步骤中,如果在单例池中找不到对应的bean对象而在creatingSet中找到了,就可以判定出现了循环依赖。
在判定出现循环依赖之后进行了提前AOP,那么应该什么时候创建AService的代理对象使得BService注入属性的时候拿到的是AService的代理并放入单例池呢?跳到二级缓存
第一级缓存singletonObjects
即单例池
第二级缓存earlySingletonObjects
二级缓存的作用是为了保证单例性:用于出现循环依赖的情况下,会提前产生一个没有经过完整生命周期的早期bean对象,并保存在二级缓存中。否则可能多次创建同一个类型的bean对象。
考虑如下例子:在上述例子中AService再加入一个CService属性,并且在CService也依赖AService属性
在进行bService的生命周期注入aService时会先去二级缓存中根据beanName找有没有aService的bean对象,如果没有就进行AOP并创建aService的代理对象放入二级缓存。
当进行cService的生命周期注入aService时就去二级缓存中找,发现已经有了aService,只可以直接取得
但是二级缓存中存放的不是完整的生命周期的bean对象,所以完成属性填充等动作之后从二级缓存中拿到aService的代理对象放入单例池中。
这时候就不需要在第四步进行AOP了,并且因为AOP的实质是在原有bean的基础上加入切面逻辑,并且AOP后生成的代理对象中还是会有target普通对象
所以在进行属性填充等动作的时候还是对AService的普通对象进行的,那么代理的对象中的普通对象还是可以拿到这些属性值,就相当于代理对象也拿到了
第三级缓存singletonFactories
打破循环依赖的关键,类似于上面提及的map,只是在spring的实现中key值是beanName,value是一个lamda表达式,三级缓存中不去判断是否出现循环依赖,而是只要是支持循环依赖并且是单例的,那么就会加入三级缓存
而lamda表达式返回的是一个对象,执行lamda方法的时候就执行了aop,所以返回的是一个代理对象
在底层源码中,通过第三级缓存来控制第四步中是否需要AOP
如果第三级缓存的map中remove出来是null,整明没有循坏依赖,就这时候进行AOP并返回增强后的代理对象,反之整明之前已经进行了AOP,不需要再进行AOP,直接返回普通对象。
到此这篇关于Spring底层原理深入分析的文章就介绍到这了,更多相关Spring底层原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!