Spring编程常见错误50例-Spring Bean生命周期常见错误

构造器内抛空指针异常

问题

下面代码中,LightMgrService管理LightService控制宿舍灯的开启和关闭,让LightMgrService初始化时能够自动调用LightServicecheck方法来检查所有宿舍灯的电路是否正常

@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);
            }
        }
        ...
    }
    

instantiateClass方法进行分析后可看出,默认构造器是在类实例化的时候被自动调用,Spring无法控制,此时负责自动装配的populateBean方法还没执行,所以LightMgrService的属性LightService还是null

解决方式

问题根源在于使用@Autowired直接标记在成员属性上而引发的装配行为是发生在构造器执行之后,修改方案如下:

// 构造器参数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);
                }
            }
            ...
        });

    ...
}
  • 分析invokeInitMethodsInitializingBean接口
// 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对象的原始类中是否有名为shutdownclose方法
  • 如果有此方法会被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实例,直到AnnotationConfigApplicationContextclose方法被调用
// DefaultSingletonBeanRegistry#registerDisposableBean
public void registerDisposableBean(String beanName, DisposableBean bean) {
    synchronized (this.disposableBeans) {
        this.disposableBeans.put(beanName, bean);
    }
}
  • AnnotationConfigApplicationContextclose方法被调用时(即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注入的LightServiceshutdown方法没有被调用?因为该注解标记的LightService没实现 AutoCloseableDisposableBean,也没添加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();
    }
}

参考

极客时间-Spring 编程常见错误 50 例

https://github.com/jiafu1115/springissue/tree/master/src/main/java/com/spring/puzzle/class4

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值