Spring底层原理

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Spring底层原理

PnJg?   2022-09-27 我要评论

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,直接返回普通对象。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们