You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JAX-RS资源中@PreDestroy未触发,如何关闭/释放资源?

解决方案

首先,你的@PreDestroy没触发大概率是因为REST资源类默认是请求作用域(per-request),这个注解会在每个请求对应的资源实例销毁时触发,而非容器关闭时。以下是几种可靠的实现方式:

1. 用Jersey容器生命周期监听器

实现Jersey的ContainerLifecycleListener,它能监听容器的启动/关闭事件,适合在容器 shutdown 时清理资源:

import org.glassfish.jersey.server.ContainerLifecycleListener;
import org.glassfish.jersey.server.Container;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecutorCleanupListener implements ContainerLifecycleListener {
    private final ExecutorService executorService;

    public ExecutorCleanupListener(ExecutorService executorService) {
        this.executorService = executorService;
    }

    @Override
    public void onStartup(Container container) {}

    @Override
    public void onReload(Container container) {}

    @Override
    public void onShutdown(Container container) {
        executorService.shutdown();
        // 可选:等待任务完成,超时则强制终止
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

然后在你的Jersey应用类里注册这个监听器,同时把ExecutorService绑定为可注入的服务:

import jakarta.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@ApplicationPath("/api")
public class MyApplication extends ResourceConfig {
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

    public MyApplication() {
        // 注册REST资源类
        register(MyResource.class);
        // 注册清理监听器
        register(new ExecutorCleanupListener(executorService));
        // 绑定ExecutorService,方便资源类注入
        bind(executorService).to(ExecutorService.class);
    }
}

资源类通过注入获取ExecutorService,无需自行创建:

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.inject.Inject;
import java.util.concurrent.ExecutorService;

@Path("/test")
public class MyResource {
    private final ExecutorService executorService;

    @Inject
    public MyResource(ExecutorService executorService) {
        this.executorService = executorService;
    }

    @GET
    public String doGet() {
        executorService.submit(() -> {
            // 执行异步任务逻辑
        });
        return "OK";
    }
}

2. 将ExecutorService包装为Singleton Bean并使用@PreDestroy

把ExecutorService包装成单例Bean,Jersey会在容器关闭时调用它的@PreDestroy方法:

import jakarta.annotation.PreDestroy;
import jakarta.inject.Singleton;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Singleton
public class ExecutorHolder {
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

    public ExecutorService getExecutorService() {
        return executorService;
    }

    @PreDestroy
    public void cleanup() {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

在应用类注册这个Bean,资源类注入使用:

@ApplicationPath("/api")
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        register(MyResource.class);
        register(ExecutorHolder.class);
    }
}

// 资源类
@Path("/test")
public class MyResource {
    private final ExecutorService executorService;

    @Inject
    public MyResource(ExecutorHolder executorHolder) {
        this.executorService = executorHolder.getExecutorService();
    }

    // ... REST方法逻辑
}

3. 使用ServletContextListener(Servlet容器层面)

基于Jetty的Servlet容器特性,实现ServletContextListener,在容器销毁时清理资源:

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@WebListener
public class ExecutorServletListener implements ServletContextListener {
    private ExecutorService executorService;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        executorService = Executors.newFixedThreadPool(5);
        // 将Executor存入ServletContext,供资源类获取
        sce.getServletContext().setAttribute("executorService", executorService);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

资源类中通过ServletContext获取ExecutorService:

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.inject.Inject;
import jakarta.servlet.ServletContext;
import java.util.concurrent.ExecutorService;

@Path("/test")
public class MyResource {
    private final ExecutorService executorService;

    @Inject
    public MyResource(ServletContext servletContext) {
        this.executorService = (ExecutorService) servletContext.getAttribute("executorService");
    }

    // ... REST方法逻辑
}

注意:需确保Servlet容器能扫描到@WebListener注解,或在web.xml中手动配置该监听器。


内容的提问来源于stack exchange,提问作者Shaik Zakir Hussain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:43:08