web-dev-qa-db-fra.com

Pourquoi la méthode destroy "close" échoue-t-elle pour JPAPagingItemReader configuré avec Java config?

Nous essayons de convertir nos travaux Spring-Batch de la configuration XML en Java. Nous utilisons Spring 4.0.1.RELEASE et Spring Batch 2.2.1.RELEASE.

Après la conversion d'un travail, l'avertissement suivant a commencé à apparaître dans le fichier journal:

15-Apr-2014 09: 59: 26.335 [Thread-2] WARN o.s.b.f.s.DisposableBeanAdapter - L'appel de la méthode destroy 'close' a échoué sur le bean avec le nom 'fileReader': org.springframework.batch.item.ItemStreamException: Erreur lors de la fermeture du lecteur d'élément

La trace de pile complète est la suivante:

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

Cette erreur n'apparaît que lors de l'utilisation de la configuration Java pour le travail mais pas la configuration XML. L'étape configurée à l'aide de XML ressemble à ceci:

<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>

La configuration Java est:

@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;
}

Pourquoi cet avertissement apparaît-il dans les journaux lors de l'utilisation de la configuration Java mais pas XML)?

21
FGreg

TLDR;

Spring essaie d'inférer automatiquement un destroyMethod lors de l'utilisation de Java (mais il ne le fait pas lors de l'utilisation de la configuration XML). Pour désactiver cette inférence automatique, utilisez:

@Bean(destroyMethod="")


La réponse se trouve dans le JavaDoc de l'annotation @Bean; spécifiquement sur la méthode org.springframework.context.annotation.Bean.destroyMethod() (accentuation du mien):

Nom facultatif d'une méthode à appeler sur l'instance de bean lors de la fermeture du contexte d'application, par exemple une méthode close () sur une implémentation JDBC DataSource ou un objet Hibernate SessionFactory. La méthode ne doit avoir aucun argument mais peut lever n'importe quelle exception.

Par commodité pour l'utilisateur, le conteneur tentera d'inférer une méthode destroy sur un objet renvoyé par la méthode @Bean. Par exemple, étant donné un @ Méthode Bean retournant une base de données Apache Commons DBCP BasicDataSource, le conteneur remarquera la méthode close () disponible sur cet objet et l'enregistrera automatiquement en tant que destroyMethod. Cette "inférence de méthode de destruction" se limite actuellement à détecter uniquement les méthodes publiques sans argument nommées "close". La méthode peut être déclarée à n'importe quel niveau de la hiérarchie d'héritage et sera détectée quel que soit le type de retour de la méthode @Bean (c'est-à-dire que la détection se produit de manière réfléchie par rapport à l'instance de bean elle-même au moment de la création).

Pour désactiver l'inférence de méthode destroy pour un @Bean particulier, spécifiez une chaîne vide comme valeur, par ex. @Bean (destroyMethod = ""). Notez que les interfaces org.springframework.beans.factory.DisposableBean et Java.io.Closeable/Java.lang.AutoCloseable seront néanmoins détectées et le méthode destroy/close correspondante invoquée.

Remarque: Uniquement invoqué sur les beans dont le cycle de vie est sous le contrôle total de l'usine, ce qui est toujours le cas pour les singletons mais non garanti pour toute autre étendue.

Après avoir changé la configuration Java en:

@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;
}

L'avertissement ne s'affichait plus. J'ai pu le confirmer en plaçant un point d'arrêt sur la méthode org.springframework.beans.factory.support.DisposableBeanAdapter.destroy() et en lançant le travail configuré XML et le travail configuré Java.

Pour la configuration XML:

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

Pour la Java (sans l'ensemble destroyMethod=""):

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

Pour la Java (avec destroyMethod="" Défini):

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

Sur la base de ces observations, j'arrive à la conclusion que le conteneur n'essaie pas d'inférer une méthode destroy lorsqu'il est configuré via XML; mais il le fait lorsqu'il est configuré via Java. C'est pourquoi l'avertissement apparaît pour la configuration Java et non la configuration XML.

De plus, la méthode que le conteneur déduit est que destroyMethod semble provenir de org.springframework.batch.item.ItemStreamSupport.close(). Cela peut donc potentiellement arriver à n'importe quel bean qui implémente l'interface ItemStreamSupport qui est configurée via l'annotation @Bean.


Une note a été ajoutée au Spring Framework Reference Material for @Bean décrivant ce comportement:

Par défaut, les beans définis à l'aide de Java ayant une méthode de fermeture ou d'arrêt publique sont automatiquement inscrits avec un rappel de destruction. Si vous avez une méthode de fermeture ou d'arrêt publique et que vous ne souhaitez pas qu'elle être appelé lorsque le conteneur s'arrête, ajoutez simplement @Bean (destroyMethod = "") à votre définition de bean pour désactiver le mode par défaut (déduit).

28
FGreg