文章整理来源:Spring编程常见错误50例_spring_spring编程_bean_AOP_SpringCloud_SpringWeb_测试_事务_Data-极客时间
案例9:构造器中提前使用未注入的 Bean
Spring 组件构造器中 引用了 注入 Bean ,报错 LightService 为 NULL 空指针异常
例子是在创建 LightMgrService 时,调用注入的 LightService 检查所有的灯
@Component
public class LightMgrService {
@Autowired
private LightService lightService;
public LightMgrService() {
lightService.check();
}
}
-------------------------------------------------------------------
@Service
public class LightService {
public void start() {
System.out.println("turn on all lights");
}
public void shutdown() {
System.out.println("turn off all lights");
}
public void check() {
System.out.println("check all lights");
}
}
Spring 初始化单例类的一般过程: getBean() -> doGetBean() -> getSingleton() ,如果 Bean 不存在,则调用 createBean() -> doCreateBean() 进行实例化
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
......
// createInstance() 创建 Bean 的实例
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
......
// Initialize the bean instance.
Object exposedObject = bean;
try {
// populateBean() 填充 Bean 的属性
populateBean(beanName, mbd, instanceWrapper);
// initializeBean() 初始化 Bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
......
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
......
return exposedObject;
}
在 doCreateBean() 方法中,1. 先 createInstance() 创建 Bean 的实例,如果定义了有参构造器,会使用有参构造器,否则,使用无参构造器;2. populateBean() 填充 Bean 的属性;3. initializeBean() 按顺序执行处理标注了 @PostConstruct 方法 、执行处理 实现了 InitializingBean 接口 的初始化方法。
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
......
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 执行处理 标注了 @PostConstruct 方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 执行处理 实现了 InitializingBean 接口 的初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
......
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
解决:1. 使用有参构造器,在构造其中先注入 LightService
2. 使用 @PostContstruct 标注 LightMgrService 的初始化方法
3. 实现 InitializingBean 接口
---------------- 1. 使用有参构造器,在构造其中先注入 LightService
@Component
public class LightMgrService {
private LightService lightService;
public LightMgrService(LightService lightService) {
this.lightService = lightService;
lightService.check();
}
}
---------------- 2. 使用 @PostConstruct
@Component
public class LightMgrService {
@Autowired
private LightService lightService;
@PostConstruct
public void init() {
lightService.check();
}
}
---------------- 3. 实现 InitializingBean 接口
@Component
public class LightMgrService implements InitializingBean {
@Autowired
private LightService lightService;
@Override
public void afterPropertiesSet() throws Exception {
lightService.check();
}
}
案例10: 意外触发 shutdown 方法
在 LightService 中实现 shutdown() 方法,并去掉 @Service 方式注入,改为在一个 Config 类中用 @Bean 方式注入
根据前面的叙述更改,在 SpringApplicationContext 调用 close() 方法关闭时,会触发 shutdown() 方法
// @Service
public class LightService {
//省略其他非关键代码
public void shutdown(){
System.out.println("shutting down all lights");
}
//省略其他非关键代码
}
-------------------------------------------------
@Configuration
public class BeanConfiguration {
@Bean
public LightService getTransmission(){
return new LightService();
}
}
解析:在通过使用 @Bean 方式注入,Spring 容器关闭时会自动调用 shutdown 方法,而使用 @Service 或 @Component 等则不会
使用 Bean 注解的方法所注册的 Bean 对象,如果用户不设置 destroyMethod 属性,则其属性值为 AbstractBeanDefinition.INFER_METHOD。此时 Spring 会检查当前 Bean 对象的原始类中是否有名为 shutdown 或者 close 的方法,如果有,此方法会被 Spring 记录下来,并在容器被销毁时自动执行;当然如若没有,那么自然什么都不会发生。
可以说 doCreateBean 管理了 Bean 的整个生命周期中几乎所有的关键节点,直接负责了 Bean 对象的生老病死,其主要功能包括:
1. Bean 实例的创建;
2. Bean 对象依赖的注入;
3. 定制类初始化方法的回调;
4. Disposable 方法的注册。
解决: 1. 避免定义 close 或 shutdown 方法
2. 在 @Bean 中设置 destroyMethod 属性为 空
@Configuration
public class BeanConfiguration {
@Bean(destroyMethod="")
public LightService getTransmission(){
return new LightService();
}
}
若想保持用 @Service ,且在 Spring 关闭时,调用 close 方法,可以让 LightService 实现 Closeable 接口方法。
@Service
public class LightService implements Closeable {
public void close() {
System.out.println("turn off all lights);
}
//省略非关键代码
}
为什么 @Service 注入的 LightService,其 shutdown 方法不能被执行?
如果我们是使用 @Service 来产生 Bean 的,那么在 hasDestroyMethod() 方法代码中我们获取的 destroyMethodName 其实是 null;而使用 @Bean 的方式,默认值为 AbstractBeanDefinition.INFER_METHOD,参考 @Bean 注解的定义
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
}
return StringUtils.hasLength(destroyMethodName);
}
-----------------------------------------------------------------------------------
public @interface Bean {
//省略其他非关键代码
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}