Bean类型, 作用域, 实例化, 生命周期
Bean类型
在 SpringFramework 中,对于 Bean 的类型,一般有两种设计:
- 普通Bean
- FactoryBean
普通Bean
三种方式
- @Component注解
- 配置类+@Bean
- xml + <bean>
@Component
public class Child {}
@Bean
public Child child() {
return new Child();
}
<bean class="com.linkedbear.spring.bean.a_type.bean.Child"/>
FactoryBean
出现场景: SpringFramework考虑到一些特殊的设计: Bean的创建需要指定一些策略,或者依赖特殊的场景来分别创建,也或者一个对象的创建过程太复杂,使用xml或者注解声明也比较复杂。可以借助**FactoryBean
**来使用工厂方法创建对象
FactoryBean介绍
FactoryBean
本身是一个接口,它本身就是一个创建对象的工厂。如果 Bean 实现了 FactoryBean
接口,则它本身将不再是一个普通的Bean ,不会在实际的业务逻辑中起作用,而是由创建的对象来起作用
FactoryBean
接口有三个方法:
- getObject(): 返回创建的对象
- getObjectType(): 返回创建的对象的类型(即泛型类型)
- isSingleton(): 创建的对象是单实例Bean还是原型Bean,默认单实例
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
FactoryBean使用
场景: 小孩子要买玩具,由一个玩具生产工厂来给这个小孩子造玩具
前置准备
孩子类
public class Child {
// 当前的小孩子想玩球
private String wantToy = "ball";
public String getWantToy() {
return wantToy;
}
}
一个抽象类和两个实现类
@Data
@NoArgsConstructor
public abstract class Toy { // 抽象玩具类
/*
玩具名
*/
private String name;
public Toy(String name) {
System.out.println("生产了一个" + name);
this.name = name;
}
}
@Data
@EqualsAndHashCode(callSuper = true)
public class Ball extends Toy { // 球
public Ball(String name) {
super(name);
}
}
@Data
@EqualsAndHashCode(callSuper = true)
public class Car extends Toy {
public Car(String car) {
super(name);
}
}
运行示例
创建一个 ToyFactoryBean
,让它实现 FactoryBean
接口, 同时希望能根据小孩子想要玩的玩具来决定生产哪种玩具, 那么这里就需要注入Child
。由于咱这里面使用的不是注解式自动注入, 那咱就用setter注入
@Setter
public class ToyFactoryBean implements FactoryBean<Toy> {
public ToyFactoryBean() {
System.out.println("ToyFactoryBean 初始化了。。。");
}
/*
Child类
*/
private Child child;
@Override
public Toy getObject() throws Exception {
// 根据Child中的wantToy属性, 来决定创建哪个玩具
switch (child.getWantToy()) {
case "ball":
return new Ball("ball");
case "car":
return new Car("car");
default:
return null;
}
}
/*
表示这个工厂类返回Toy类
*/
@Override
public Class<Toy> getObjectType() {
return Toy.class;
}
}
希望能让它根据小孩子想要玩的玩具来决定生产哪种玩具,那咱就得在这里面注入Child
。由于咱这里面使用的不是注解式自动注入,那咱就用setter注入吧
注解方式注册这两个
@Configuration
public class BeanTypeConfiguration {
@Bean
public Child child() {
return new Child();
}
@Bean
public ToyFactoryBean toyFactory() {
ToyFactoryBean toyFactory = new ToyFactoryBean();
toyFactory.setChild(child());
return toyFactory;
}
}
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
Toy toy = ctx.getBean(Toy.class);
// ToyFactoryBean 初始化了。。。
// Ball()
System.out.println(toy);
}
}
FactoryBean与Bean同时存在
修改配置类BeanTypeConfiguration
, 向IOC容器预先的创建一个Ball
, 这样 FactoryBean
再创建一个, IOC容器里就会同时存在两个 Toy
了
@Configuration
public class BeanTypeConfiguration {
@Bean
public Child child() {
return new Child();
}
// ---------- Start ----------
@Bean
public Toy ball() {
return new Ball("ball");
}
// ---------- End ----------
@Bean
public ToyFactoryBean toyFactory() {
ToyFactoryBean toyFactory = new ToyFactoryBean();
toyFactory.setChild(child());
return toyFactory;
}
}
再次运行BeanTypeAnnoApplication
类, 发现抛出NoUniqueBeanDefinitionException
异常, 提示有两个 Toy
了
异常信息如下
Exception in thread “main” org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.linkedbear.spring.bean.a_type.bean.Toy’ available: expected single matching bean but found 2: ball,toyFactory
异常表明**FactoryBean
创建的Bean是直接放在IOC容器中了**
查看IOC 容器中现有的 Toy
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
// 获取指定类型的所有Bean,并返回Map集合
Map<String, Toy> toys = ctx.getBeansOfType(Toy.class);
toys.forEach((name, toy) -> {
System.out.println("toy name : " + name + ", " + toy.toString());
});
/*
输出结果如下:
ToyFactoryBean 初始化了。。。
toy name : ball, Ball()
toy name : toyFactory, Ball()
*/
}
}
FacotryBean的加载和创建Bean的时机
FactoryBean的加载时机
之前的准备已经铺垫好了
Toy的构造方法中调用System打印
public class Toy {
public Toy(String name) {
System.out.println("生产了一个" + name);
this.name = name;
}
ToyFactoryBean
的默认构造方法中也有System打印
public ToyFactoryBean() {
System.out.println("ToyFactoryBean 初始化了。。。");
}
修改BeanTypeAnnoApplication
的main方法, 只初始化IOC容器
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
}
}
控制台输出结果如下: ToyFactoryBean 初始化了。。。
, 这表示只有ToyFactoryBean
被初始化, 说明**FactoryBean
本身的加载是伴随IOC容器的初始化时机一起的**
Factory创建Bean的时机
经过之前的代码, 发现如果只初始化IOC容器, 那么此时是Facotry没有创建Bean的(即有了玩具工厂, 但是玩具工厂还没有生产出玩具), 说明 FactoryBean
中要创建的 Bean 还没有被加载, 也就得出: FactoryBean
生产Bean的机制是延迟生产
修改BeanTypeAnnoApplication
的main方法, 添加获取玩具的方法
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
Toy toy = ctx.getBean(Toy.class);
}
}
控制台打印结果如下, 表明了也就是获取Bean的时候, Factory才会生产Bean
ToyFactoryBean 初始化了。。。
生产了一个ball
FactoryBean创建Bean的实例数
FactoryBean
接口中有一个默认的方法 isSingleton
, 默认是 true , 代表默认是单实例的
调用两次getBean(), 判断两次对象是否一致
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
Toy toy1 = ctx.getBean(Toy.class);
Toy toy2 = ctx.getBean(Toy.class);
// 打印结果: true
System.out.println(toy1 == toy2);
}
}
获取FactoryBean本体
拿 Toy
本体去取, 取到的都是 FactoryBean
生产的 Bean 。一般情况下咱也用不到 FactoryBean
本体, 但如果真的需要取, 使用的方法也很简单: 要么直接传 FactoryBean
的 class (很容易理解), 也可以传 ID 。不过, 如果真的靠传ID的话, 传配置文件 / 配置类声明的 ID 就不好使了, 因为那样只会取出生产出来的 Bean
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
// 打印结果: com.linkedbear.spring.bean.a_type.bean.ToyFactoryBean@d29f28
System.out.println(ctx.getBean("toyFactory"));
}
}
取 FactoryBean
的方式, 需要在Bean的ID前面加&符号
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
// 打印结果: Toy{name='ball'}
System.out.println(ctx.getBean("&toyFactory"));
}
}
BeanFactory与FactoryBean的区别
BeanFactory
: SpringFramework 中实现 IOC的最底层容器(此处的回答可以从两种角度出发: 从类的继承结构上看, 它是最顶级的接口, 也就是最顶层的容器实现;从类的组合结构上看, 它则是最深层次的容器, ApplicationContext
在最底层组合了 BeanFactory
)
FactoryBean
: 创建对象的工厂Bean, 可以使用它来直接创建一些初始化流程比较复杂的对象
Bean作用域
为什么会出现多种不同的作用域呢
肯定是它可以被使用的范围不同了。那为什么不都统一成一样的作用范围呢?说白了, 资源是有限的, 如果一个资源允许同时被多个地方访问(如全局常量), 那就可以把作用域提的很高;反之, 如果一个资源伴随着一个时效性强的, 带强状态的动作, 那这个作用域就应该局限于这一个动作, 不能被这个动作之外的干扰。
SpringFramework中内置的作用域
SpringFramework 中内置了 6 种作用域(5.x 版本):
作用域类型 | 概述 |
---|---|
singleton | 一个 IOC 容器中只有一个【默认值】 |
prototype | 每次获取创建一个 |
request | 一次请求创建一个(仅Web应用可用) |
session | 一个会话创建一个(仅Web应用可用) |
application | 一个 Web 应用创建一个(仅Web应用可用) |
websocket | 一个 WebSocket 会话创建一个(仅Web应用可用) |
划分为三类,
- singleton(Spring核心)
- peorotype(Spring核心)
- web应用领域: request, session, application, websocket
singleton
SpringFramework中默认所有的 Bean 都是单实例的, 即: 一个IOC容器中只有一个
前置准备
创建Child
和 Toy
共两个类
@Data
public class Child {
private Toy toy;
}
// Toy中标注@Component注解, 表示给Spring托管
@Component
public class Toy {}
创建配置类, 并注册两个Child类, 表示两个小孩
@Configuration
@ComponentScan("com.linkedbear.spring.bean.b_scope.bean")
public class BeanScopeConfiguration {
@Bean
public Child child1(Toy toy) {
Child child = new Child();
child.setToy(toy);
return child;
}
@Bean
public Child child2(Toy toy) {
Child child = new Child();
child.setToy(toy);
return child;
}
}
运行示例
获取其中的 Child
, 打印里面的 Toy
public class BeanScopeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanScopeConfiguration.class);
ctx.getBeansOfType(Child.class)
.forEach((name, child) -> {
System.out.println(name + ":" + child);
});
/*
打印结果:
Toy constructor run ...
child1:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@4b0d79fc)
child2:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@4b0d79fc)
*/
}
}
可以看到上述两个child的内存地址都一样, 证明默认情况下, Bean 的作用域是单实例的
prototype: 原型Bean
Spring官方的定义是: 每次对原型Bean提出请求时, 都会创建一个新的Bean实例。 这里面提到的"提出请求" , 包括任何依赖查找, 依赖注入的动作, 都算做一次"提出请求"。
由此咱也可以总结一点: 如果连续 getBean()
两次, 那就应该创建两个不同的Bean实例;向两个不同的Bean中注入两次, 也应该注入两个不同的Bean实例
其实对于原型这个概念, 在设计模式中也是有对应的: 原型模式。原型模式实质上是使用对象深克隆, 乍看上去跟 SpringFramework 的原型 Bean 没什么区别, 但咱仔细想, 每一次生成的原型Bean本质上都还是一样的, 只是可能带一些特殊的状态等等, 这个可能理解起来比较抽象, 可以跟下面的 request 域结合着理解。
前置准备和运行示例
修改Toy
类: 给 Toy
的类上标注一个额外的注解: @Scope
, 并声明为原型类型
@Component
@Scope("prototype")
// @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 这种写法也行, 建议使用常量
public class Toy {}
这个 prototype 不是随便写的常量, 而是在 ConfigurableBeanFactory
中定义好的常量
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
// 省略其它代码
}
重新运行BeanScopeAnnoApplication
类, 打印结果如下
child1:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@273e7444)
child2:Child(toy=com.linkedbear.spring.bean.b_scope.bean.Toy@7db12bb6)
原型Bean的创建时机
单实例 Bean 的创建咱已经知道, 是在 ApplicationContext
被初始化时就已经创建好了, 那这些原型 Bean 又是什么时候被创建的呢?其实也不难想出, 它都是什么时候需要, 什么时候创建
从以下角度时候, 原型Bean每次创建都是新, 即是初始化就创建也无济于事
修改Toy, 添加一个无参构造方法
@Component
@Scope("prototype")
public class Toy {
public Toy() {
System.out.println("Toy constructor run ...");
}
}
修改BeanScopeAnnoApplication
, 只让它扫描bean包(注意Toy类上要添加@Component注解), 不加载配置类, 这样就相当于只有一个Toy类被扫描进去了, Child 不会注册到 IOC 容器中
public class BeanScopeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.bean.b_scope.bean");
// 控制台什么也没打印, 因为没有Toy的使用需求嘛, 它当然不会被创建
}
}
Web应用领域的作用域
- request: 请求Bean, 每次客户端向 Web 应用服务器发起一次请求, Web 服务器接收到请求后, 由 SpringFramework 生成一个 Bean , 直到请求结束
- session: 会话Bean, 每个客户端在与 Web 应用服务器发起会话后, SpringFramework 会为之生成一个 Bean , 直到会话过期
- application: 应用Bean, 每个 Web 应用在启动时, SpringFramework 会生成一个 Bean , 直到应用停止(有的也叫 global-session )
- websocket: WebSocket Bean , 每个客户端在与 Web 应用服务器建立 WebSocket 长连接时, SpringFramework 会为之生成一个 Bean , 直到断开连接
Bean实例化方式
普通Bean实例化
所有<bean>
标签@Bean
注解的方式, 都是普通Bean的对象, 它们默认是单实例, 在 IOC 容器初始化时就已经被初始化了
借助FactoryBean创建Bean
普通Bean+FactoryBean
public class Ball {}
/*
Ball的FacotryBean
*/
public class BallFactoryBean implements FactoryBean<Ball> {
/*
返回Bean
*/
@Override
public Ball getObject() {
return new Ball();
}
/*
返回Bean的类型
*/
@Override
public Class<Ball> getObjectType() {
return Ball.class;
}
}
@Configuration
public class BeanConfiguration {
/*
注册BallFactoryBean
*/
@Bean
public BallFactoryBean ballFactoryBean() {
return new BallFactoryBean();
}
}
注册Bean时, 只需要注入FactoryBean
, IOC容器会自动识别, 并默认在第一次获取时创建对应的Bean并缓存(针对默认的单实例 FactoryBean
)
借助静态工厂创建Bean
测试示例
创建Bean+静态工厂
public class Car {
public Car() {
System.out.println("Car constructor run ...");
}
}
/*
静态类工厂
*/
public class CarStaticFactory {
public static Car getCar() {
return new Car();
}
}
配置xml
静态工厂的使用通常运用于 xml 方式比较多(主要是注解驱动没有直接能让它起作用的注解, 编程式配置又可以直接调用, 显得没那么大必要)
<!--注册Car-->
<bean id="car1" class="com.linkedbear.spring.bean.c_instantiate.bean.Car"/>
<!--注册工厂类, 并指定创建对象的工厂方法-->
<bean id="car2" class="com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory" factory-method="getCar"/>
运行启动类进行测试
public class BeanInstantiateXmlApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean/bean-instantiate.xml");
ctx.getBeansOfType(Car.class).forEach((beanName, car) -> {
System.out.println(beanName + " : " + car);
});
/*
控制台打印结果如下:
Car constructor run ...
Car constructor run ...
car1 : com.linkedbear.spring.bean.c_instantiate.bean.Car@5b239d7d
car2 : com.linkedbear.spring.bean.c_instantiate.bean.Car@6572421
*/
}
}
即是创建两个Car, 一个Car的Bean, 一个CarStaticFactory
中getCar()
生产的Bean
静态工厂是否在IOC容器内
car2注册到IOC容器内了, 静态工厂是否会像FacotryBean一样注册到IOC容器中, 尝试从IOC容器中获取试试
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean/bean-instantiate.xml");
ctx.getBeansOfType(Car.class).forEach((beanName, car) -> {
System.out.println(beanName + " : " + car);
});
// 尝试取一下试试
System.out.println(ctx.getBean(CarInstanceFactory.class));
}
运行, 发现程序抛出了 NoSuchBeanDefinitionException
异常:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory' available
结论: 静态工厂本身不会被注册到 IOC 容器中
编程式使用静态工厂
由于 SpringFramework 中并没有提供关于静态工厂相关的注解, 所以只能使用注解配置类+编程式使用静态工厂了, 而这个使用方式相当的简单
@Configuration
public class BeanConfiguration {
@Bean
public Car car2() {
// 直接 类.方法 调用即可
return CarStaticFactory.getCar();
}
}
借助实例工厂创建Beans
测试示例
创建实例工厂
创建一个CarInstanceFactory
代表实例工厂, 它跟静态工厂唯一的区别是方法不再是static方法了
public class CarInstanceFactory {
public Car getCar() {
return new Car();
}
}
配置xml
对于实例工厂, 要想调用对象的方法, 那自然得先把对象实例化才行了, 所以咱就需要先在 xml中注册实例工厂, 随后才能创建真正的目标 Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="carInstanceFactory" class="com.linkedbear.spring.bean.c_instantiate.bean.CarInstanceFactory"/>
<bean id="car3" factory-bean="carInstanceFactory" factory-method="getCar"/>
</beans>
发现car3
的<bean>
标签可以不传入class属性, 用 factory-bean
和 factory-method
属性也可以完成Bean的创建
运行启动类
public class BeanInstantiateXmlApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean/bean-instantiate.xml");
ctx.getBeansOfType(Car.class).forEach((beanName, car) -> {
System.out.println(beanName + " : " + car);
});
}
}
实例工厂是否在IOC容器中
在, 实例工厂的<bean>中都注册了, 那么必然是在IOC容器当中的
编程式使用实例工厂
@Configuration
public class BeanConfiguration {
@Bean
public Car car3(CarInstanceFactory carInstanceFactory) {
return carInstanceFactory.getCar();
}
}
Bean生命周期(简易版)
Bean生命周期阶段划分
一个对象从被创建, 到被垃圾回收, 可以宏观的划分为 5 个阶段:
- 创建/实例化阶段: 此时会调用类的构造方法, 产生一个新的对象
- 初始化阶段: 此时对象已经创建好, 但还没有被正式使用, 可能这里面需要做一些额外的操作(如预初始化数据库的连接池)
- 运行使用期: 此时对象已经完全初始化好, 程序正常运行, 对象被使用
- 销毁阶段: 此时对象准备被销毁, 已不再使用, 需要预先的把自身占用的资源等处理好(如关闭, 释放数据库连接)
- 回收阶段: 此时对象已经完全没有被引用了, 被垃圾回收器回收
Spring能干预的生命周期阶段(针对单例Bean)
生命周期阶段 | 是否能干预 |
---|---|
创建/实例化 | |
初始化 | √ |
运行使用 | |
销毁 | √ |
回收 |
SpringFramework如何能让我们干预Bean的初始化和销毁
Servlet
里面有两个方法, 分别叫init
和destroy
, 被Web容器(Tomcat等)调用的吧, 用来初始化和销毁Servlet的
方法的设计思想其实就是回调机制, 它都不是自己设计的, 而是由父类/接口定义好的, 由第三者(框架, 容器等)来调用
生命周期的触发, 更适合叫回调, 因为生命周期方法是咱定义的, 但方法被调用, 是框架内部帮我们调的, 那也就可以称之为"回调"了
init-method&desotry-method
Bean的初始化和销毁阶段做一个额外操作
前置准备
创建Cat和Dog类
@Data
public class Cat {
private String name;
public void init() {
System.out.println(name + "被初始化了。。。");
}
public void destroy() {
System.out.println(name + "被销毁了。。。");
}
}
@Data
public class Dog {
private String name;
public void init() {
System.out.println(name + "被初始化了。。。");
}
public void destroy() {
System.out.println(name + "被销毁了。。。");
}
}
创建xml配置文件/配置类
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.linkedbear.spring.lifecycle.a_initmethod.bean.Cat"
init-method="init" destroy-method="destroy">
<property name="name" value="mimi"/>
</bean>
</beans>
- init-method: 指向初始化方法
- destroy-method: 执行销毁方法
配置类
@Configuration
public class InitMethodConfiguration {
@Bean(initMethod = "init", destroyMethod = "destroy")
public Dog dog() {
Dog dog = new Dog();
dog.setName("wangwang");
return dog;
}
}
initMethod和destroyMethod的作用的同理
初始化和销毁方法的特征
- 方法访问权限无限制要求(SpringFramework底层会反射调用的)
- 方法无参数(如果真的设置了参数, SpringFramework也不知道传什么进去)
- 方法无返回值(返回给 SpringFramework 也没有意义)
- 可以抛出异常(异常不由自己处理, 交予SpringFramework可以打断Bean的初始化/销毁步骤)
运行示例
分别初始化xml和注解驱动的容器, 不过需要注意的是, 这次咱接收的类型不再用 ApplicationContext
, 而是用实现类本身, 目的是为了调用 close
方法对容器进行关闭, 以触发 Bean 的销毁动作
xml驱动
public class InitMethodXmlApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("lifecycle/bean-initmethod.xml");
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
/*
控制台输出结果如下:
Cat 构造方法执行了。。。
setName方法执行了。。。
mimi被初始化了。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
mimi被销毁了。。。
IOC容器销毁完成。。。
*/
}
}
结论:
- 在 IOC 容器初始化之前, 默认情况下Bean已经创建好了, 而且完成了初始化动作;容器调用销毁动作时, 先销毁所有Bean, 最后IOC容器全部销毁完成
- Bean 的生命周期中, 是先对属性赋值, 后执行
init-method
标记的方法
initMethod和destroyMethod方法作用范围如下图
注解驱动
public class InitMethodAnnoApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(InitMethodConfiguration.class);
System.out.println("IOC容器初始化完成。。。");
Thread.sleep(3000);
ctx.close();
}
}
控制台打印结果如下
wangwang被初始化了。。。
IOC容器初始化完成。。。
wangwang被销毁了。。。
JSR250规范: @PostConstruct和@PreDestroy
之前手动声明注册的Bean, 对于那些使用模式注解的Bean, 没有可以让你声明 init-method
和 destroy-method
的地方了, @Component
注解上也只有一个 value
属性而已
JSR250 规范中除了有 @Resource
这样的自动注入注解, 还有负责生命周期的注解, 包括 @PostConstruct
, @PreDestroy
两个注解, 分别对应init-method
和destroy-method
使用示例
创建Bean
钢笔与墨水, 刚买来的动作代表实例化, 加墨水的动作代表初始化, 倒掉所有墨水的动作代表销毁
对于JSR250规范的这两个注解的使用, 直接标注在Bean的方法上即可
@Data
@Component
public class Pen {
/*
墨水含量
*/
private Integer ink;
/*
添加墨水
*/
@PostConstruct // 实现了initMethod相似的作用, 方法声明一样的, 修饰符可以是private
public void addInk() {
System.out.println("钢笔中已加满墨水。。。");
this.ink = 100;
}
/*
倒掉所有墨水
*/
@PreDestroy // 实现了destroyMethod相似的作用, 方法声明一样的, 修饰符可以是private
public void outwellInk() {
System.out.println("钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
}
启动
public class JSR250AnnoApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JSR250Configuration.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
/*
控制台打印结果如下:
准备初始化IOC容器。。。
@PostConstruct - 钢笔中已加满墨水。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
destory-method - 合上钢笔。。。
IOC容器销毁完成。。。
*/
}
}
如下图
JSR250规范与init-method共存
如果不使用@Component
注解来注册Bean而转用<bean>
/@Bean
的方式, 那@PostConstruct
与@PreDestroy
注解是可以与init-method
/destroy-method
共存的
前期准备
新建一个Pen2
, 方法如下
@Data
@Component
public class Pen2 {
/**
* 墨水含量
*/
private Integer ink;
public void open() {
System.out.println("init-method - 打开钢笔。。。");
}
public void close() {
System.out.println("destory-method - 合上钢笔。。。");
}
@PostConstruct // JSR250规范
public void addInk() {
System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy // JSR250规范
public void outwellInk() {
System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
}
配置类注册Pen2
, 并使用指定initMethod和destroyMethod
@Configuration
public class JSR250Configuration {
@Bean(initMethod = "open", destroyMethod = "close")
public Pen2 pen() {
return new Pen2();
}
}
运行示例
public class JSR250AnnoApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JSR250Configuration.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
/*
控制条打印结果:
准备初始化IOC容器。。。
@PostConstruct - 钢笔中已加满墨水。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
destory-method - 合上钢笔。。。
IOC容器销毁完成。。。
*/
}
}
结论: JSR250规范的执行优先级高于init/destroy
如下图
InitializingBean&DisposableBean
实际上是两个接口, 而且是Spring内部预先定义好的两个关于生命周期的接口。他们的触发时机与上面的init-method
/destroy-method
以及JSR250规范的两个注解一样, 都是在Bean的初始化和销毁阶段要回调的
前置准备
使用Pen
作为演示对象, 实现InitializingBean和DisposableBean接口
@Data
@Component
public class Pen implements InitializingBean, DisposableBean {
/*
墨水含量
*/
private Integer ink;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("钢笔中已加满墨水。。。");
this.ink = 100;
}
@Override
public void destroy() throws Exception {
System.out.println("钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
}
配置类不变
@Configuration
public class InitializingDisposableConfiguration {
@Bean(initMethod = "open", destroyMethod = "close")
public Pen3 pen() {
return new Pen3();
}
}
运行示例
public class InitializingDisposableAnnoApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.lifecycle.c_initializingbean.bean");
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
/*
控制台打印结果:
准备初始化IOC容器。。。
钢笔中已加满墨水。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
钢笔中的墨水都放干净了。。。
IOC容器销毁完成。。。
*/
}
}
三种生命周期并存
复制出一个 Pen
来, 命名为 Pen3
, 并同时实现三种生命周期的控制, Servelt的init/destory, JSR250规范的@PostConstruct和@PreDestroy, Spring的InitializingBean和DisposableBean
@Data
public class Pen3 implements InitializingBean, DisposableBean {
private Integer ink;
public void open() {
System.out.println("init-method - 打开钢笔。。。");
}
public void close() {
System.out.println("destroy-method - 合上钢笔。。。");
}
@PostConstruct // JSR250规范
public void addInk() {
System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy // JSR250规范
public void outwellInk() {
System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
// Spring
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean - 准备写字。。。");
}
// Spring
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean - 写完字了。。。");
}
}
注解驱动方式注册这个 Pen3
@Configuration
public class InitializingDisposableConfiguration {
// Sevlet
@Bean(initMethod = "open", destroyMethod = "close")
public Pen3 pen() {
return new Pen3();
}
}
public class InitializingDisposableAnnoApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(InitializingDisposableConfiguration.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
/*
控制台打印:
准备初始化IOC容器。。。
@PostConstruct - 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
*/
}
}
执行顺序才是最关键的:@PostConstruct
→ InitializingBean
→ init-method
如下图
prototype(原型)Bean的生命周期
单实例Bean的生命周期是陪着IOC容器一起的, 容器初始化, 单实例Bean也跟着初始化(当然不绝对, 后面会介绍延迟Bean);容器销毁, 单实例Bean也跟着销毁。原型Bean由于每次都是取的时候才产生一个, 所以它的生命周期与IOC容器无关
前置准备
Pen类
@Data
public class Pen implements InitializingBean, DisposableBean {
private Integer ink;
public void open() {
System.out.println("init-method - 打开钢笔。。。");
}
public void off() {
System.out.println("destroy-method - 合上钢笔。。。");
}
@PostConstruct
public void addInk() {
System.out.println("@PostConstruct - 钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy
public void outwellInk() {
System.out.println("@PreDestroy - 钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean - 准备写字。。。");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean - 写完字了。。。");
}
}
配置类
@Configuration
public class PrototypeLifecycleConfiguration {
@Bean(initMethod = "open", destroyMethod = "off")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 注意这里的Bean范围是prototype
public Pen pen() {
return new Pen();
}
}
IOC容器初始化时原型Bean不初始化
public class PrototypeLifecycleApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
PrototypeLifecycleConfiguration.class);
System.out.println("IOC容器初始化完成。。。");
}
}
控制台只打印了 IOC容器初始化完成。。。
表明了原型 Bean 的创建不随 IOC 的初始化而创建
原型Bean的初始化动作与单实例Bean一致
修改main方法, 获取一次Pen实例
public class PrototypeLifecycleAnnoApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
PrototypeLifecycleConfiguration.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备获取一个Pen。。。");
Pen pen = ctx.getBean(Pen.class);
/*
控制台打印结果如下:
IOC容器初始化完成。。。
准备获取一个Pen。。。
@PostConstruct - 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
*/
}
}
三种初始化的动作都执行了, 证明原型Bean的初始化动作与单实例Bean完全一致, 注意这里强调了是初始化动作
原型Bean的销毁不包括destroy-method
在 main
方法中再添加几行代码, 将这个 Pen 销毁掉
public class PrototypeLifecycleAnnoApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
PrototypeLifecycleConfiguration.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备获取一个Pen。。。");
Pen pen = ctx.getBean(Pen.class);
System.out.println("已经取到了Pen。。。");
System.out.println();
System.out.println("用完Pen了, 准备销毁。。。");
ctx.getBeanFactory().destroyBean(pen);
System.out.println("Pen销毁完成。。。");
/*
控制台打印结果如下:
IOC容器初始化完成。。。
准备获取一个Pen。。。
@PostConstruct - 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
已经取到了Pen。。。
用完Pen了, 准备销毁。。。
@PreDestroy - 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
Pen销毁完成。。。
*/
}
}
控制台中只打印了 @PreDestroy
注解和 DisposableBean
接口的执行, 没有触发 destroy-method
的执行
原型Bean在销毁时不处理destroy-method
标注的方法, 可以从以下角度理解, 由于destroy-method
方法是Servlet
的,原型Bean不知道是否还继续生产, 所以Servlet
容器不能销毁
SpringFramework中控制Bean生命周期的三种方式
init-method & destroy-method | @PostConstruct & @PreDestroy | InitializingBean & DisposableBean | |
---|---|---|---|
执行顺序 | 最后 | 最先 | 中间 |
组件耦合度 | 无侵入(只在 <bean> 和 @Bean 中使用) | 与 JSR 规范耦合 | 与 SpringFramework 耦合 |
容器支持 | xml 、注解原生支持 | 注解原生支持, xml需开启注解驱动 | xml 、注解原生支持 |
单实例Bean | √ | √ | √ |
原型Bean | 只支持 init-method | √ | √ |
如下图
参考资料
从 0 开始深入学习 Spring