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