70 votes

Qu'est-ce qu'une NoSuchBeanDefinitionException et comment la réparer ?

Veuillez expliquer les points suivants concernant NoSuchBeanDefinitionException exception au printemps :

  • Qu'est-ce que cela signifie ?
  • Dans quelles conditions sera-t-il jeté ?
  • Comment puis-je l'éviter ?

Ce post est conçu pour être un Q&R complet sur les cas de NoSuchBeanDefinitionException dans les applications utilisant Spring.

126voto

Sotirios Delimanolis Points 77933

El javadoc de NoSuchBeanDefinitionException explique

Exception déclenchée lorsqu'une BeanFactory est demandé pour une instance de bean pour pour laquelle il ne trouve pas de définition. Il peut s'agir d'un bean non existant inexistant, un bean non unique, ou une instance singleton enregistrée manuellement sans définition de bean associée.

A BeanFactory est essentiellement l'abstraction représentant Le conteneur d'inversion de contrôle de Spring . Il expose les beans en interne et en externe, à votre application. Lorsqu'il ne peut pas trouver ou récupérer ces beans, il lance une requête de type NoSuchBeanDefinitionException .

Voici quelques raisons simples pour lesquelles un BeanFactory (ou des classes connexes) ne serait pas en mesure de trouver un haricot et comment vous pouvez vous assurer qu'il le fait.


Le haricot n'existe pas, il n'a pas été enregistré.

Dans l'exemple ci-dessous

@Configuration
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        ctx.getBean(Foo.class);
    }
}

class Foo {}   

nous n'avons pas enregistré une définition de haricot pour le type Foo soit par le biais d'un @Bean méthode, @Component un balayage, une définition XML, ou tout autre moyen. Le site BeanFactory géré par le AnnotationConfigApplicationContext n'a donc aucune indication sur l'endroit où obtenir le haricot demandé par getBean(Foo.class) . L'extrait ci-dessus lance

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException:
    No qualifying bean of type [com.example.Foo] is defined

De la même manière, l'exception aurait pu être levée en essayant de satisfaire à une exigence de l'UE. @Autowired dépendance. Par exemple,

@Configuration
@ComponentScan
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
    }
}

@Component
class Foo { @Autowired Bar bar; }
class Bar { }

Ici, une définition de bean est enregistrée pour Foo par le biais de @ComponentScan . Mais Spring ne sait rien de Bar . Il ne parvient donc pas à trouver un haricot correspondant lorsqu'il essaie de câbler automatiquement l'ordinateur. bar du champ de la Foo instance du haricot. Il lance (imbriqué dans un UnsatisfiedDependencyException )

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No qualifying bean of type [com.example.Bar] found for dependency [com.example.Bar]: 
        expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Il existe plusieurs façons d'enregistrer les définitions de haricots.

  • @Bean dans un @Configuration ou <bean> dans la configuration XML
  • @Component (et ses méta-annotations, par exemple. @Repository ) par @ComponentScan o <context:component-scan ... /> en XML
  • Manuellement par le biais de GenericApplicationContext#registerBeanDefinition
  • Manuellement par le biais de BeanDefinitionRegistryPostProcessor

...et plus encore.

Assurez-vous que les haricots que vous attendez sont correctement enregistrés.

Une erreur courante est d'enregistrer des haricots plusieurs fois, c'est-à-dire de mélanger les options ci-dessus pour le même type. Par exemple, je pourrais avoir

@Component
public class Foo {}

et une configuration XML avec

<context:component-scan base-packages="com.example" />
<bean name="eg-different-name" class="com.example.Foo />

Une telle configuration enregistrerait deux beans de type Foo un avec le nom foo et un autre avec le nom eg-different-name . Assurez-vous que vous n'enregistrez pas accidentellement plus de haricots que vous ne le souhaitiez. Ce qui nous amène à...

Si vous utilisez à la fois des configurations XML et des configurations basées sur des annotations, veillez à importer l'une à partir de l'autre. XML fournit

<import resource=""/>

tandis que Java fournit le @ImportResource annotation.

On s'attendait à un seul haricot correspondant, mais on en a trouvé 2 (ou plus).

Il arrive que vous ayez besoin de plusieurs beans pour le même type (ou interface). Par exemple, votre application peut utiliser deux bases de données, une instance MySQL et une instance Oracle. Dans un tel cas, vous aurez deux beans DataSource pour gérer les connexions à chacun d'eux. A titre d'exemple (simplifié), on peut citer

@Configuration
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        System.out.println(ctx.getBean(DataSource.class));
    }
    @Bean(name = "mysql")
    public DataSource mysql() { return new MySQL(); }
    @Bean(name = "oracle")
    public DataSource oracle() { return new Oracle(); }
}
interface DataSource{}
class MySQL implements DataSource {}
class Oracle implements DataSource {}

jette

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
    No qualifying bean of type [com.example.DataSource] is defined:
        expected single matching bean but found 2: oracle,mysql

parce que les deux haricots ont été enregistrés par @Bean ont satisfait à l'exigence de BeanFactory#getBean(Class) c'est-à-dire qu'ils mettent tous deux en œuvre DataSource . Dans cet exemple, Spring n'a aucun mécanisme pour différencier ou prioriser les deux. Mais de tels mécanismes existent.

Vous pourriez utiliser @Primary (et son équivalent en XML), tel que décrit dans le document documentation et en ce poste . Avec ce changement

@Bean(name = "mysql")
@Primary
public DataSource mysql() { return new MySQL(); } 

l'extrait précédent ne lèverait pas l'exception et renverrait l'adresse de l'utilisateur. mysql haricot.

Vous pouvez également utiliser @Qualifier (et son équivalent en XML) pour avoir plus de contrôle sur le processus de sélection des haricots, comme décrit dans le document documentation . Alors que @Autowired est principalement utilisé pour le câblage automatique par type, @Qualifier vous permet d'effectuer un câblage automatique par nom. Par exemple,

@Bean(name = "mysql")
@Qualifier(value = "main")
public DataSource mysql() { return new MySQL(); }

peut maintenant être injecté en tant que

@Qualifier("main") // or @Qualifier("mysql"), to use the bean name
private DataSource dataSource;

sans problème. @Resource est également une option.

Utilisation d'un mauvais nom de haricot

Tout comme il existe de multiples façons d'enregistrer des haricots, il existe également de multiples façons de les nommer.

@Bean a name

Le nom de ce haricot ou, s'il est pluriel, les alias de ce haricot. S'il est laissé non spécifié, le nom du bean est le nom de la méthode annotée. S'il est spécifié, le nom de la méthode est ignoré.

<bean> a le id pour représenter l'identifiant unique d'un haricot y name peut être utilisé pour créer un ou plusieurs alias illégaux dans un id (XML).

@Component et ses méta annotations ont value

La valeur peut indiquer une suggestion pour un nom de composant logique, pour être transformé en un bean Spring dans le cas d'un composant autodétecté.

Si cela n'est pas spécifié, un nom de haricot est automatiquement généré pour le type annoté, généralement la version en minuscules du nom du type. Par exemple MyClassName devient myClassName comme nom de haricot. Les noms des beans sont sensibles à la casse. Notez également que les noms et les majuscules incorrects se produisent généralement dans les beans auxquels il est fait référence par des chaînes comme @DependsOn("my BeanName") ou des fichiers de configuration XML.

@Qualifier comme mentionné précédemment, vous permet d'ajouter plus d'alias à un haricot.

Veillez à utiliser le bon nom lorsque vous faites référence à un haricot.


Cas plus avancés

Profils

Profils de définition des haricots vous permettent d'enregistrer des haricots de manière conditionnelle. @Profile en particulier,

Indique qu'un composant est éligible à l'enregistrement lorsqu'une ou plusieurs des conditions suivantes sont remplies plusieurs profils spécifiés sont actifs.

Un profil est un groupe logique nommé qui peut être activé. de manière programmatique via ConfigurableEnvironment.setActiveProfiles(java.lang.String...) ou de manière déclarative en définissant la spring.profiles.active comme une JVM comme une variable d'environnement, ou comme un paramètre de contexte de servlet dans le fichier web.xml pour les applications Web. Les profils peuvent également être être activés de manière déclarative dans les tests d'intégration @ActiveProfiles annotation.

Prenons cet exemple où le spring.profiles.active n'est pas définie.

@Configuration
@ComponentScan
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        System.out.println(Arrays.toString(ctx.getEnvironment().getActiveProfiles()));
        System.out.println(ctx.getBean(Foo.class));
    }
}

@Profile(value = "StackOverflow")
@Component
class Foo {
}

Cela ne montrera aucun profil actif et lancera un NoSuchBeanDefinitionException pour un Foo haricot. Puisque le StackOverflow Le profil n'était pas actif, le haricot n'était pas enregistré.

Au lieu de cela, si j'initialise le ApplicationContext tout en enregistrant le profil approprié

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("StackOverflow");
ctx.register(Example.class);
ctx.refresh();

le haricot est enregistré et peut être retourné/injecté.

Proxies AOP

Utilisations du printemps Proxies AOP beaucoup pour mettre en œuvre un comportement avancé. Voici quelques exemples :

Pour y parvenir, Spring dispose de deux options :

  1. Utilisez la fonction Proxy pour créer une instance d'une classe dynamique au moment de l'exécution qui n'implémente que les interfaces de votre haricot. et délègue toutes les invocations de méthodes à une instance réelle du haricot.
  2. Utilice CGLIB proxies pour créer une instance d'une classe dynamique au moment de l'exécution qui implémente à la fois les interfaces et les types concrets de votre haricot cible et délègue toutes les invocations de méthodes à une instance réelle du haricot.

Prenons l'exemple des proxies JDK (réalisés par le biais de @EnableAsync par défaut proxyTargetClass de false )

@Configuration
@EnableAsync
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        System.out.println(ctx.getBean(HttpClientImpl.class).getClass());
    }
}

interface HttpClient {
    void doGetAsync();
}

@Component
class HttpClientImpl implements HttpClient {
    @Async
    public void doGetAsync() {
        System.out.println(Thread.currentThread());
    }
}

Ici, Spring tente de trouver un bean de type HttpClientImpl que nous nous attendons à trouver parce que le type est clairement annoté de @Component . Cependant, au lieu de cela, nous obtenons une exception

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No qualifying bean of type [com.example.HttpClientImpl] is defined

Le printemps a enveloppé le HttpClientImpl et l'a exposé à travers un Proxy qui n'implémente que HttpClient . Vous pouvez donc le récupérer avec

ctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33
// or
@Autowired private HttpClient httpClient;

Il est toujours recommandé de programme aux interfaces . Lorsque vous ne pouvez pas, vous pouvez dire à Spring d'utiliser les proxies CGLIB. Par exemple, avec @EnableAsync vous pouvez définir proxyTargetClass à true . Annotations similaires ( EnableTransactionManagement etc.) ont des attributs similaires. XML aura également des options de configuration équivalentes.

ApplicationContext Hiérarchies - Spring MVC

Spring vous permet de construire ApplicationContext avec d'autres ApplicationContext comme parents, en utilisant ConfigurableApplicationContext#setParent(ApplicationContext) . Un contexte enfant aura accès aux beans du contexte parent, mais l'inverse n'est pas vrai. Ce poste explique en détail quand cela est utile, en particulier dans Spring MVC.

Dans une application Spring MVC typique, vous définissez deux contextes : l'un pour l'ensemble de l'application (le Root) et l'autre spécifiquement pour l'application DispatcherServlet (routage, méthodes de gestion, contrôleurs). Vous pouvez obtenir plus de détails ici :

C'est également très bien expliqué dans la documentation officielle, aquí .

Une erreur courante dans les configurations Spring MVC consiste à déclarer la configuration WebMVC dans le contexte Root avec l'option @EnableWebMvc annoté @Configuration ou <mvc:annotation-driven /> en XML, mais le @Controller dans le contexte du servlet. Comme le contexte Root ne peut pas accéder au contexte servlet pour trouver des beans, aucun gestionnaire n'est enregistré et toutes les demandes échouent avec 404s. Vous ne verrez pas de NoSuchBeanDefinitionException mais l'effet est le même.

Assurez-vous que vos beans sont enregistrés dans le contexte approprié, c'est-à-dire là où ils peuvent être trouvés par les beans enregistrés pour WebMVC ( HandlerMapping , HandlerAdapter , ViewResolver , ExceptionResolver etc.). La meilleure solution consiste à isoler correctement les haricots. Le site DispatcherServlet est responsable de l'acheminement et de la gestion des demandes, donc tous les beans liés doivent être placés dans son contexte. Le site ContextLoaderListener qui charge le contexte Root, doit initialiser tous les beans dont le reste de votre application a besoin : services, référentiels, etc.

Tableaux, collections et cartes

Les haricots de certains types connus sont traités de manière spéciale par Spring. Par exemple, si vous essayez d'injecter un tableau de fichiers de type MovieCatalog dans un champ

@Autowired
private MovieCatalog[] movieCatalogs;

Spring trouvera tous les haricots de type MovieCatalog les envelopper dans un tableau, et injecter ce tableau. Ceci est décrit dans le La documentation de Spring discute @Autowired . Un comportement similaire s'applique à Set , List y Collection les cibles d'injection.

Pour un Map Spring se comportera également de cette manière si le type de clé est String . Par exemple, si vous avez

@Autowired
private Map<String, MovieCatalog> movies;

Spring trouvera tous les haricots de type MovieCatalog et les ajouter comme valeurs à un Map où la clé correspondante sera leur nom de haricot.

Comme décrit précédemment, si aucun haricot du type demandé n'est disponible, Spring lancera une erreur de type NoSuchBeanDefinitionException . Parfois, cependant, vous voulez simplement déclarer un bean de ces types de collections comme

@Bean
public List<Foo> fooList() {
    return Arrays.asList(new Foo());
}

et les injecter

@Autowired
private List<Foo> foos;

Dans cet exemple, Spring échouerait avec un NoSuchBeanDefinitionException parce qu'il n'y a pas Foo haricots dans votre contexte. Mais vous ne vouliez pas d'un Foo haricot, vous vouliez un List<Foo> haricot. Avant Spring 4.3, vous auriez dû utiliser @Resource

Pour les beans qui sont eux-mêmes définis comme une collection/map ou un tableau de type, @Resource est une bonne solution, en faisant référence à la collection ou au tableau de haricots par un nom unique. Cela dit, à partir de 4.3 , les types de collections/maps et de tableaux peuvent être appariés par le biais de la fonction @Autowired aussi bien, pour autant que l'élément est préservée dans le fichier @Bean signatures de type de retour ou les hiérarchies d'héritage des collections. Dans ce cas, les valeurs des qualificatifs peuvent être utilisées pour choisir parmi les collections de même type, comme indiqué dans le paragraphe précédent.

Cela fonctionne pour l'injection de constructeur, de setter et de champ.

@Resource
private List<Foo> foos;
// or since 4.3
public Example(@Autowired List<Foo> foos) {}

Cependant, il échouera pour @Bean méthodes, c'est-à-dire

@Bean
public Bar other(List<Foo> foos) {
    new Bar(foos);
}

Ici, Spring ignore tout @Resource o @Autowired annoter la méthode, parce que c'est une @Bean et ne peut donc pas appliquer le comportement décrit dans la documentation. Cependant, vous pouvez utiliser le langage d'expression Spring (SpEL) pour faire référence aux beans par leur nom. Dans l'exemple ci-dessus, vous pourriez utiliser

@Bean
public Bar other(@Value("#{fooList}") List<Foo> foos) {
    new Bar(foos);
}

pour faire référence au haricot nommé fooList et l'injecter.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X