从源码分析Spring销毁Bean的三种方式

1.背景

目前项目上云之后从原有的服务注册中心consul改为nacos,因此希望实现优雅重启服务的方式,当服务重启时可以将服务从nacos下线,同时能通知到消费端服务更新Ribbon的注册列表,避免出现请求异常的情况,后来发现行不通,还是要依赖ribbon的重试机制进行重新发起请求,但是研究优雅停机时研究了下nacos是如何停止容器并销毁Bean的。

2.销毁Bean的三种方式

2.1.注解PreDestroy方式

通过停止服务发现Nacos销毁Bean的方式是通过@PreDestroy的方式进行销毁的。

image.png

通过日志输出可以看到Nacos调用了deregister方法进行销毁Bean,因此看下该方法源码如下:

public class NacosServiceRegistry implements ServiceRegistry<Registration> {
    //...省略
    @Override
    public void deregister(Registration registration) {
        //输出的日志就是这个
        log.info("De-registering from Nacos Server now...");

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No dom to de-register for nacos client...");
            return;
        }

        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        String serviceId = registration.getServiceId();
        String group = nacosDiscoveryProperties.getGroup();

        try {
            namingService.deregisterInstance(serviceId, group, registration.getHost(),
                    registration.getPort(), nacosDiscoveryProperties.getClusterName());
        }
        catch (Exception e) {
            log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",
                    registration.toString(), e);
        }

        log.info("De-registration finished.");
    }
}

现在来看deregistry的调用方,该方法的调用方其实是SpringCloud中的方法,SpringCloud中实现容器的关闭和JVM的关闭,只不过最终的实现是由Nacos实现。源码如下(该源码来源SpringCloud):

public abstract class AbstractAutoServiceRegistration<R extends Registration>
        implements AutoServiceRegistration, ApplicationContextAware,
        ApplicationListener<WebServerInitializedEvent> {
    //...省略部分代码
    //最初的调用是SpringCloud提供的方法,该方法使用了@PreDestroy注解
    //该注解是JDK提供的一个注解,用于注销jvm的一种方式,Spring对该注解进行捕获处理
    @PreDestroy
    public void destroy() {
        stop();
    }

    //调用deregistry的逻辑方法
    public void stop() {
        if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
            //调用deregistry方法
            deregister();
            if (shouldRegisterManagement()) {
                //该方法最终也是调用deregistry方法
                deregisterManagement();
            }
            this.serviceRegistry.close();
        }
    }
}

看完SpringCloud注销JVM后,现在看Nacos如何实现的deregister的,首先SpringCloud提供了ServiceRegistry接口,其他服务注册中心只要实现该接口,即可完成自己的服务注册,注销等操作。从源码中可以知道Consul和Nacos各自实现了该接口。

image.png

接口代码如下:

public interface ServiceRegistry<R extends Registration> {

    void register(R registration);

    void deregister(R registration);

    void close();

    void setStatus(R registration, String status);

    <T> T getStatus(R registration);

}

Nacos实现代码如下:

public class NacosServiceRegistry implements ServiceRegistry<Registration> {

    //...省略部分代码

    //注册服务Nacos实现逻辑
    @Override
    public void register(Registration registration) {

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
            return;
        }

        String serviceId = registration.getServiceId();
        String group = nacosDiscoveryProperties.getGroup();

        Instance instance = getNacosInstanceFromRegistration(registration);

        try {
            namingService.registerInstance(serviceId, group, instance);
            log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
                    instance.getIp(), instance.getPort());
        }
        catch (Exception e) {
            log.error("nacos registry, {} register failed...{},", serviceId,
                    registration.toString(), e);
            // rethrow a RuntimeException if the registration is failed.
            // issue : <https://github.com/alibaba/spring-cloud-alibaba/issues/1132>
            rethrowRuntimeException(e);
        }
    }
    //注销服务Nacos实现逻辑
    @Override
    public void deregister(Registration registration) {

        log.info("De-registering from Nacos Server now...");

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No dom to de-register for nacos client...");
            return;
        }

        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        String serviceId = registration.getServiceId();
        String group = nacosDiscoveryProperties.getGroup();

        try {
            namingService.deregisterInstance(serviceId, group, registration.getHost(),
                    registration.getPort(), nacosDiscoveryProperties.getClusterName());
        }
        catch (Exception e) {
            log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",
                    registration.toString(), e);
        }

        log.info("De-registration finished.");
    }
}

因此以上就是Nacos关闭Bean容器以及注销Bean的一种方式,该方式是最简洁的方式进行实现的,直接借助@PreDestroy注解即可实现。

现在写一个测试类测试下该注解,代码如下:

@Component
public class DestroyBean {

    @PreDestroy
    public void destroyBean() {
        System.out.println("销毁bean! by @PreDestroy"+System.currentTimeMillis());
    }
}

随便写一个测试类启动即可:

@RunWith(SpringRunner.class)
@Slf4j
@SpringBootTest(classes = TestApplication.class)
public class DestroyTest implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Before
    public void before() throws Exception {
    }

    @After
    public void after() throws Exception {

    }

    @Rule
    public TestWatcher watchman = new TestWatcher() {

        @Override
        protected void starting(Description description) {
            super.starting(description);
            log.info("<<<<<<<开始执行测试:{}", description.getMethodName());
        }

        @Override
        protected void finished(Description description) {
            super.finished(description);
            log.info("{}执行完毕>>>>>>>", description.getMethodName());
        }

        @Override
        protected void failed(Throwable e, Description description) {
            super.failed(e, description);
            log.error("-----方法执行失败,异常:{}", e.getMessage());
        }
    };

    @Test
    public void testHandleTransport() throws Exception {
        //TODO: Test goes here...
        applicationContext.publishEvent(new CustomEvent(applicationContext));
    }
}

image.png

运行测试类的方法后,可以看到服务关闭时最终执行了加了PreDestroy注解的方法,这样就可以知道只要加入该注解,那么就可以在其中进行销毁Bean的操作。

2.2.事件订阅方式

Spring中提供了事件订阅的功能,即在平时可以使用的实现ApplicationListener,即可以创建实现类实现ApplicationListener接口,然后重写其中的onApplicationEvent方法,具体实现如下:

@Component
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {

    @SneakyThrows
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("关闭事件:" + this.getClass().getName()+"时间:"+System.currentTimeMillis());
        Thread.sleep(10000);
    }
}

通过该方式注销Bean时,当容器关闭时会调用到该方法中,那么在该方法中可以自己实现注销Bean的逻辑。那么Spring时如何提供事件注销Bean的功能的呢?Spring提供了一种关闭容器的方式即ShutdownHook的方式,而事件订阅的方式就是通过调用shutdownHook的方式进行关闭的,shutdownHook是开启一个线程进行执行的。源码如下:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    //执行shutdownhook
    @Override
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            // No shutdown hook registered yet.
            //初始化一个线程执行doClose方法
            this.shutdownHook = new Thread() {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }

    protected void doClose() {
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing " + this);
            }

            LiveBeansView.unregisterApplicationContext(this);

            try {
                //发布ClosedEvent事件
                publishEvent(new ContextClosedEvent(this));
            }
            //...省略
     }
}

当发布ContextClosedEvent事件时,最终会调用到SimpleApplicationEventMulticaster实现类中,具体逻辑如下:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    //进入colsedEvent调用方
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
    //
    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            doInvokeListener(listener, event);
        }
    }
    //最终的调用方
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            //最终执行到实现ApplicationListener并重写onApplicationEvent方法中
            listener.onApplicationEvent(event);
        }
        //...省略
    }

}

执行demo代码,可以看到Spring调用了ContextClosedEventListener的方法逻辑。

image.png

但是这里还有个问题就是该逻辑被执行了两次,该问题还没定位到为啥会执行两次,后续会补充。

2.3.实现 DisposableBean方式

Spring中暴露了初始化Bean和销毁Bean的两个接口,分别是InitializingBean和DisposableBean,因此在平时开发中可以使用这两个接口进行初始化Bean和销毁Bean,那么DisposableBean的销毁Bean在Spring中如何实现的呢?

首先看实现了DisposableBean的实现类是如何被Spring捕捉的,源码如下:

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
        //...省略
        try {
            //将实现DisposableBean的实现类注入到disposableBeans容器中
            registerDisposableBeanIfNecessary(beanName, bean, mbd);
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
        }

        return exposedObject;
    }

该代码是创建Bean的逻辑,在创建Bean完成后执行的DisposableBean的注入,具体逻辑封装在抽象类中,如下:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    //执行注入DisposableBean的逻辑
    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()) {
                //注入时并不是直接使用DisposableBean,而是使用了DisposableBean的适配器,DisposableAdapter,注入时通过new DisposableAdapter的方式将DisposableBean的实现类注入到disposableBeans容器中。
                registerDisposableBean(beanName,
                        new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
            }
            else {
                // A bean with a custom scope...
                Scope scope = this.scopes.get(mbd.getScope());
                if (scope == null) {
                    throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
                }
                scope.registerDestructionCallback(beanName,
                        new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
            }
        }
    }
}

以上代码中通过组装需要注入的DisposableAdapter对象,该对象实现了DisposableBean,具体注入逻辑如下:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    //注入逻辑,容器名称为disposableBeans
    public void registerDisposableBean(String beanName, DisposableBean bean) {
        synchronized (this.disposableBeans) {
            this.disposableBeans.put(beanName, bean);
        }
    }
}

以上都是注入到容器中的逻辑,接下来销毁Bean时是如何处理的,销毁Bean的方式依然通过shutdownHook的方式,shutdownHook逻辑中通过执行destroyBeans执行销毁,逻辑代码如下:

    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            // No shutdown hook registered yet.
            this.shutdownHook = new Thread() {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }
		//执行销毁方式,该逻辑中封装了事件方式销毁和DisposableBean方式销毁
    protected void doClose() {
        //...省略部分代码
            try {
                //事件订阅方法执行销毁Bean的方式
                publishEvent(new ContextClosedEvent(this));
            }
            //...省略
            //销毁Bean
            destroyBeans();
            //...省略
    }

    //通过beanFactory调用销毁单例Bean
    protected void destroyBeans() {
        getBeanFactory().destroySingletons();
    }
    //销毁单例Bean的逻辑
    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--) {
						//通过遍历的方式销毁Bean,主要遍历disposableBeanNames
            destroySingleton(disposableBeanNames[i]);
        }

        this.containedBeanMap.clear();
        this.dependentBeanMap.clear();
        this.dependenciesForBeanMap.clear();

        clearSingletonCache();
    }

    //该逻辑中封装了移除容器中的Bean以及销毁DisposableBean容器
    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);
    }
    //销毁Bean对象的逻辑,该逻辑中对循环依赖也同步做了处理
    protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
        Set<String> dependencies;
        synchronized (this.dependentBeanMap) {
            dependencies = this.dependentBeanMap.remove(beanName);
        }
        if (dependencies != null) {
            //循环依赖的时候的销毁逻辑
            for (String dependentBeanName : dependencies) {
                destroySingleton(dependentBeanName);
            }
        }
        if (bean != null) {
            try {
                bean.destroy();
            }
            catch (Throwable ex) {
                if (logger.isInfoEnabled()) {
                    logger.info("Destroy method on bean with name '" + beanName + "' threw an exception", ex);
                }
            }
        }
        //...省略部分代码
    }

通过Demo验证如下:

@Component
public class DestroyBeanImpl implements InitializingBean,DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("destroy bean by DisposableBean");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Initializing bean by InitializingBean");
    }
}

image.png

以上是通过DisposableBean的方式进行注销Bean的,该过程中可以看到,Spring主要将实现了DisposableBean的Bean进行注入到专属容器中,当需要销毁时,通过获取容器中的bean,然后通过调用实现类的destroy方法进行注销,这样对外提供了对Bean注入以及销毁的扩展性,对内封装了各个执行逻辑以及确定了操作的容器。

3.总结

以上就是Spring销毁Bean的三种方式,三种方式各有春秋,平时开发中可以随意使用,我将三种方式进行测试。

image.png


作者:陈汤姆
链接:从源码分析Spring销毁Bean的三种方式 - 掘金