为什么使用 Java 配置的 JPAPagingItemReader 的销毁方法“关闭”失败?

2022-09-01 14:15:31

我们正在尝试将Spring-Batch作业从XML配置转换为Java配置。我们使用的是Spring 4.0.1.RELEASE和Spring Batch 2.2.1.RELEASE。

转换一个作业后,日志文件中开始出现以下警告:

15-Apr-2014 09:59:26.335 [Thread-2] WARN o.s.b.f.s.DisposableBeanAdapter - 在名为'fileReader'的bean上调用销毁方法“close”失败:org.springframework.batch.item.ItemStreamException:关闭 item reader时出错

完整的堆栈跟踪是:

org.springframework.batch.item.ItemStreamException: Error while closing item reader
    at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close(AbstractItemCountingItemStreamItemReader.java:131) ~[spring-batch-infrastructure-2.2.1.RELEASE.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_25]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_25]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_25]
    at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_25]
    at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:349) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:272) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:540) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:516) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:824) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:485) [spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:921) [spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:895) [spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:809) [spring-context-4.0.1.RELEASE.jar:4.0.1.RELEASE]
Caused by: java.lang.IllegalStateException: EntityManager is closed
    at org.hibernate.ejb.EntityManagerImpl.close(EntityManagerImpl.java:132) ~[hibernate-entitymanager-4.2.5.Final.jar:4.2.5.Final]
    at sun.reflect.GeneratedMethodAccessor14.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_25]
    at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_25]
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:334) ~[spring-orm-4.0.1.RELEASE.jar:4.0.1.RELEASE]
    at $Proxy67.close(Unknown Source) ~[na:na]
    at org.springframework.batch.item.database.JpaPagingItemReader.doClose(JpaPagingItemReader.java:236) ~[spring-batch-infrastructure-2.2.1.RELEASE.jar:na]
    at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close(AbstractItemCountingItemStreamItemReader.java:128) ~[spring-batch-infrastructure-2.2.1.RELEASE.jar:na]
    ... 13 common frames omitted

仅当对作业使用 Java 配置而不是 XML 配置时,才会出现此错误。使用 XML 配置的步骤如下所示:

<batch:step id="createFile" next="insertFile">
    <batch:tasklet>
        <batch:chunk reader="fileReader" writer="fileWriter"
            commit-interval="#{jobProperties[commit_interval]}" />
    </batch:tasklet>
</batch:step>

<bean id="fileReader"
    class="org.springframework.batch.item.database.JpaPagingItemReader">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    <property name="queryString"
        value="select mt from MyTable mt where status in ('1','2','3')" />
    <property name="pageSize" value="1000" />
</bean>

Java 配置是:

@Bean
public Job fileProcessJob(JobBuilderFactory jobBuilders,
        Step loadConfig,
        Step createFile,
        Step insertFile
        ) {
    return jobBuilders.get(moduleName)
            .start(loadConfig)
            .next(createFile)
            .next(insertFile)
            .build()
            .build();
}

@Bean
public ItemReader<MyTable> cetFileReader(EntityManagerFactory entityManagerFactory) {
    JpaPagingItemReader<MyTable> itemReader = new JpaPagingItemReader<MyTable>();
    itemReader.setEntityManagerFactory(entityManagerFactory);
    itemReader.setQueryString("select mt from MyTable mt where status in ('1','2','3')");
    itemReader.setPageSize(1000);
    return itemReader;
}

为什么在使用 Java 配置而不是 XML 配置时,日志中会出现此警告?


答案 1

TLDR;

Spring尝试在使用Java配置时自动推断(但在使用XML配置时不会这样做)。要禁用此自动推理,请使用:destroyMethod

@Bean(destroyMethod="")


答案就在注释的JavaDoc中;具体到方法(强调我的):@Beanorg.springframework.context.annotation.Bean.destroyMethod()

在关闭应用程序上下文时要调用 Bean 实例的方法的可选名称,例如 JDBC DataSource 实现上的 close() 方法或 Hibernate SessionFactory 对象。该方法必须没有参数,但可能会引发任何异常。

为了方便用户,容器将尝试针对从@Bean方法返回的对象推断销毁方法。例如,给定一个返回Apache Commons DBCP BasicDataSource的@Bean方法,容器将注意到该对象上可用的close()方法,并自动将其注册为销毁方法。这种“销毁方法推断”目前仅限于检测名为“close”的公共无参数方法。该方法可以在继承层次结构的任何级别声明,并且无论@Bean方法的返回类型如何(即,在创建时对Bean实例本身进行反射性检测),都会被检测到。

要禁用特定@Bean的销毁方法推理,请指定一个空字符串作为值,例如@Bean(destroyMethod=“”)。请注意,org.springframework.beans.factory.DisposableBean 和 java.io.Closeable/java.lang.AutoCloseable 接口仍然会被检测到,并调用相应的 destroy/close 方法。

注意:仅在生命周期由工厂完全控制的 Bean 上调用,对于单例始终如此,但对于任何其他范围,则不保证。

将 Java 配置更改为:

@Bean(destroyMethod="")
public ItemReader<MyTable> cetFileReader(EntityManagerFactory entityManagerFactory) {
    JpaPagingItemReader<MyTable> itemReader = new JpaPagingItemReader<MyTable>();
    itemReader.setEntityManagerFactory(entityManagerFactory);
    itemReader.setQueryString("select mt from MyTable mt where status in ('1','2','3')");
    itemReader.setPageSize(1000);
    return itemReader;
}

警告不再出现。我能够通过在方法上放置断点并启动 XML 配置的作业和 Java 配置的作业来确认这一点。org.springframework.beans.factory.support.DisposableBeanAdapter.destroy()

对于 XML 配置:

  • DisposableBeanAdapter.invokeDisposableBean = false
  • DisposableBeanAdapter.destroyMethod = null
  • DisposableBeanAdapter.destroyMethodName = null

对于 Java 配置(未设置):destroyMethod=""

  • DisposableBeanAdapter.invokeDisposableBean = false
  • DisposableBeanAdapter.destroyMethod = public void org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close() throws org.springframework.batch.item.ItemStreamException
  • DisposableBeanAdapter.destroyMethodName = close

对于 Java 配置(带 set):destroyMethod=""

  • DisposableBeanAdapter.invokeDisposableBean = false
  • DisposableBeanAdapter.destroyMethod = null
  • DisposableBeanAdapter.destroyMethodName = null

基于这些观察结果,我得出的结论是,容器在通过XML配置时不会尝试推断销毁方法。但是当通过Java配置时,它确实如此。这就是为什么警告出现在 Java 配置而不是 XML 配置中的原因。

此外,容器推断的方法是销毁方法似乎来自 。因此,这可能发生在任何实现通过注释配置的接口的Bean上。org.springframework.batch.item.ItemStreamSupport.close()ItemStreamSupport@Bean


在 Spring 框架参考资料中添加了一条注释,用于描述此行为@Bean:

缺省情况下,使用 Java 配置定义的具有公共关闭或关闭方法的 Bean 将自动使用销毁回调进行登记。如果您有一个公共关闭或关闭方法,并且您不希望在容器关闭时调用它,只需将@Bean(destroyMethod=“”)添加到 Bean 定义中以禁用默认(推断)模式。


答案 2

推荐