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底层原理内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Spring底层原理深入分析的更多相关文章

  1. VUE响应式原理的实现详解

    这篇文章主要为大家详细介绍了VUE响应式原理的实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

  2. Spring JdbcTemplate执行数据库操作详解

    JdbcTemplate是Spring框架自带的对JDBC操作的封装,目的是提供统一的模板方法使对数据库的操作更加方便、友好,效率也不错,这篇文章主要介绍了Spring JdbcTemplate执行数据库操作,需要的朋友可以参考下

  3. Spring Batch批处理框架操作指南

    Spring Batch 是 Spring 提供的一个数据处理框架。企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作,这篇文章主要介绍了Spring Batch批处理框架操作指南,需要的朋友可以参考下

  4. 五分钟理解keep alive用法及原理

    这篇文章主要为大家介绍了keep alive用法及原理示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  5. Spring详细讲解@Autowired注解

    @Autowired注解可以用在类属性,构造函数,setter方法和函数参数上,该注解可以准确地控制bean在何处如何自动装配的过程。在默认情况下,该注解是类型驱动的注入

  6. 使用Spring AOP实现用户操作日志功能

    这篇文章主要介绍了使用Spring AOP实现了用户操作日志功能,功能实现需要一张记录日志的log表,结合示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  7. Spring Security认证器实现过程详解

    一些权限框架一般都包含认证器和决策器,前者处理登陆验证,后者处理访问资源的控制,这篇文章主要介绍了Spring Security认证器实现过程,需要的朋友可以参考下

  8. spring学习JdbcTemplate数据库事务管理

    这篇文章主要为大家介绍了spring学习JdbcTemplate数据库事务管理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  9. Spring Boot 集成Redisson实现分布式锁详细案例

    这篇文章主要介绍了Spring Boot 集成Redisson实现分布式锁详细案例,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

  10. Spring Security实现接口放通的方法详解

    在用Spring Security项目开发中,有时候需要放通某一个接口时,我们需要在配置中把接口地址配置上,这样做有时候显得麻烦。本文将通过一个注解的方式快速实现接口放通,感兴趣的可以了解一下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部