Veuillez expliquer ce qui suit à propos de NoSuchBeanDefinitionException
exception au printemps:
Cet article est conçu pour être un Q & A complet sur les occurrences de NoSuchBeanDefinitionException
dans des applications utilisant Spring.
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.
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.
@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 XMLGenericApplicationContext#registerBeanDefinition
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
.
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.
Tout comme il existe plusieurs façons d’enregistrer des beans, il existe également de nombreuses manières de les nommer.
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.
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é.
Spring utilise proxy AOP beaucoup pour implémenter un comportement avancé. Quelques exemples incluent:
@Transactional
@Cacheable
@Async
et @Scheduled
Pour ce faire, Spring a deux options:
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 MVCSpring 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.
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.