没有调用Spring单例bean的@PreDestroy方法

时间:2013-05-04 10:27:38

标签: java spring java-ee

我在beans.xml中定义了一个Spring bean,如下所示:

<context:annotation-config />
[...]
<bean id="myBackend" class="mycompany.BackendBean" scope="singleton" />

bean内部有两个方法,必须在Web应用程序的开始和结束之前执行:

public class BackendBean implements IBackend {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(BackendBean.class);

    @PostConstruct
    public void init()
    {
        LOGGER.debug("init");
    }

    @PreDestroy
    public void destroy()
    {
        LOGGER.debug("destroy");
    }
}

当我运行服务器(mvn jetty:run)时,我可以在控制台中看到init方法的输出,从中我得出结论init方法已执行。

当我按Ctrl-C并且Jetty开始关闭时,我看不到destroy方法的输出。

当应用程序终止时,为了执行destroy方法,我应该更改什么?

7 个答案:

答案 0 :(得分:12)

对于Spring在应用程序关闭时调用@PreDestroy回调方法,您必须添加一个关闭钩子并关闭它所在的应用程序上下文。您可以使用Runtime.getRuntime().addShutdownHook(Thread)将挂钩附加到JVM或者Jetty如果提供这样的API。以下是使用JVM关闭钩子的方法:

final ApplicationContext appContext = ... // create your application context 
                         // using one of the various application context classes
Runtime.getRuntime().addShutdownHook(new Thread() {
   public void run() {
       appContext.close();
   }});

答案 1 :(得分:2)

一个老问题,但想分享我发现的东西。

我有一段类似的代码,最初认为 @PreDestroy 方法没有被调用。但是后来我添加了一个打印语句和 LOGGER info() 并且惊讶地看到打印被执行了。显然这是因为 logback 甚至在调用 @PreDestroy 方法之前就关闭了。

以下来源可能有用:https://github.com/spring-projects/spring-framework/issues/24431

答案 2 :(得分:1)

在课堂上使用@Scope("prototype")时,即使尝试使用context.close();context.registerShutdownHook();来关闭,@ PreDestroy也无法正常工作

答案 3 :(得分:0)

我不知道你为什么要Spring来照顾这个。除非我误解了您的问题,否则您可以使用容器应用程序生命周期。

尝试撰写LifeCycle (jetty)LifeCycleListener (tomcat)并覆盖LifeCyle onStartonStop。在适当的event发生时,在tomcat中为LifeCycleListener开发类似的解决方案。

答案 4 :(得分:0)

Here is a subtle point you need to be aware of with "prototype" scoped beans.

For "prototype" scoped beans, Spring does not call the @PreDestroy method. 

Here is the answer from the Spring reference manual. Section 1.5.2

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-prototype

与其他作用域相比,Spring不能管理一个作用域的完整生命周期 原型bean :容器实例化,配置并以其他方式组装 原型对象,然后将其交给客户端,没有该原型的进一步记录 实例。

因此,尽管在所有对象上都调用了初始化生命周期回调方法,而不管作用域如何,但在原型的情况下,未调用已配置的销毁生命周期回调。客户端代码必须清除原型作用域的对象并释放原型Bean所拥有的昂贵资源。

要使Spring容器释放由原型作用域的bean占用的资源,请尝试使用自定义bean后处理器,该处理器包含对需要清理的bean的引用。

注意:这也适用于XML配置。

答案 5 :(得分:0)

古老的问题,但解决方案是创建一个处理PreDestroy的类:

@Component
public class SetBeanProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {

    private BeanFactory beanFactory;
    private final List<Object> prototypeBeans = new LinkedList<>();

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (beanFactory.isPrototype(beanName)) {
            synchronized (prototypeBeans) {
                prototypeBeans.add(bean);
            }
        }
        return bean;
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


    @Override
    public void destroy() throws Exception {

        // note : if we have several instance of a prototype (each instance is distinct) the method will be called several times ... 
        synchronized (prototypeBeans) {
            for (Object bean : prototypeBeans) {

                if (bean instanceof DisposableBean) {
                    DisposableBean disposable = (DisposableBean)bean;
                    try {
                        disposable.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            prototypeBeans.clear();
        }
    }

使用此类的类应通过以下方式实现:

@Component
public class myClass implements SomeClassName, DisposableBean {
    ...

    @Override
    public void destroy() throws Exception {
        System.out.println("# myClass: destroy() method called");       
    }
}

答案 6 :(得分:0)

您可以使用方法引用“close”在 Spring 中执行 @PreDestroy 方法:

Runtime.getRuntime().addShutdownHook(new Thread(applicationContext::close));