正文
近,想实现一个功能:
利用钩子函数,实现系统退出的时候,处理一些后续的工作。例如释放资源,释放链接,释放线程池等重要资源。但是在我们使用的过程中,有可能会出现使用错误的情况。比如:添加钩子函数的时候,是有先后顺序的,这个时候,我们应当如何的处理呢?
一、钩子函数
在Java里面,存在一种函数,称为钩子函数,获取(添加)的方式是:
Runtime.getRuntime().addShutdownHook();
上面是获取以及添加一个钩子。
二、钩子函数执行的顺序
在Java中,添加钩子函数的顺序与执行的顺序相反。也就是说,后添加的是最先执行的。也就是说,是一个栈的结构,后进先出结构。
上面的执行验证了刚才所说的执行情况。
三、如何有效的管理钩子
在编写代码的时候,会有各个位置添加钩子函数,但是为了避免乱,这个时候,我们要实现一个管理钩子的方法。保证我们的顺序。
思路:我们使用双端队列实现,可以保证我们添加钩子函数的执行顺序。我们可以限定添加的行为,来满足具体业务的需求。
package com.bupt.hook; import java.util.Deque; import java.util.LinkedList; /** * @author breakpoint/赵先生 <zlgtop@163.com> * create on 2021/02/09 */ public final class HookContext { private static final Deque<Runnable> threadHooks = new LinkedList<Runnable>(); private static volatile Boolean canAdd = true; // 在最后添加钩子函数 public static void addHookLast(Runnable runnable) { if (canAdd) { threadHooks.addLast(runnable); } } // 在开始添加钩子函数 public static void addHookFirst(Runnable runnable) { if (canAdd) { threadHooks.addFirst(runnable); } } //将我们所有的hook 添加到系统 // 执行的顺序 fist -> last public static void addHookToSystemByCurrent() { canAdd = false; Runtime runtime = Runtime.getRuntime(); runtime.addShutdownHook(new Thread() { @Override public void run() { while (!threadHooks.isEmpty()) { Runnable hook = threadHooks.pollFirst(); if (null != hook) { hook.run(); } } } }); } }
四、什么时候添加到系统?
- 添加钩子函数在任何位置都是可以添加的
- 将所有钩子添加到系统是最后进行的,原因是猴子之间是存在先后顺序的。
一般我们项目都是采用Spring框架来实现项目,一起看一下项目的启动的流程:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
上面展示的是refresh函数,是spring框架得分启动的过程,我们要将钩子添加到系统,应当最后添加。所以,我们finishRefresh函数里面添加钩子的结束操作。
五、具体的操作
具体的实现可以利用ApplicationListener来实现具体的逻辑:
上面的操作方法是,接受系统初始化完成之后的操作,所有的bean都已经生成了,这时候,已经没有多余的钩子添加到我们的系统中,这样我们就可以保证我们的正确的顺序。