使用java钩子函数的技巧

简介: 使用java钩子函数的技巧

正文


近,想实现一个功能:

利用钩子函数,实现系统退出的时候,处理一些后续的工作。例如释放资源,释放链接,释放线程池等重要资源。但是在我们使用的过程中,有可能会出现使用错误的情况。比如:添加钩子函数的时候,是有先后顺序的,这个时候,我们应当如何的处理呢?

一、钩子函数

在Java里面,存在一种函数,称为钩子函数,获取(添加)的方式是:

Runtime.getRuntime().addShutdownHook();

上面是获取以及添加一个钩子。

二、钩子函数执行的顺序

在Java中,添加钩子函数的顺序与执行的顺序相反。也就是说,后添加的是最先执行的。也就是说,是一个栈的结构,后进先出结构。


43.png

上面的执行验证了刚才所说的执行情况。

三、如何有效的管理钩子

在编写代码的时候,会有各个位置添加钩子函数,但是为了避免乱,这个时候,我们要实现一个管理钩子的方法。保证我们的顺序。

思路:我们使用双端队列实现,可以保证我们添加钩子函数的执行顺序。我们可以限定添加的行为,来满足具体业务的需求。

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();
                    }
                }
            }
        });
    }
}

四、什么时候添加到系统?

  1. 添加钩子函数在任何位置都是可以添加的
  2. 将所有钩子添加到系统是最后进行的,原因是猴子之间是存在先后顺序的。

一般我们项目都是采用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来实现具体的逻辑:

45.png

上面的操作方法是,接受系统初始化完成之后的操作,所有的bean都已经生成了,这时候,已经没有多余的钩子添加到我们的系统中,这样我们就可以保证我们的正确的顺序。

相关文章
|
4月前
|
Java
java基础(11)函数重载以及函数递归求和
Java支持函数重载,即在同一个类中可以声明多个同名方法,只要它们的参数类型和个数不同。函数重载与修饰符、返回值无关,但与参数的类型、个数、顺序有关。此外,文中还展示了如何使用递归方法`sum`来计算两个数之间的和,递归的终止条件是当第一个参数大于第二个参数时。
39 1
java基础(11)函数重载以及函数递归求和
|
3月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
34 1
|
3月前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
36 3
|
5月前
|
Java 调度 Android开发
Android经典实战之Kotlin的delay函数和Java中的Thread.sleep有什么不同?
本文介绍了 Kotlin 中的 `delay` 函数与 Java 中 `Thread.sleep` 方法的区别。两者均可暂停代码执行,但 `delay` 适用于协程,非阻塞且高效;`Thread.sleep` 则阻塞当前线程。理解这些差异有助于提高程序效率与可读性。
93 1
|
6月前
|
存储 Java 编译器
Java中ArrayList的常用函数
确切地说,`ArrayList` 提供的这些方法构成了一套强大并且灵活的工具集,可以满足各种程序设计情况中的需求。例如,通过使用 `iterator()`方法,开发者可以在不知道集合大小的情况下遍历集合中全部或部分元素;而 `sort()`方法则能够对集合中的元素进行排序。这些函数在日常的Java编程中极其常见且重要,掌握它们对于进行集合操作和数据处理来说是基础且必须的。
46 2
Java中ArrayList的常用函数
|
5月前
|
存储 运维 Java
函数计算产品使用问题之怎么配置定时触发器来调用Java函数
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
5月前
|
开发框架 Java Android开发
JNI中调用Java函数
JNI中调用Java函数
42 0
|
5月前
|
开发框架 Java Android开发
JNI中调用Java函数
JNI中调用Java函数
44 0
|
6月前
|
Rust Cloud Native Java
Java演进问题之Serverless应用或函数的冷启动如何解决
Java演进问题之Serverless应用或函数的冷启动如何解决
|
7月前
|
存储 算法 搜索推荐
Java中的数组函数库及其使用技巧
Java中的数组函数库及其使用技巧