假设我们有一个天气预报业务,里面有个shutdown()函数:目的是如果调用了它,那么所有调用了天气业务的客户端都不能在使用这个功能。
往往我们都是通过以下方式去写这么个Bean的。
@Component
public class WeatherService {
public void shutdown() {
System.out.println("通知所有调用方,天气预报业务下线!");
}
}
假设我们有10太客户端,都使用了这个天气预报业务,那么我们模拟其中一台机器宕机:
@SpringBootApplication()
public class Main8080 {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Main8080.class, args);
context.close();
}
}
程序运行结果如下:

这样是没问题的。但是倘若将上述Bean的定义方式改为:记得去掉@Component注解
public class WeatherService {
public void shutdown() {
System.out.println("通知所有调用方,天气预报业务下线!");
}
}
@Configuration
public class MyConfig {
@Bean
public WeatherService getWeatherService(){
return new WeatherService();
}
}
程序运行结果如下:

我去,一台机器挂了,竟然触发了我们自定义的shutdown函数,导致所有的客户端,一共10台,都无法再享用天气预报业务了。很明显,这不是我们想要的结果。因为我们自定义的shutdown函数,其调用时机应该由我们自己在业务上控制,而不是程序关闭的时候自动被执行了。
首先,上述案例中,我们看到了两种Bean的定义方式:
@Component直接修饰。@Configuration修饰的配置类中自定义@Bean。而这种定义Bean方式的不同就导致了不同的结果。我们来分析下原因。
因为咱们的案例中,对应的shutdown函数是被错误的执行的,而且发生时机在于程序的关闭。因此我们可以想到是否和Bean的生命周期有关?比如Bean的销毁:destroy操作。 那么我们来看下@Bean引入的Bean有什么骚操作,我们看下@Bean注解的源码,
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
// ...省略
/**
* The optional name of a method to call on the bean instance upon closing the
* application context, for example a {@code close()} method on a JDBC
* {@code DataSource} implementation, or a Hibernate {@code SessionFactory} object.
* The method must have no arguments but may throw any exception.
* As a convenience to the user, the container will attempt to infer a destroy
* method against an object returned from the {@code @Bean} method. For example, given
* an {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource},
* the container will notice the {@code close()} method available on that object and
* automatically register it as the {@code destroyMethod}. This 'destroy method
* inference' is currently limited to detecting only public, no-arg methods named
* 'close' or 'shutdown'. The method may be declared at any level of the inheritance
* hierarchy and will be detected regardless of the return type of the {@code @Bean}
* method (i.e., detection occurs reflectively against the bean instance itself at
* creation time).
*
To disable destroy method inference for a particular {@code @Bean}, specify an
* empty string as the value, e.g. {@code @Bean(destroyMethod="")}. Note that the
* {@link org.springframework.beans.factory.DisposableBean} callback interface will
* nevertheless get detected and the corresponding destroy method invoked: In other
* words, {@code destroyMethod=""} only affects custom close/shutdown methods and
* {@link java.io.Closeable}/{@link java.lang.AutoCloseable} declared close methods.
*
Note: Only invoked on beans whose lifecycle is under the full control of the
* factory, which is always the case for singletons but not guaranteed for any
* other scope.
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
我们来借用有道翻译,来瞅瞅其中这么一段话:

但是恕我直言,这个翻译真的不咋滴,用我个人的话就是:通过@Bean方式修饰的类,如果这个类中含有名称为close或者shutdown的无参函数。那么容器就会将他们视为一种销毁方法。 (注意,光同名还不行,还要无参数)
那么根据源码,我们知道,如果我们没有手动定义destroyMethod属性,那么这个destroyMethod()这个属性值就是AbstractBeanDefinition.INFER_METHOD。那么我们全局搜下这个引用,最终在DisposableBeanAdapter中找到了:
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing 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);
}
}
那么这段代码究竟是哪里调用的呢?我们来说下老生常谈的东西:Bean的创建。Bean的创建我们都知道有三个步骤:
而在这之后其实还有一个步骤就和本案例有关了:
registerDisposableBeanIfNecessary(beanName, bean, mbd);
这段代码主要执行的是Disposable方法的注册。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
if (mbd.isSingleton()) {
// 记录当前Bean使用了哪一种destory方法,这些方法将会在程序close的时候调用
// 在这里是AnnotationConfigApplicationContext.close()。
registerDisposableBean(beanName,
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
}
// ...
}
}
}
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}
请记住最后的this.disposableBeans。而咱们的AnnotationConfigApplicationContext.close()函数,一个个调用链走下去,最终到达的地方是:
AnnotationConfigApplicationContext.close()
↓↓↓↓↓↓↓↓↓↓↓↓
public void destroySingletons() {
if (logger.isTraceEnabled()) {
logger.trace("Destroying singletons in " + this);
}
synchronized (this.singletonObjects) {
this.singletonsCurrentlyInDestruction = true;
}
String[] disposableBeanNames;
synchronized (this.disposableBeans) {
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
this.containedBeanMap.clear();
this.dependentBeanMap.clear();
this.dependenciesForBeanMap.clear();
clearSingletonCache();
}
可见这里复用了this.disposableBeans,里面保存的是Bean在创建过程中,注册的销毁方法。然后一个个去执行。因此我们程序关闭的时候,我们自定义的shutdown函数被执行了。
方案一:将对应的destroyMethod属性设置为空。避免其被赋值为默认的AbstractBeanDefinition.INFER_METHOD。
@Configuration
public class MyConfig {
@Bean(destroyMethod = "")
public WeatherService getWeatherService(){
return new WeatherService();
}
}
方案二:养成良好的编码习惯,避免命名函数或者字段的时候,使用这种带有特殊含义的名称。 这里就和我的另一篇文章Spring常见问题解决 - @Value注解注入的值出错了?很像了。
shutdown。username或者user.name等等。根据我们知道,对于Disposable方法的注册,是有一定条件需要满足的。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
if (mbd.isSingleton()) {
// 记录当前Bean使用了哪一种destory方法,这些方法将会在程序close的时候调用
// 在这里是AnnotationConfigApplicationContext.close()。
registerDisposableBean(beanName,
new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
}
// ...
}
}
}
我们看下requiresDestruction()函数:
protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
return (bean.getClass() != NullBean.class &&
(DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || (hasDestructionAwareBeanPostProcessors() &&
DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessors()))));
}
@Bean方式来注入Bean,并且不显式地指定对应的destroyMethod方法。close 和shutdown同名的无参方法视为这个Bean在生命周期结束时候执行的销毁方法。DisposableBeanAdapter.hasDestroyMethod这个判断为true。我们再来看下hasDestroyMethod函数:
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
// 如果使用的是@Bean(),依据本文案例的话,这里的destroyMethodName就是AbstractBeanDefinition.INFER_METHOD
// 如果使用的是其他方式注入的,这里的destroyMethodName就是null
String destroyMethodName = beanDefinition.getDestroyMethodName();
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);
}
那么,如果我们想要这样的Bean在程序关闭的时候也执行对应的shutdown函数咋办?
@Component
public class WeatherService {
public void shutdown() {
System.out.println("通知所有调用方,天气预报业务下线!");
}
}
我们关注下上文的hasDestroyMethod函数中这么一行代码:
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
如果Bean是AutoCloseable类型的也可以让其执行shutdown。那么我们只需要对程序做出以下更改:
@Component
public class WeatherService implements Closeable {
public void shutdown() {
System.out.println("通知所有调用方,天气预报业务下线!");
}
@Override
public void close() throws IOException {
shutdown();
}
}
可以得到相同的结果:

这是因为Closeable是AutoCloseable的一个子类:
public interface Closeable extends AutoCloseable {}
@Bean()引入某个类,注意,这里没有写什么参数。那么如果这个类中有和close、shutdown同名的函数。在程序关闭的时候会被自动执行。@Component、@Service等方式注入的类,一般不会有这样的情况。如果希望实现同等效果,可以实现Closeable接口,完成close()方法的具体实现。Bean的类型来的。if (bean instanceof DisposableBean || bean instanceof AutoCloseable)。除此之外,就看这个Bean的destroyMethodName是否为AbstractBeanDefinition.INFER_METHOD。那是因为:
public @interface Bean {
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}