Spring 50例常见错误(四)

文章整理来源: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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值