当前位置 博文首页 > 好好学习天天向上:Spring编程常见错误--Spring Core篇-04 |Spri

    好好学习天天向上:Spring编程常见错误--Spring Core篇-04 |Spri

    作者:[db:作者] 时间:2021-07-15 21:57

    四、Spring Bean 生命周期常见错误

    一.构造器内抛空指针异常:构造器执行之后才进行@AutoWired的装配。

    1、代码

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    @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");
        }
    }
    

    从整个案例代码实现来看,我们的期待是在 LightMgrService 初始化过程中,LightService 因为标记为 @Autowired,所以能被自动装配好;然后在 LightMgrService 的构造器执行中,LightService 的 shutdown() 方法能被自动调用;最终打印出 check all lights。
    然而事与愿违,我们得到的只会是 NullPointerException,错误示例如下:

    在这里插入图片描述

    2、案例分析

    Spring启动步骤

    在这里插入图片描述
    启动步骤可以分为三个部分

    (1).第一部分:启动必要的系统类

    将一些必要的系统类,比如 Bean 的后置处理器类,注册到 Spring 容器,如CommonAnnotationBeanPostProcessor 类;

    (2).第二部分

    将后置处理器实例化,并注册到 Spring 的容器中;

    (3).第三部分:最后一步才对定制类进行实例化。

    实例化所有用户定制类,调用后置处理器进行辅助装配、类初始化等等。

    后半部分我没看懂…但是结论是
    使用 @Autowired 直接标记在成员属性上而引发的装配行为是发生在构造器执行之后的。
    也就是对于这部分代码,先执行的是它的构造方法,之后才是@Autowired进行装配。所以是找不到lightService.check()这个方法的。

    @Component
    public class LightMgrService {
      @Autowired
      private LightService lightService;
      public LightMgrService() {
        lightService.check();
      }
    }
    

    3、问题修正

    1.取消自动装配,以构造器参数隐式带入。

    @Component
    public class LightMgrService {
        private LightService lightService;
        public LightMgrService(LightService lightService) {
            this.lightService = lightService;
            lightService.check();
        }
    }
    

    2.@PostConstruct注解

    @PostConstruct修饰的方法,在服务器加载servlet时运行,且只运行一次。在构造函数之后,init函数之前执行。
    通常在spring中使用它的加载顺序为:
    Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

    @Component
    public class LightMgrService {
      @Autowired
      private LightService lightService;
      
      @PostConstruct
      public void init() {
           lightService.check();
      }
    }
    

    2.InitializingBean

    InitializingBean和@PostConstruct注解本质上没区别,推荐使用注解。

    @Component
    public class LightMgrService implements InitializingBean {
        @Autowired
        private LightService lightService;
      
        @Override
        public void afterPropertiesSet() throws Exception {
            lightService.check();
        }
    }
    

    二.意外触发 shutdown 方法:@Bean会自动在类的方法中寻找shutdown或者close方法

    @Bean方法在容器进行重启的时候,会调用shutdown方法。

    1、代码

    1.@Service方式注入

    @Service
    public class LightService {
      //省略其他非关键代码
      public void shutdown(){
        System.out.println("shutting down all lights");
      }
      //省略其他非关键代码
    }
    

    1.@Configuration+@Bean方式注入

    @Configuration
    public class BeanConfiguration {
        @Bean
        public LightService getTransmission(){
            return new LightService();
        }
    }
    

    2、问题分析

    使用 Bean 注解的方法所注册的 Bean 对象,如果用户不设置 destroyMethod 属性,则其属性值为AbstractBeanDefinition.INFER_METHOD。此时 Spring 会检查当前 Bean 对象的原始类中是否有名为 shutdown 或者 close 的方法,如果有,此方法会被 Spring 记录下来,并在容器被销毁时自动执行;当然如若没有,那么自然什么都不会发生。

    private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
       String destroyMethodName = beanDefinition.getDestroyMethodName();
       if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||(destroyMethodName == null && bean instanceof AutoCloseable)) {
          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);
    }
    

    3、问题修正

    1. 避免在 Java 类中定义一些带有特殊意义动词的方法来解决
    2. 如果一定要定义名为 close 或者 shutdown 方法,也可以通过将 Bean 注解内 destroyMethod 属性设置为空的方式来解决这个问题。
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    @Configuration
    public class BeanConfiguration {
        @Bean(destroyMethod="")
        public LightService getTransmission(){
            return new LightService();
        }
    }
    
    
    cs