文章目录
- 【探索Spring底层】SpringBean初始化销毁与Scope
- 1. Spring Bean初始化与销毁
- 2. Scope的类型
- 3. Scope失效问题及其解决
- 3.1 @Lazy
- 3.2 proxyMode
- 3.3 ObjectFactory
- 3.4 ApplicationContext
【探索Spring底层】SpringBean初始化销毁与Scope
1. Spring Bean初始化与销毁
Spring Bean的初始化方法有三种
- 通过@PostConstruct注解标注在这个Bean内方法上
- 实现InitializingBean接口,重写afterPropertiesSet方法
- 利用@Bean注解指定该Bean中的某个方法为初始化方法
@Bean(initMethod = "init3")
public class Bean1 implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
@PostConstruct
public void init1() {
log.debug("初始化1");
}
@Override
public void afterPropertiesSet() throws Exception {
log.debug("初始化2");
}
public void init3() {
log.debug("初始化3");
}
}
Spring Bean销毁的方法对应也是有三种
- 通过@PreDestroy标注Bean中某个方法作为销毁方法
- 实现DisposableBean接口,并重写destroy方法作为销毁方法
- 通过注入Bean时的@Bean注解的destroyMethod属性指明销毁方法
@Bean(destroyMethod = "destroy3")
public Bean2 bean2() {
return new Bean2();
}
public class Bean2 implements DisposableBean {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
@PreDestroy
public void destroy1() {
log.debug("销毁1");
}
@Override
public void destroy() throws Exception {
log.debug("销毁2");
}
public void destroy3() {
log.debug("销毁3");
}
}
既然有三种方法初始化和销毁,那么它们肯定有顺序
对于初始化来说,三种方法执行顺序如下:
- @PostConstruct
- 实现InitializingBean接口
- @Bean(initMethod = “init3”)
而对于销毁来说,三种方法的执行顺序如下:
- @PreDestroy
- 实现DisposableBean接口
- @Bean(destroyMethod = “destroy3”)
2. Scope的类型
Scope是Spring中的一个关键属性,其指定了Spring中的Bean的生命周期,也可以理解为Spring容器的创建方法。
一般来说我们使用得最多得是singleton,也就是单例模式,这个也是Spring中Bean的默认Scope
Scope有五种:
- singleton:容器启动时创建(未设置延迟),容器关闭时销毁
- prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
- request,每次请求用到此 bean 时创建,请求结束时销毁
- session,每个会话用到此 bean 时创建,会话结束时销毁
- application,web 容器用到此 bean 时创建,容器停止时销毁
3. Scope失效问题及其解决
模拟场景出现
@ComponentScan("com.itheima.a08.sub")
public class A08_1 {
private static final Logger log = LoggerFactory.getLogger(A08_1.class);
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A08_1.class);
E e = context.getBean(E.class);
log.debug("{}", e.getF1().getClass());
log.debug("{}", e.getF1());
log.debug("{}", e.getF1());
log.debug("{}", e.getF1());
context.close();
}
@Component
public class E {
@Autowired
private F1 f1;
public F1 getF1() {
return f1;
}
}
@Scope("prototype")
@Component
public class F1 {
}
运行上面的程序,会发现打印的bean均为同一个,但是这个设置F1的Scope为prototype
这个F1希望的是多例,但是输出的确实同一个,也就是单例
这是因为它们是同一个对象,而不是期望的多例对象
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
e 创建
e set 注入 f
f 创建
想要解决这个也很简单,一共有四种方法解决
3.1 @Lazy
第一种则是使用需要使用一个@Lazy注解来生成代理
使用该注解代理之后,代理对象虽然还是同一个,但是使用代理对象的任意方法时候,由代理创建新的f对象
使用f方法
使用f方法
使用f方法
e 创建
e set 注入 f代理
f 创建
f 创建
f 创建
@Component
public class E {
@Autowired
@Lazy
private F1 f1;
public F1 getF1() {
return f1;
}
}
注意
- @Lazy 也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
- @Autowired 加在 set 方法的目的类似
@Component
public class E {
@Autowired
@Lazy
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
// ...
}
3.2 proxyMode
在使用@Scope定义F1时候,直接在 @Scope 注解加上proxyMode = ScopedProxyMode.TARGET_CLASS
(其实底层本质也是自定义 TargetSource)
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
3.3 ObjectFactory
ObjectFactory是一个普通的对象工厂接口,其是一个函数式接口
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
里面的getObject是可以从对应域中获取指定的对象
@Component
public class E {
@Autowired
private ObjectFactory<F1> f1;
public F1 getF1() {
return f1.getObject();
}
}
3.4 ApplicationContext
最后一种就是最常见的ApplicationContext,因为F1已经注册在Spring容器中了,只需要调用ApplicationContextg的getBean方法就可以获取到多例子
@Component
public class E {
@Autowired
private ApplicationContext context;
public F1 getF1() {
return context.getBean(F1.class);
}
}