web-dev-qa-db-fra.com

Qu'est-ce qu'une exception NoSuchBeanDefinitionException et comment le corriger?

Veuillez expliquer ce qui suit à propos de NoSuchBeanDefinitionException exception au printemps:

  • Qu'est-ce que ça veut dire?
  • Dans quelles conditions sera-t-il jeté?
  • Comment puis-je l'empêcher?

Cet article est conçu pour être un Q & A complet sur les occurrences de NoSuchBeanDefinitionException dans des applications utilisant Spring.

50

Le javadoc of NoSuchBeanDefinitionException explique

Exception levée lorsqu'une instance BeanFactory est demandée pour une instance de bean pour laquelle elle ne trouve pas de définition. Cela peut pointer vers un bean non existant, un bean non unique ou une instance singleton enregistrée manuellement sans définition de bean associée.

Un BeanFactory est fondamentalement l'abstraction représentant conteneur Inversion of Control de Spring . Il expose les haricots de manière interne et externe à votre application. Lorsqu'il ne peut pas trouver ou récupérer ces haricots, il lance un NoSuchBeanDefinitionException.

Vous trouverez ci-dessous des raisons simples pour lesquelles un BeanFactory (ou des classes associées) ne pourrait pas trouver un haricot et comment vous en assurer.


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é de définition de bean pour le type Foo via une méthode _@Bean_, un scan _@Component_, une définition XML ou tout autre moyen. Le BeanFactory géré par le AnnotationConfigApplicationContext n'a donc aucune indication sur l'endroit où obtenir le bean demandé par getBean(Foo.class). L'extrait ci-dessus jette

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

De même, une exception aurait pu être levée en essayant de satisfaire une dépendance _@Autowired_. 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 à _@ComponentScan_. Mais Spring ne sait rien de Bar. Par conséquent, il ne parvient pas à trouver le bean correspondant lors de la tentative de virement automatique du champ bar de l'instance de bean Foo. Il jette (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 des définitions de beans.

  • Méthode _@Bean_ dans une classe _@Configuration_ ou _<bean>_ dans une configuration XML
  • _@Component_ (et ses méta-annotations, par exemple. _@Repository_) à _@ComponentScan_ ou _<context:component-scan ... />_ en XML
  • Manuellement par GenericApplicationContext#registerBeanDefinition
  • Manuellement à travers BeanDefinitionRegistryPostProcessor

...et plus.

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

Une erreur courante consiste à enregistrer des beans plusieurs fois, c'est-à-dire. 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, l’un portant le nom foo et l’autre le nom _eg-different-name_. Assurez-vous de ne pas enregistrer accidentellement plus de haricots que vous ne le souhaitiez. Ce qui nous amène à ...

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

_<import resource=""/>
_

tandis que Java fournit l'annotation @ImportResource .

Haricot correspondant unique attendu, mais 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 autre Oracle. Dans un tel cas, vous auriez deux beans DataSource pour gérer les connexions à chacun. Pour exemple (simplifié), le suivant

_@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 beans enregistrés via les méthodes _@Bean_ satisfont à l'exigence de BeanFactory#getBean(Class) , c'est-à-dire. ils implémentent tous les deux DataSource. Dans cet exemple, Spring ne dispose d'aucun mécanisme pour différencier ou hiérarchiser les deux. Mais de tels mécanismes existent.

Vous pouvez utiliser @Primary (et son équivalent en XML) comme décrit dans la documentation et dans this post . Avec ce changement

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

l'extrait précédent ne lève pas l'exception et renvoie le bean mysql.

Vous pouvez également utiliser _@Qualifier_ (et son équivalent en XML) pour mieux contrôler le processus de sélection du bean, comme décrit dans la documentation . Alors que _@Autowired_ est principalement utilisé pour le transfert automatique par type, _@Qualifier_ vous permet de procéder au transfert automatique par nom. Par exemple,

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

pourrait maintenant être injecté comme

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

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

Utiliser un nom de bean incorrect

Tout comme il existe plusieurs façons d’enregistrer des beans, il existe également de nombreuses manières de les nommer.

@Bean a name

Le nom de ce haricot, ou si pluriel, alias pour ce haricot. Si non spécifié, le nom du bean est le nom de la méthode annotée. Si spécifié, le nom de la méthode est ignoré.

_<bean>_ possède l'attribut id pour représenter l'identificateur unique d'un bean et name peut être utilisé pour créer un ou plusieurs alias illégaux dans un (XML) id.

@Component et ses méta-annotations ont value

La valeur peut indiquer une suggestion pour un nom de composant logique, à transformer en bean Spring dans le cas d'un composant détecté automatiquement.

Si cela n'est pas précisé, un nom de bean est automatiquement généré pour le type annoté, généralement la version de casse de chameau inférieure du nom de type.

_@Qualifier_, comme mentionné précédemment, vous permet d'ajouter plusieurs alias à un bean.

Assurez-vous d'utiliser le bon nom lors du câblage automatique par nom.


Cas plus avancés

Profils

Profils de définition de bean vous permettent d'enregistrer les beans de manière conditionnelle. @Profile , plus précisément,

Indique qu'un composant est éligible pour l'enregistrement lorsqu'un ou plusieurs profils spécifiés sont actifs.

Un profil est un groupe logique nommé pouvant être activé par programme via ConfigurableEnvironment.setActiveProfiles(Java.lang.String...) ou de manière déclarative en définissant la propriété _spring.profiles.active_ en tant que propriété du système JVM, variable d'environnement ou paramètre de contexte Servlet dans web.xml pour les applications Web. Les profils peuvent également être activés de manière déclarative dans les tests d'intégration via l'annotation @ActiveProfiles .

Considérez cet exemple où la propriété _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 jettera un NoSuchBeanDefinitionException pour un bean Foo. Puisque le profil StackOverflow n'était pas actif, le bean 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 renvoyé/injecté.

Proxys AOP

Spring utilise proxy AOP beaucoup pour implémenter un comportement avancé. Quelques exemples incluent:

Pour ce faire, Spring a deux options:

  1. Utilisez la classe Proxy du JDK pour créer une instance d'une classe dynamique au moment de l'exécution qui implémente uniquement les interfaces de votre bean et délègue toute la méthode. invocations à une instance de bean réelle.
  2. Utilisez les proxies CGLIB pour créer une instance d'une classe dynamique qui implémente les interfaces et les types concrets de votre bean cible et délègue tous les appels de méthodes à une instance de bean réelle.

Prenons cet exemple de proxy JDK (obtenu grâce à proxyTargetClass de _@EnableAsync_ par défaut 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());
    }
}
_

Spring tente ici de trouver un haricot de type HttpClientImpl que nous espérons trouver car ce type est clairement annoté de _@Component_. Cependant, à la place, nous obtenons une exception

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

Spring a enveloppé le bean HttpClientImpl et l'a exposé via un objet Proxy n'implémentant que HttpClient. Pour que vous puissiez 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 . Si vous ne le pouvez pas, vous pouvez dire à Spring d’utiliser des procurations CGLIB. Par exemple, avec @EnableAsync , vous pouvez définir proxyTargetClass sur true. Les 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 créer des instances ApplicationContext avec d'autres instances ApplicationContext en tant que parents, à l'aide de ConfigurableApplicationContext#setParent(ApplicationContext) . Un contexte enfant aura accès aux beans dans le contexte parent, mais l'inverse n'est pas vrai. This post ​​explique en détail quand cela est utile, en particulier dans Spring MVC.

Dans une application Spring MVC typique, vous définissez deux contextes: un pour l'application entière (la racine) et l'autre spécifiquement pour DispatcherServlet (routage, méthodes de gestionnaire, contrôleurs). Vous pouvez obtenir plus de détails ici:

C'est aussi très bien expliqué dans la documentation officielle, ici .

Une erreur courante dans les configurations Spring MVC consiste à déclarer la configuration WebMVC dans le contexte racine avec _@EnableWebMvc_ annoté _@Configuration_ classes ou _<mvc:annotation-driven />_ en XML, mais les beans @Controller dans le contexte de servlet. Etant donné que le contexte racine ne peut accéder au contexte de servlet pour trouver des beans, aucun gestionnaire n'est enregistré et toutes les demandes échouent avec 404. NoSuchBeanDefinitionException, mais l'effet est le même.

Assurez-vous que vos haricots sont enregistrés dans le contexte approprié, c'est-à-dire. 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. DispatcherServlet est responsable de l'acheminement et du traitement des demandes, ainsi tous les beans associés doivent entrer dans son contexte. Le ContextLoaderListener, qui charge le contexte racine, devrait 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 avez essayé d’injecter un tableau de MovieCatalog dans un champ

_@Autowired
private MovieCatalog[] movieCatalogs;
_

Spring trouvera tous les haricots de type MovieCatalog, les emballera dans un tableau et injectera ce tableau. Ceci est décrit dans la Documentation de Spring traitant de _@Autowired_ . Un comportement similaire s'applique aux cibles d'injection Set, List et Collection.

Pour une cible d’injection 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 ajoutera 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 un NoSuchBeanDefinitionException. Parfois, cependant, vous voulez simplement déclarer un haricot de ces types de collection 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 car il n'y a pas de beans Foo dans votre contexte. Mais vous ne vouliez pas d'un bean Foo, vous vouliez un bean _List<Foo>_. Avant Spring 4.3, vous devriez utiliser _@Resource_

Pour les beans qui sont eux-mêmes définis comme un type collection/map ou array, _@Resource_ est une solution de choix, faisant référence à la collection spécifique ou au bean array par nom unique. Cela dit, à partir de la version 4.3 , les types collection/map et array peuvent être mis en correspondance à l'aide de l'algorithme de correspondance de types de Spring _@Autowired_, à condition que les informations de type d'élément sont conservées dans les signatures de type de retour _@Bean_ ou les hiérarchies d'héritage de collection. Dans ce cas, les valeurs de qualificateur peuvent être utilisées pour sélectionner des collections du même type, comme indiqué dans le paragraphe précédent.

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

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

Cependant, cela échouera pour les méthodes _@Bean_, c.-à-d.

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

Ici, Spring ignore tout _@Resource_ ou _@Autowired_ annotant la méthode, car il s'agit d'une méthode _@Bean_ et ne peut donc pas appliquer le problème décrit dans la documentation. Cependant, vous pouvez utiliser le langage SpEL (Spring Expression Language) pour désigner les beans par leur nom. Dans l'exemple ci-dessus, vous pouvez utiliser

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

faire référence au bean nommé fooList et l’injecter.

84