没事不要乱写close和shutdown方法,搞不好线上就出个大bug

导读:本篇文章讲解 没事不要乱写close和shutdown方法,搞不好线上就出个大bug,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

在Spring项目中,我们在定义一个bean的时候,可能会随手写一个close或者shutdown方法去关闭一些资源。但是有时候这两个看起来很正常的方法名,即使我们不添加任何特殊配置,也可能会给我们带来潜在的bug。

问题复现

通过一个简单的bean重现一下这个问题。

定义一个系统配置类,在某些条件下,我们会调用这个类的close方法去执行一些关闭资源的动作。

@Data
public class SystemConfig {

    private String config;
    private String type;
    //....省略其他属性

    //一个普通的close方法,没有做任何特殊配置
    public void close(){
        //在某些条件下,关闭一些系统资源,不仅局限于本系统
        System.out.println("开始关闭>>>");
    }
}

通过@Bean将这个类注入到Spring容器中:

@SpringBootApplication(scanBasePackages = "com.shishan.demo2023.*")
public class Demo2023Application {

    public static void main(String[] args) {
        SpringApplication.run(Demo2023Application.class, args);
    }

    @Bean
    public SystemConfig systemConfig(){
        SystemConfig systemConfig = new SystemConfig();
        systemConfig.setConfig("config");
        return systemConfig;
    }

}

在很长一段时间内,这段代码都执行得很好。但是有一次系统意外停机时,bug发生了。

没事不要乱写close和shutdown方法,搞不好线上就出个大bug

通过上图可以看到,close方法在没有任何主动调用的情况下,被Spring自动执行了。。。

原理探究

先说结论:问题主要出现在@Bean注解的destroyMethod属性上。

没事不要乱写close和shutdown方法,搞不好线上就出个大bug

我们点开@Bean注解,在destroyMethod方法上,可以看到一段注释。

翻译过来的意思就是:

为了方便用户,容器将尝试针对从 @Bean方法返回的对象推断destroy方法。例如,给定一个 @Bean方法返回一个Apache Commons DBCP BasicDataSource,容器将注意到该对象上可用的close() 方法,并自动将其注册为destroyMethod。

简单来说,当使用@Bean注解时,如果destroyMethod属性没有设置值,Spring会自动检查通过@Bean方法注入的对象是否包含close方法或者shutdown方法,如果有,则将其注册为destroyMethod,并且在bean被销毁时自动调用该方法。

通过搜索destroyMethod的默认值AbstractBeanDefinition.INFER_METHOD的引用,我们可以在DisposableBeanAdapter类中的inferDestroyMethodIfNecessary方法找到Spring是如何判断close方法的。

@Nullable
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
  String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
  if (destroyMethodName == null) {
    destroyMethodName = beanDefinition.getDestroyMethodName();
    boolean autoCloseable = (bean instanceof AutoCloseable);
    //如果destroyMethod没有定义,而且是默认值
    if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
        (destroyMethodName == null && autoCloseable)) {
      destroyMethodName = null;
      if (!(bean instanceof DisposableBean)) {
        //并且没有实现DisposableBean接口
        if (autoCloseable) {
          destroyMethodName = CLOSE_METHOD_NAME;
        }
        else {
          try {
            //先找close方法
            destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
          }
          catch (NoSuchMethodException ex) {
            try {
              //如果close方法没找到,就尝试找shutdown方法
              destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
            }
            catch (NoSuchMethodException ex2) {
              // no candidate destroy method found
            }
          }
        }
      }
    }
    beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
  }
  return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}

通过以上源码我们可以看出,Spring会尝试先找close方法,再找shutdown方法,如果找到了,就将其设置为destroyMethod,如果都没有找到,那就不做处理。

总得来说,建议避免在Java类中定义一些带有特殊意义动词的方法,当然如果在线上运行的类已经定义了close或者shutdown方法另作他用,也可以通过将Bean注解内destroyMethod属性设置为显示指定其他方法的方式来解决这个问题。

最后

在实际项目中,用@Bean方式注入的,一般都是第三方包的类。这些第三方包中的类由于没有强依赖Spring,所以无法直接使用@Component、@Service将类注入容器。而且这些类在容器销毁的时候可能也有一些后置处理的需求,为了保持黑盒,Spring就采用这种默认的配置帮助我们执行一些后置处理。如果我们作为第三方开发,建议能够了解这种机制,以免出现一些意想不到的bug。

学习技术,分享技术,期待与大家共同进步,也感谢您的点赞与关注。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84844.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!