web-dev-qa-db-fra.com

Les haricots prototype Spring doivent-ils être détruits manuellement?

J'ai remarqué que les hameçons @PreDestroy de mon prototype dont les haricots Spring avaient une portée n'étaient pas exécutés.

J'ai depuis lu ici que c'est en fait par conception. Le conteneur Spring détruira les haricots singleton, mais pas les haricots prototypes. Je ne comprends pas pourquoi. Si le conteneur Spring crée mon prototype de bean et exécute son hook @PostConstruct, pourquoi ne va-t-il pas également détruire mon bean lorsque le conteneur est fermé? Une fois mon conteneur Spring fermé, est-il logique de continuer à utiliser l’un de ses grains? Je ne vois pas de scénario où vous voudriez fermer un conteneur avant d’en avoir fini avec ses haricots. Est-il même possible de continuer à utiliser un prototype de haricot Spring après la fermeture de son conteneur?

Ce qui précède décrit le contexte étonnant de ma question principale: si le conteneur Spring ne détruit pas les haricots prototype, cela signifie-t-il qu'une fuite de mémoire peut se produire? Ou le prototype de haricot sera-t-il ramassé à un moment donné?

La documentation du printemps dit:

Le code client doit nettoyer les objets à portée prototype et libérer ressources coûteuses que le ou les prototypes de haricots contiennent. Pour obtenir le Conteneur à ressort permettant de libérer les ressources détenues par des haricots à l’image du prototype, essayez d'utiliser un post-processeur de bean personnalisé, qui contient une référence à haricots qui doivent être nettoyés.

Qu'est-ce que ça veut dire? Le texte me suggère que, en tant que programmeur, je suis responsable de la destruction explicite (manuelle) de mon prototype de haricot. Est-ce correct? Si oui, comment je fais ça?

6
IqbalHamid

Pour le bénéfice des autres, je présenterai ci-dessous ce que j'ai recueilli de mes enquêtes:

Tant que le bean prototype ne contient pas lui-même une référence à une autre ressource, telle qu'une connexion à une base de données ou un objet de session, les déchets sont collectés dès que toutes les références à l'objet ont été supprimées ou que l'objet sort de sa portée. Il n’est donc généralement pas nécessaire de détruire explicitement un haricot prototype.

Toutefois, dans le cas où une fuite de mémoire peut se produire, comme décrit ci-dessus, les beans prototypes peuvent être détruits en créant un post-processeur de beans singleton dont la méthode de destruction appelle explicitement les crochets de destruction de vos beans prototypes. Comme le post-processeur est lui-même de portée singleton, son crochet de destruction will est invoqué par Spring:

  1. Créez un post-processeur de haricots pour gérer la destruction de tous vos haricots prototypes. Cela est nécessaire car Spring ne détruit pas les beans prototypes et, par conséquent, aucun hook de @PreDestroy dans votre code ne sera appelé par le conteneur. 

  2. Implémentez les interfaces suivantes:

    1 .BeanFactoryAware
    Cette interface fournit une méthode de rappel qui reçoit un objet Beanfactory. Cet objet BeanFactory est utilisé dans la classe post-processeur pour identifier tous les beans prototypes via sa méthode BeanFactory.isPrototype (String beanName) .

    2. JetableBean
    Cette interface fournit une méthode de rappel Destroy () appelée par le conteneur Spring. Nous appellerons les méthodes Destroy () de tous nos beans prototypes à partir de cette méthode .

    3. BeanPostProcessor
    L'implémentation de cette interface donne accès aux rappels post-traitement à partir de l'intérieur desquels nous préparons une liste interne <> de tous les objets prototypes instanciés par le conteneur Spring. Nous allons plus tard parcourir cette liste <> pour détruire chacun de nos prototypes de haricots.


3. Enfin, implémentez l’interface DisposableBean dans chacun de vos beans prototypes, en fournissant la méthode Destroy () requise par le présent contrat.

Pour illustrer cette logique, je fournis ci-dessous un code issu de cet article article

/**
* Bean PostProcessor that handles destruction of prototype beans
*/
@Component
public class DestroyPrototypeBeansPostProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {

    private BeanFactory beanFactory;

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

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @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 {
        synchronized (prototypeBeans) {
            for (Object bean : prototypeBeans) {
                if (bean instanceof DisposableBean) {
                    DisposableBean disposable = (DisposableBean)bean;
                    try {
                        disposable.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            prototypeBeans.clear();
        }
    }
}
7
IqbalHamid

Votre réponse est géniale. J'aimerais également partager quelques notes sur une solution alternative permettant aux membres de prototypes gérés en mode natif par le cycle de vie du conteneur Spring IoC grâce à l'utilisation des haricots internes.

J'ai récemment écrit une réponse à une question distincte sur les haricots intérieurs. Les beans internes sont créés en affectant des valeurs de propriété de bean en tant qu'objets BeanDefinition. Les valeurs de propriété de définition de bean sont automatiquement résolues en (inner) instances (en tant que beans singleton gérés) du bean qu'elles définissent.

L'élément de configuration de contexte XML suivant peut être utilisé pour créer des beans ForkJoinPool distincts et configurables pour chaque référence qui sera gérée (@PreDestroy sera appelé à la fermeture du contexte):

<!-- Prototype-scoped bean for creating distinct FJPs within the application -->
<bean id="forkJoinPool" class="org.springframework.beans.factory.support.GenericBeanDefinition" scope="prototype">
    <property name="beanClass" value="org.springframework.scheduling.concurrent.ForkJoinPoolFactoryBean" />
</bean>

Ce comportement dépend toutefois de l'affectation de la référence en tant que valeur de la propriété d'une définition de bean. Cela signifie que @Autowired- et constructeur-injection ne fonctionnent pas avec cela par défaut, car ces méthodes d'auto-câblage résolvent la valeur immédiatement plutôt que d'utiliser la résolution de la propriété dans AbstractAutowireCapableBeanFactory#applyPropertyValues . Le câblage automatique par type ne fonctionnera pas non plus, car la résolution de type ne se propage pas via des beans BeanDefinitions pour trouver le type produit.

Cette méthode ne fonctionnera que si l'une ou l'autre des deux conditions est vraie:

  • Les beans dépendants sont aussi définis en XML
  • Ou si le mode autowire est défini sur AutowireCapableBeanFactory#AUTOWIRE_BY_NAME

<!-- Setting bean references through XML -->
<beans ...>
    <bean id="myOtherBean" class="com.example.demo.ForkJoinPoolContainer">
        <property name="forkJoinPool" ref="forkJoinPool" />
    </bean>
</beans>

<!-- Or setting the default autowire mode -->
<beans default-autowire="byName" ...>
    ...
</beans>

Deux modifications supplémentaires pourraient probablement être apportées pour permettre l'injection de constructeur et l'injection de @Autowired.

  • Constructeur injecteur:

    La fabrique de haricots attribue une AutowireCandidateResolver à l'injection du constructeur. La valeur par défaut (ContextAnnotationAutowireCandidateResolver) peut être remplacée (DefaultListableBeanFactory#setAutowireCandidateResolver) pour appliquer un résolveur candidat qui recherche des beans éligibles de type BeanDefinition pour l'injection.

  • @Autowired- injection:

    Le postprocesseur de bean AutowiredAnnotationBeanPostProcessor définit directement les valeurs de bean sans résoudre les beans BeanDefinition internes. Ce postprocesseur peut être remplacé ou un postprocesseur de bean séparé peut être créé pour traiter une annotation personnalisée pour les beans prototypes gérés (par exemple, @AutowiredManagedPrototype).

0
Mike Hill