Spring 原理入门
IOC-对象池
IOC 控制反转,Bean 对象由 Spring 来托管(生命周期),这些对象所在的位置暂且称为 Bean 的对象池(Map)。Bean 对象的创建除了实例化,还经历了哪些步骤最终放到了对象池呢?
Bean 的创建与销毁
Bean 创建流程
以 Spring Boot 和 注解为例说明:
-
Spring 按需扫描包,找到标有如 @Component 注解的类
-
默认调用无参构造方法,进行类的实例化(类反射)
如果没有无参构造方法,会用有参的构造方法。如果没有无参,但存在多个有参构造方法会抛错误,这样Spring 不知道用哪个了,除非在某个有参的构造方法上打上 @Autowired。
-
Bean 属性的依赖注入,反射找到带 @Autowired 等注解的方法,进行 Bean 注入
-
通过各种Aware接口声明了依赖关系,则会注入Bean对容器基础设施层面的依赖
具体包括BeanNameAware、BeanFactoryAware和ApplicationContextAware,分别会注入Bean ID、Bean Factory或者ApplicationContext
-
初始化前,调用 BeanPostProcessor 的前置初始化方法 postProcessBeforeInitialization @PostConstruct
-
初始化,如果实现了 InitializingBean 接口,则会调用 afterPropertiesSet 方法
-
初始化后,调用 BeanPostProcessor 的后置初始化方法 postProcessAfterInitialization
初始化后的步骤会进行 AOP 动态代理生成
-
Bean 对象放入单例池 Map,如果是动态代理的Bean 对象,放入的是代理后的Bean。Map 的key 是 beanName,value 对应是Bean 对象 Map<beanName, beanObj>
Bean 销毁
Spring Bean 的销毁过程会依次调用:
- DisposableBean 的 destroy 方法
- Bean 自身定制的 destroy 方法
寻找 Bean
单例 Bean 理解
单例Bean 是指一个Bean 对象在单例池里有一个,但同一个Class类型的Bean 在单例池中是可能多个的。看池的数据结构 Map<BeanName, BeanObj>,如果BeanName 不同就可以。
比如一种是 @Component 形式,一种是 @Configuration + @Bean 方法,就可以形成一个类型多个Bean。
寻找 Bean 流程
由上边,知道一个 Bean 类型可能有多个 Bean 对象,那么Spring 是如何寻找的?
『先 byType,再 byName』 先看类型,如果单例池中只有该类型的一个 Bean 对象,那就选它了。否则,会再看 BeanName。
- 问题:为什么不直接看 BeanName ?
因为参数名字,用户可以随意指定的(虽然不建议)。因此只有先看类型不OK时,才会看名字。
Bean 作用域
Spring Bean有五个作用域,其中最基础的有下面两种:
-
Singleton
Spring的默认作用域,也就是 IOC容器中创建唯一的一个Bean实例。(BeanName唯一,Bean实例可能有多个)。
-
Prototype
针对每个getBean请求,容器都会单独创建一个Bean实例。
-
Request,为每个HTTP请求创建单独的Bean实例。
-
Session,很显然Bean实例的作用域是Session范围。
-
GlobalSession,用于Portlet容器,因为每个Portlet有单独的Session,GlobalSession提供一个全局性的HTTP Session。
单例 Bean 的线程安全
单例 Bean可能存在线程安全问题,当类属性是有状态的,则Spring 多线程环境下会出现并发问题。所以 Bean 的属性建议都是无状态的,如注入的其他 Bean。
AOP 机制
AOP 即所谓的面向切面编程。核心思路就是代理一个对象,然后再对这个对象做修饰。
如果静态代理实现,怎么代理和修饰?了解设计模式或者对Java特性熟悉的,就知道两种方式:
-
基于接口
修饰类和被修饰类 大家都实现一个接口。修饰类在实现接口时,持有被修饰类对象,并且在实现的方法里增加修饰动作。
-
基于子类
道理一样,在子类做增强逻辑
显然,静态代理使用起来啰嗦且不人性。如果接口实现类或者子类能自动生成,岂不是OK。这就是动态代理,帮我们生成类字节码,当然也是基于接口(JDK)或者基于子类(CGLib)。AOP 的思路正是依赖的动态代理。
-
问题:如果一个Bean Class 发现需要AOP了,那么放到单例池中的Bean 对象 是原始Bean对象还是动态代理后的Bean对象 ?
答案是动态代理后的Bean对象,代理后的Bean 有个 target 属性(CGLib),会被赋值成原始 Bean 对象。
Spring 中事务
@Transaction 正是基于动态代理实现的。
- 大概原理
- 代理对象中执行切面(@Transaction)逻辑,建立一个连接,再把 autocommit 置为 false,方便方法全部执行完 再commit。(这里连接会放到 threadLocal里)。
- 用代理对象的 target 属性,即原始Bean 对象,执行被注解修饰的方法 (注意是原始Bean 对象执行的)
- 执行完,根据是否异常等来决定是commit 还是rollback
- 事务失效
- 从上边第一步知道,用代理Bean 对象调用该方法才能用到事务
- 从上边第二步知道,该方法内如果去调用其他事务修饰方法(尤其当前类中),仍然需要使用代理对象。因为此时的 this 对象是原始Bean 对象,而非代理对象。如果原始Bean,仍然没法使事务生效
- 事务传播机制
所谓传播机制是指,两个带事务 @Transactional 注解的方法发送调用,如发生异常时,在处理事务上的表现机制。
Spring 中自己定义了几种传播机制(注解的 propagation 属性来设置)。
传播机制 | 描述 |
---|---|
REQUIRED | 默认的事务传播级别,表示如果当前存在事务则加入该事务,如果当前不存在事务则创建一个事务 |
REQUIRES_NEW | 无论外部调用是否存在事务,都创建一个新的事务。当调用者的事务发生异常回滚的时候,不会对被调用者的事务产生影响。当被调用者出现异常回滚时,调用者看是否捕获了异常决定是否回滚。 |
NESTED | 当调用者方法的事务出现异常回滚时,那么其嵌套的事务也会被回滚。当被调用者方法事务发送回滚时,调用者方法事务是否回滚取决于是否捕获异常。 |
SUPPORTS | 支持事务,表示如果当前存在事务则加入该事务,如果当前不存在事务则以非事务的方式运行 |
NOT_SUPPORTED | 不支持事务,即以非事务的方式运行,如果外部存在事务则将事务挂起 |
MANDATORY | 强制型,如果当前存在事务则以事务的方式运行,如果不存在事务则报异常 |
NEVER | 不能存在事务,以非事务的方式运行,如果外部存在事务,则抛出异常 |
常见还是 REQUIRED、REQUIRES_NEW、NESTED 这三个。
Spring 怎么解决的循环依赖
- 怎么产生的
- 怎么解决的
产生循环依赖
显然 A Bean 里要依赖注入 B Bean,而注入 B Bean时 B Bean又依赖了 A Bean。
当循环依赖时,Spring 如何解决的?
如何解决循环依赖
增加一个暂存 Map
假设增加一个Map 当做中间层来存一个暂时Bean对象,即实例化完,但未彻底创建完的Bean。
举例有两个Bean A 和 B,有循环依赖:
- 假设先实例化 Bean A,此时把实例化的 A 放到一个暂存 Map<A, instance>
- 给 A 注入依赖 B,发现单例池里没有B,遂去创建 B
- 实例化 B
- 给 B 注入依赖 A,发现单例池里没有A,再去看暂存 Map,发现有,遂注入依赖 A,经过剩余创建步骤后 Bean B 创建完成放入单例池
- 接着2,完成 A 的依赖注入,这下单例池有 B 了。
看起来除了单例池的 Map,额外再需要增加一个暂存 Map 就可以打破循环依赖了。但 Spring 又用了三个Map 解决,这是为什么呢?
三级 Map
既然设计了三级 Map,那我们分析下 两级 Map 会遇到什么问题?这里不得不提到 动态代理的Bean 的存在。
前边有说到,当Bean 要生成代理对象时,最终放入单例池里的是这个代理对象而不是原始Bean。上边增加一个 Map 时,还只是把刚实例化的原始Bean 注入了依赖,而不是最终的代理Bean。
-
问题,可以把代理Bean 的生成放到依赖注入(循环依赖)前,然后把代理Bean 放到 暂存 Map 不就可以了?
这意味着 AOP 的动作要提前,原来在 初始化后 的步骤里。这看起来也OK。todo
-
三级 Map
- 单例池 singletonObjects (存放的 经过完整创建周期的 Bean 对象)
- 二级缓存 earlySingletonObjects (存放的 未经过完整创建周期的 Bean对象)
- 三级缓存 singletonFactories,AOP 时更加方便 (值得注意的是 Map 中的 value 是个 lambda 表达式,包含 BeanName + BeanDefination + 原始Bean)
总结
Spring 的两个核心 IOC + AOP
- IOC 控制反转,让 Bean 对象由 Spring 来托管,Bean 创建流程值得关注
- AOP 面向切面,利用动态代理机制实现