构造器内抛空指针异常
问题
下面代码中,LightMgrService
管理LightService
控制宿舍灯的开启和关闭,让LightMgrService
初始化时能够自动调用LightService
的check
方法来检查所有宿舍灯的电路是否正常
@Component
public class LightMgrService {
@Autowired
private LightService lightService;
public LightMgrService() {
lightService.check();
}
}
@Service
public class LightService {
...
public void check() {
System.out.println("check all lights");
}
}
但是当启动项目后出现NullPointerException
原因
先了解Spring类初始化过程:
-
将一些必要的系统类(如Bean的后置处理器类)注册到Spring容器
-
将这些后置处理器实例化,并注册到Spring的容器中
-
实例化所有用户定制类,调用后置处理器进行辅助装配、类初始化等
- 即Spring初始化单例类的一般过程,基本都是
getBean()- >doGetBean()->getSingleton()
,如Bean不存在则调用createBean()->doCreateBean()
进行实例化,下面为doGetBean
方法:
// AbstractAutowireCapableBeanFactory#doCreateBean protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { ... if (instanceWrapper == null) { // 实例化Bean instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); ... Object exposedObject = bean; try { // 注入Bean依赖 populateBean(beanName, mbd, instanceWrapper); // 初始化Bean,如执行@PostConstruct标记的方法 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { ... } ... }
- 用来实例化Bean的
createBeanInstance
方法最终执行到instantiateClass
方法:
// BeanUtils#instantiateClass public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException { Assert.notNull(ctor, "Constructor must not be null"); try { ReflectionUtils.makeAccessible(ctor); if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) { return KotlinDelegate.instantiateClass(ctor, args); } else { Class<?>[] parameterTypes = ctor.getParameterTypes(); Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters"); Object[] argsWithDefaultValues = new Object[args.length]; for (int i = 0 ; i < args.length; i++) { if (args[i] == null) { Class<?> parameterType = parameterTypes[i]; argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null); } else { argsWithDefaultValues[i] = args[i]; } } // 最终执行该方法 return ctor.newInstance(argsWithDefaultValues); } } ... }
- 即Spring初始化单例类的一般过程,基本都是
对instantiateClass
方法进行分析后可看出,默认构造器是在类实例化的时候被自动调用,Spring无法控制,此时负责自动装配的populateBean
方法还没执行,所以LightMgrService
的属性LightService
还是null
解决方式
问题根源在于使用@Autowired
直接标记在成员属性上而引发的装配行为是发生在构造器执行之后,修改方案如下:
- 方案1:使用构造器参数来隐式注入(该部分知识点可参考Spring Bean常见错误-定义的Bean缺少隐式依赖)
// 构造器参数LightService会被自动注入LightService的Bean,从而在构造器执行时不会出现空指针
private LightService lightService;
public LightMgrService(LightService lightService) {
this.lightService = lightService;
lightService.check();
}
- 方案2:添加
init
方法,并使用PostConstruct
注解进行修饰
@Autowired
private LightService lightService;
@PostConstruct
public void init() {
lightService.check();
}
- 方案3:实现
InitializingBean
接口,并在其afterPropertiesSet
方法中执行初始化代码
@Component
public class LightMgrService implements InitializingBean {
@Autowired
private LightService lightService;
@Override
public void afterPropertiesSet() throws Exception {
lightService.check();
}
}
对于方案2和3进行以下分析:
- Spring在类属性完成注入后会回调用户定制的初始化方法,即在
populateBean
方法后会调用initializeBean
方法:
// AbstractAutowireCapableBeanFactory#initializeBean
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
...
// applyBeanPostProcessorsBeforeInitialization和invokeInitMethods分别处理@PostConstruct注解和InitializingBean接口的逻辑
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
...
}
- 分析
applyBeanPostProcessorsBeforeInitialization
与@PostConstruct
:
// InitDestroyAnnotationBeanPostProcessor#buildLifecycleMetadata(applyBeanPostProcessorsBeforeInitialization最终执行到这)
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
...
do {
final List<LifecycleElement> currInitMethods = new ArrayList<>();
final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
// 此处的this.initAnnotationType值为PostConstruct.class
// Spring将遍历查找被PostConstruct.class注解过的方法,返回到上层并最终调用此方法
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
...
});
...
}
- 分析
invokeInitMethods
与InitializingBean
接口
// AbstractAutowireCapableBeanFactory#invokeInitMethods
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
// 判断当前Bean是否实现了InitializingBean接口,实现了该接口才会调用该Bean的接口实现方法afterPropertiesSet()
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
...
else {
((InitializingBean) bean).afterPropertiesSet();
}
}
...
}
意外触发shutdown方法
问题
将上面的案例进行改造,去除LightService
的@Service
注解,使用配置类BeanConfiguration
来创建并注册LightService
类型的Bean(配置类就是用于创建一堆的Bean):
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");
}
}
@Configuration
public class BeanConfiguration {
@Bean
public LightService getTransmission(){
return new LightService();
}
}
同时在修改启动类,让Spring启动后立马关闭当前Spring的上下文:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
context.close();
}
}
按照预期代码运行后不会有任何输出,但实际却打印了shutting down all lights
,即执行了shutDown
方法
原因
只有通过使用@Bean
注册到Spring容器的对象才会在容器被关闭的时候自动调用shutdown
方法,而使用@Component
(即@Service
)将当前类自动注入到 Spring容器时该方法不会被自动执行
使用
@Bean的方法所注册的Bean对象,如果用户不设置destroyMethod
属性,其属性值为AbstractBeanDefinition.INFER_METHOD
(方案2的依据)- Spring会检查当前Bean对象的原始类中是否有名为
shutdown
或close
方法 - 如果有此方法会被Spring记录下来,并在容器被销毁时自动执行
// DisposableBeanAdapter#inferDestroyMethodIfNecessary
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.getDestroyMethodName();
// 如果destroyMethodName=INFER_METHOD
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// 且没实现DisposableBean接口
if (!(bean instanceof DisposableBean)) {
try {
// 尝试查找close方法
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
// 尝试查找shutdown方法
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
return null;
}
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
inferDestroyMethodIfNecessary
方法的调用链如下:
doCreateBean- >registerDisposableBeanIfNecessary->registerDisposableBean(new DisposableBeanAdapter)->inferDestroyMethodIfNecessary
- 分析
doCreateBean
方法,它包含了Bean的整个生命周期几乎所有的关键点-Bean实例的创建、Bean对象依赖的注入、定制类初始化方法的回调、Disposable方法的注册
// AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
...
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
- 分析
registerDisposableBean
方法:disposableBeans
将暂存DisposableBeanAdapter
实例,直到AnnotationConfigApplicationContext
的close
方法被调用
// DefaultSingletonBeanRegistry#registerDisposableBean
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}
- 当
AnnotationConfigApplicationContext
的close
方法被调用时(即Spring容器被销毁时),最终会调用destroySingleton
方法:遍历disposableBeans
属性获取DisposableBean
,并调用其close
方法或shutdown
方法
// DefaultSingletonBeanRegistry#destroySingleton
public void destroySingleton(String beanName) {
// Remove a registered singleton of the given name, if any.
removeSingleton(beanName);
// Destroy the corresponding DisposableBean instance.
DisposableBean disposableBean;
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
}
destroyBean(beanName, disposableBean);
}
为什么@Service
注入的LightService
的shutdown
方法没有被调用?因为该注解标记的LightService
没实现 AutoCloseable
、 DisposableBean
,也没添加DisposableBeanAdapter
- 想要执行
shutdown
方法须添加DisposableBeanAdapter
,而添加是有条件的:
// AbstractBeanFactory#registerDisposableBeanIfNecessary
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
// ①确定是否是单例 ②确定是否在需要在关闭时销毁(这一步返回了false)
if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
if (mbd.isSingleton()) {
registerDisposableBean(beanName,
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
}
else {
...
}
}
}
- 由于该案例都是单例,所以仅需考虑是否在需要在关闭时销毁:
// AbstractBeanFactory#requiresDestruction
protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
return (bean.getClass() != NullBean.class &&
// 关键方法
(DisposableBeanAdapter.hasDestroyMethod(bean, mbd) ||
(hasDestructionAwareBeanPostProcessors() &&
DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessors()))));
}
// DisposableBeanAdapter#hasDestroyMethod
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
// *使用@Service产生Bean时,类并没有实现AutoCloseable、DisposableBean
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
String destroyMethodName = beanDefinition.getDestroyMethodName();
// *使用@Service产生Bean时destroyMethodName是null,而使用使@Bean默认值为AbstractBeanDefinition.INFER_METHOD
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);
}
解决方式
- 方案1:避免在Java类中定义带有特殊意义动词的方法,即不使用
shutdown
进行命名 - 方案2:将Bean注解内
destroyMethod
属性设置为空的方式
@Configuration
public class BeanConfiguration {
@Bean(destroyMethod="")
public LightService getTransmission(){
return new LightService();
}
}
参考
https://github.com/jiafu1115/springissue/tree/master/src/main/java/com/spring/puzzle/class4
文章评论