web-dev-qa-db-fra.com

Retrouver la cause de Spring "non éligible pour le proxy automatique"

Lorsque vous commencez à jouer avec les trucs de proxy automatique de Spring, vous rencontrez souvent ce comportement comme indiqué:

Les classes qui implémentent l'interface BeanPostProcessor sont spéciales et sont donc traitées différemment par le conteneur. Tous les BeanPostProcessors et leurs beans directement référencés seront instanciés au démarrage, dans le cadre de la phase de démarrage spéciale d'ApplicationContext, puis tous ces BeanPostProcessors seront enregistrés de manière triée - et appliqués à tous les autres beans. Étant donné que le proxy automatique AOP est implémenté en tant que BeanPostProcessor lui-même, aucun BeanPostProcessor ou beans référencés directement ne sont éligibles pour le proxy automatique (et n'auront donc pas d'aspects 'tissés').

Pour un tel bean, vous devriez voir un message du journal d'informations: "Le bean" foo "n'est pas éligible pour être traité par tous les BeanPostProcessors (par exemple: non éligible pour le proxy automatique)".

En d'autres termes, si j'écris mon propre BeanPostProcessor et que cette classe fait directement référence à d'autres beans dans le contexte, ces beans référencés ne seront pas éligibles pour le proxy automatique et un message est enregistré à cet effet.

Mon problème est que retrouver où se trouve cette référence directe peut être très difficile, car la "référence directe" peut en fait être une chaîne de dépendances transitives qui finit par prendre la moitié des beans dans le contexte de l'application. Tout ce que Spring vous donne, c'est ce seul message d'information, et ce n'est pas vraiment beaucoup d'aide, au-delà de vous dire quand un bean a été pris dans ce web de références.

Le BeanPostProcessor que je développe possède des références directes à d'autres beans, mais c'est un ensemble très limité de références. Malgré cela, à peu près chaque bean dans mon contexte est alors exclu du proxy automatique, selon les messages du journal, mais je ne vois pas où cette dépendance se produit.

Quelqu'un a-t-il trouvé un meilleur moyen de retracer cela?

43
skaffman

Juste pour clore cette question, l'effondrement du graphe d'objet non initialisé a été provoqué par le BeanPostProcessor utilisant @Autowired pour obtenir ses dépendances, et le mécanisme de câblage automatique a effectivement causé l'initialisation de toutes les autres définitions de bean avant que mon BeanPostProcessor n'ait la chance d'avoir son mot à dire. La solution n'est pas d'utiliser le câblage automatique pour vos BPP.

21
skaffman

Suivez cette recette:

  1. Ouvrez BeanPostProcessorChecker dans votre IDE (c'est une classe interne de AbstractApplicationContext)
  2. Définissez un point d'arrêt sur if (logger.isInfoEnabled()) { dans la méthode postProcessAfterInitialization
  3. Exécutez votre code
  4. Lorsque vous atteignez le point d'arrêt, recherchez les appels à getBean(String,Class<T>) dans votre trace de pile.

    L'un de ces appels tentera de créer un BeanPostProcessor. Ce haricot devrait être le coupable.

Contexte

Imaginez cette situation:

public class FooPP implements BeanPostProcessor {
    @Autowire
    private Config config;
}

Lorsque Spring doit créer config (car c'est une dépendance de FooPP), il a un problème: le contrat stipule que tout BeanPostProcessor doit être appliqué à chaque bean en cours créé. Mais lorsque Spring a besoin de config, il y a au moins un PP (à savoir FooPP) qui n'est pas prêt pour le service!

Cela empire lorsque vous utilisez un @Configuration classe pour définir ce bean:

@Configuration
public class BadSpringConfig {
     @Lazy @Bean public Config config() { return new Config(); }
     @Lazy @Bean public FooPP fooPP() { return new FooPP(); }
}

Chaque classe de configuration est un bean. Cela signifie que pour construire une fabrique de bean à partir de BadSpringConfig, Spring doit appliquer le post-processeur fooPP mais pour ce faire, il a d'abord besoin de la fabrique de bean ...

Dans cet exemple, il est possible de rompre l'une des dépendances cycliques. Vous pouvez faire en sorte que FooPP implémente BeanFactoryAware pour que Spring injecte le BeanFactory dans le post-processeur. De cette façon, vous n'avez pas besoin de câblage automatique.

Plus loin dans le code, vous pouvez paresseusement demander le bean:

private LazyInit<Config> helper = new LazyInit<Config>() {

    @Override
    protected InjectionHelper computeValue() {
        return beanFactory.getBean( Config.class );
    }
};

@Override
public Object postProcessBeforeInitialization( Object bean, String beanName ) throws BeansException {
     String value = helper.get().getConfig(...);
}

( source pour LazyInit )

Pour rompre le cycle entre la fabrique de beans et le post-processeur, vous devez configurer le post-processeur dans un fichier de configuration XML. Spring peut lire cela et construire toutes les structures sans se confondre.

31
Aaron Digulla

Je ne sais pas si cela peut être utile, mais le Eclipse Spring IDEaffichage graphique semble qu'il pourrait être utile pour trier les références de bean ..

4
Tim