springBean声明周期常见错误
案例1:构造器内抛空指针异常
如上面这段代码:
@Service
@Slf4j
public class LigntService {
public void start(){
log.info("---------------------turn on all alignts------------------");
}
public void shutdown(){
log.info("---------------------turn off all lignts-------------------");
}
public void check(){
log.info("-------------------check all alight---------------------");
}
}
package start;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.annotation.HttpMethodConstraint;
@Component
public class LightMgrService {
@Autowired
private LigntService ligntService;
public LightMgrService(){
ligntService.check();
}
}
我们的期待是们的期待是在 LightMgrService 初始化过程中,LightService 因为标记为 @Autowired,所以能被自动装配好;然后在 LightMgrService 的构造器执行中,LightService 的 shutdown() 方法能被自动调用;最终打印出 check all lights。然而事与愿违,报错了
这是为什么呢?
spring类初始化大致可以分为三个部分:
第一部分,将一些必要的系统类,比如 Bean 的后置处理器类,注册到 Spring 容器,其中就包括我们这节课关注的 CommonAnnotationBeanPostProcessor 类;
第二部分,将这些后置处理器实例化,并注册到 Spring 的容器中;
第三部分,实例化所有用户定制类,调用后置处理器进行辅助装配、类初始化等等。
需要补充的是:
1.很多必要的系统类,尤其是 Bean 后置处理器(比如 CommonAnnotationBeanPostProcessor、AutowiredAnnotationBeanPostProcessor 等),都是被 Spring 统一加载和管理的,并在 Spring 中扮演了非常重要的角色;
2.通过 Bean 后置处理器,Spring 能够非常灵活地在不同的场景调用不同的后置处理器,比如接下来我会讲到示例问题如何修正,修正方案中提到的 PostConstruct 注解,它的处理逻辑就需要用到 CommonAnnotationBeanPostProcessor(继承自 InitDestroyAnnotationBeanPostProcessor)这个后置处理器。
重点第三部分:spring初始化单例类的一般过程,基本上都是getBean()-doGetBean()-getSingleton(),如果发现Bean不存在,则调用createBean()-doCreateBean()进行实例化。
查看doCreateBean()的源码如下:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
//省略掉部分代码
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
//省略部分代码
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
}
//省略掉部分代码
return exposedObject;
}
通过上述代码展示了Bean初始化的三个关键步骤:
- 对应实例化Bean -----------------createBeanInstance
- 注入Bean -------------------populateBean
- 初始化Bean ------------------initializeBean
备注,因为我的版本和文章的版本不一样,我就用图片的方式显示一下调用顺序
最终会执行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);
}
}
catch (InstantiationException ex) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
}
catch (IllegalAccessException ex) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
}
catch (IllegalArgumentException ex) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
}
catch (InvocationTargetException ex) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
}
}
因为当前的语言并非 Kotlin,所以最终将调用 ctor.newInstance() 方法实例化用户定制类 LightMgrService,而默认构造器显然是在类实例化的时候被自动调用的,Spring 也无法控制。而此时负责自动装配的 populateBean 方法还没有被执行,LightMgrService 的属性 LightService 还是 null,因而得到空指针异常也在情理之中。
问题原因就是:在使用了@Autowired直接标记在成员属性上而引发的装配行为是发生在构造器之后就行的。可以修改成:
@Component
public class LightMgrService {
private LigntService ligntService;
public LightMgrService(LigntService ligntService){
this.ligntService=ligntService;
ligntService.check();
}
}
当使用上面的代码时,构造器参数 LightService 会被自动注入 LightService 的 Bean,从而在构造器执行时,不会出现空指针。可以说,使用构造器参数来隐式注入是一种 Spring 最佳实践,因为它成功地规避了案例 1 中的问题。