Spring IOC 原理
概念
Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,而后依据这张注册表实例化 Bean,拆卸好 Bean 之间的依赖关系,为下层利用提供准备就绪的运行环境。其中 Bean 缓存池为 HashMap 实现。
IOC 容器的实现参考上篇
IOC 和依赖注入
Spring IoC 容器负责对象的生命周期和对象之间的(依赖)关系。
在创立新的 Bean 时,IoC 容器会主动注入新 Bean 的所依赖的其余 Bean,而无须本人手动创立。
- IoC 容器主动实现对象的初始化,防止在开发过程中写一大段初始化代码。
- 创立实例的时候不须要理解细节。
长处:不会对业务代码形成很强的侵入性,对象具备更好的可测试性,可重用性和可拓展性。
IoC 全称为 InversionofControl,翻译为“管制反转”.
- 谁管制谁:在传统的开发模式下,咱们都是采纳间接 new 一个对象的形式来创建对象,也就是说你依赖的对象间接由你本人管制,然而有了 IOC 容器后,则间接由 IoC 容器来管制。所以“谁管制谁”,当然是 IoC 容器管制对象。
- 管制什么:管制对象。
为何是反转:没有 IoC 的时候咱们都是在本人对象中被动去创立被依赖的对象,这是正转。然而有了 IoC 后,所依赖的对象间接由 IoC 容器创立后注入到被注入的对象中,依赖的对象由原来的被动获取变成被动承受,所以是反转。 - 哪些方面反转了:所依赖对象的获取被反转了。
“依赖注入”明确形容了“被注入对象依赖 IoC 容器配置依赖对象”。
了解 DI 的要害是:“谁依赖谁,为什么须要依赖,谁注入谁,注入了什么”,那咱们来深入分析一下:
- 谁依赖谁:“被注入的对象”依赖“依赖对象”。举个例子,对象 A 依赖 B,那么 IoC 容器在注入 A 对象之前,须要先注入 B 对象;对象 A 依赖 IoC 容器;对象 B 被注入到对象 A 中,所以 A 是被注入的对象,B 是依赖对象,A 依赖 B。
- 为什么须要依赖:容器治理对象须要 IoC 容器来提供对象须要的内部资源;
- 谁注入谁:很显著是 IoC 容器注入某个对象,也就是注入“依赖对象”;
- 注入了什么:就是注入某个对象所须要的内部资源(包含对象、资源、常量数据)。
二者的关系
管制反转(Inversion of Control)就是依赖倒置准则的一种代码设计的思路。具体采纳的办法就是所谓的依赖注入(Dependency Injection)。
Bean 作用域
Spring 中为 Bean 定义了 5 中作用域,别离为 singleton(单例)、prototype(原型)、request、session 和 global session,5 种作用域阐明如下:
- singleton:单例模式(多线程下不平安)
Spring IOC 容器中只会存在一个共享的 Bean 实例,无论有多少个 Bean 援用它,始终指向同一对象。该模式在多线程下是不平安的。singleton 作用域是 Spring 中的缺省作用域,也能够显示的将 Bean 定义为 singleton 模式,配置为:
<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>
- prototype: 原型模式,每次应用时创立
每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创立一个新的 Bean 实例,每个 Bean 实例都有本人的属性和状态。依据教训,对有状态的 bean 应用 prototype 作用域,而对无状态的 bean 应用 singleton 作用域。
-
request:一次 request 一个实例
在一次 Http 申请中,容器会返回该 Bean 的同一实例。而对不同的 Http 申请则会产生新的 Bean,而且该 Bean 仅在以后 Http Request 内无效, 以后 Http 申请完结,该 bean
实例也将会被销毁。<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
-
session
在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 申请则会创立新的实例,该 Bean 实例仅在以后 Session 内无效。同 Http 申请雷同,每一次
session 申请创立新的实例,而不同的实例之间不共享属性,且实例仅在本人的 session 申请内无效,申请完结,则实例将被销毁。<bean id="userPreference" class="com.ioc.UserPreference" scope="session"/>
- global Session
在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在
应用 portlet context 时无效。
Bean 生命周期
Bean 的生命周期概括起来就是 4 个阶段:
- 实例化(Instantiation);
- 属性赋值(Populate);
- 初始化(Initialization);
- 销毁(Destruction)。
- 实例化:实例化一个 Bean,也就是咱们常说的 new。
- IOC 依赖注入:依照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入。
- setBeanName 实现:如果这个 Bean 曾经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String)办法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
- BeanFactoryAware 实现:如果这个 Bean 曾经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory,setBeanFactory(BeanFactory)传递的是 Spring 工厂本身(能够用这个形式来获取其它 Bean,只需在 Spring 配置文件中配置一个一般 Bean 就能够)。
- ApplicationContextAware 实现:如果这个 Bean 曾经实现 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext)办法,传入 Spring 上下文(同样这个形式也能够实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更多的实现办法)
- postProcessBeforeInitialization 接口实现 - 初始化预处理:如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBeforeInitialization(Object obj, String s)办法,BeanPostProcessor 常常被用作是 Bean 内容的更改,并且因为这个是在 Bean 初始化完结时调用那个的办法,也能够被利用于内存或缓存技术。
- init-method:如果 Bean 在 Spring 配置文件中配置了 init-method 属性会主动调用其配置的初始化办法。
- postProcessAfterInitialization:如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)办法。
注:以上工作实现当前就能够利用这个 Bean 了,那这个 Bean 是一个 Singleton 的 - Destroy 过期主动清理阶段:当 Bean 不再须要时,会通过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy()办法;
- destroy-method 自配置清理:最初,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会主动调用其配置的销毁办法。
bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们能够本人定制初始化和登记办法。也有相应的注解(@PostConstruct 和 @PreDestroy)
<bean id=""class="" init-method="初始化办法" destroy-method="销毁办法">
依赖注入形式
对于 spring 配置一个 bean 时,如果须要给该 bean 提供一些初始化参数,则须要通过依赖注入形式,所谓的 依赖注入就是通过 spring 将 bean 所须要的一些参数传递到 bean 实例对象的过程(将依赖关系注入到对象中)
在创立新的 Bean 时,IoC 容器会主动注入新 Bean 的所依赖的其余 Bean,而无须本人手动创立。
-
结构器注入
private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC;
public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) {
this.dependencyA = dependencyA;
this.dependencyB = dependencyB;
this.dependencyC = dependencyC;
}
2. setter 办法注入
private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;
@Autowired
public void setDependencyA(DependencyA dependencyA) {
this.dependencyA = dependencyA;
}
@Autowired
public void setDependencyB(DependencyB dependencyB) {
this.dependencyB = dependencyB;
}
@Autowired
public void setDependencyC(DependencyC dependencyC) {
this.dependencyC = dependencyC;
}
3. 字段注入
@Autowired
private DependencyA dependencyA;
@Autowired
private DependencyB dependencyB;
@Autowired
private DependencyC dependencyC;
| 注入形式 | ** 长处 ** | ** 毛病 ** |
| --------------- | ---------------------------------------------------- | ------------------------------------------------------------ |
| 字段注入 | 简略,便于增加新的 dependency | 可能会呈现注入失败而呈现 NullPointedException;在 Test 和其余 Module 不可用;不可用于 final 字段,从而无奈保障字段的不变性。|
| setter 注入 | 灵活性高,便于批改依赖对象 | 对于仅应用 setter 注入的依赖对象须要进行非空查看;对象无奈在结构实现后马上进入就绪状态 |
| constructor 注入 | 对象在结构实现之后,即已进入就绪状态,能够马上应用。| 当依赖对象比拟多的时候,构造方法的参数列表会比拟长,保护和应用也比拟麻烦,依据繁多职责准则,此时应该思考重构了。应用不慎还有可能呈现循环依赖。|
***Spring4.x 之后,注入形式应该按需抉择 setter 或 constructor 注入形式。***
#### 主动拆卸形式
** 主动拆卸是为了将依赖注入“自动化”的一个简化配置的操作。**
当一个对象的属性是另一个对象时,实例化时,须要为这个对象属性进行实例化。这就是拆卸。Spring 拆卸包含手动拆卸和主动拆卸,手动拆卸是有基于 xml 拆卸、构造方法、setter 办法等;主动拆卸有五种主动拆卸的形式,能够用来领导 Spring 容器用主动拆卸形式来进行依赖注入。1. no:默认的形式是不进行主动拆卸,通过显式设置 ref 属性来进行拆卸。2. byName:通过参数名 主动拆卸,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname,之后容器试图匹配、拆卸和该 bean 的属性具备雷同名字的 bean。3. byType:通过参数类型主动拆卸,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、拆卸和该 bean 的属性具备雷同类型的 bean。如果有多个 bean 符合条件,则抛出谬误。4. constructor:这个形式相似于 byType,然而要提供给结构器参数,如果没有确定的带参数的结构器参数类型,将会抛出异样。5. autodetect:首先尝试应用 constructor 来主动拆卸,如果无奈工作,则应用 byType 形式。罕用的主动拆卸注解有以下几种:@Autowired,@Qualifier(@Resource, @Inject, @Named 为 JavaEE 的规范,不倡议应用)。**@Autowired** 注解是 byType 类型的,这个注解能够用在属性下面,setter 方面下面以及结构器下面。应用这个注解时,就不须要在类中为属性增加 setter 办法了。然而这个属性是强制性的,也就是说必须得拆卸上,如果没有找到适合的 bean 可能拆卸上,就会抛出异样:`NoSuchBeanDefinitionException,如果 required=false 时,则不会抛出异样。另一种状况是同时有多个 bean 是一个类型的,也会抛出这个异样。此时须要进一步明确要拆卸哪一个 Bean,这时能够组合应用 @Qualifier 注解,值为 Bean 的名字即可。**@Qualifier** 注解应用 byName 进行拆卸,这样能够在多个类型一样的 bean 中,明确应用哪一个名字的 bean 来进行拆卸。@Qualifier 注解起到了放大主动拆卸候选 bean 的范畴的作用。自动检测配置,也是 springmvc 中最牛的一项性能。只有一个配置 `<context:component-scan base-package="">` 或者注解 `@ComponentScan("")`
该配置会主动扫描指定的包及其子包上面被构造型注解标注的类,并将这些类注册为 spring bean,这样就不必在配置文件一个一个地配置成 bean 标签。构造型注解包含:@Controller,@Components,@Service,@Repository 和应用 @Component 标注的自定义注解。#### 循环依赖
依赖注入稍不留神就会呈现循环依赖:Bean 之间的依赖程序:BeanA -> BeanB -> BeanA
举个例子:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(CircularDependencyB circB) {this.circB = circB;}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(CircularDependencyA circA) {this.circA = circA;}
}
** 解决办法 **
呈现循环依赖是因为设计问题,** 最佳解决办法是从新设计 **。在理论开发中,推倒重来往往是不容许的,所以会有以下几种补救办法。1. 改用 setter 注入形式(举荐)
与 constructor 注入不同,setter 是按需注入的,并且容许依赖对象为 null;
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {this.circB = circB;}
public CircularDependencyB getCircB() {return circB;}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public void setCircA(CircularDependencyA circA) {this.circA = circA;}
}
2. @Lazy 注解(提早初始化)
// 先构建 CircularDependencyA 实现后,再构建 CircularDependencyB,突破 dependency circle。@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {this.circB = circB;}
}
```
-
应用 ApplicationContextAware, InitializingBean
// ApplicationContextAware 获取 SpringContext,用于加载 bean;InitializingBean 定义了设置 Bean 的 property 之后的动作。@Component public class CircularDependencyA implements InitializingBean, ApplicationContextAware { private CircularDependencyB circB; private ApplicationContext context; @Override public void afterPropertiesSet() throws Exception {this.circB = context.getBean(CircularDependencyB.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext;} public CircularDependencyB getCircularDependencyB() {return circB;} }
Spring 循环以来解决原理
https://cloud.tencent.com/dev…