Qu'est-ce Qu'une NoSuchBeanDefinitionException et comment la corriger?

Veuillez expliquer ce qui suit au sujet de NoSuchBeanDefinitionException exception au printemps:

  • que signifie-t-il?
  • dans quelles conditions elle être levée?
  • Comment puis-je l'empêcher?

ce message est conçu pour être un Q&A complet sur les occurrences de NoSuchBeanDefinitionException dans les applications utilisant le ressort.

34
demandé sur Sotirios Delimanolis 2016-08-26 23:08:22

1 réponses

Le javadoc NoSuchBeanDefinitionException explique

Exception lancée quand un BeanFactory est demandé pour une instance de haricot qui il ne peut pas trouver une définition. Ce qui pourrait indiquer un non-existant haricot, un haricot non unique, ou une instance unique enregistrée manuellement sans définition de haricot associée.

Un BeanFactory est fondamentalement le abstraction représentant Inversion du ressort du conteneur de commande . Il expose les haricots à l'interne et à l'externe, à votre demande. Quand il ne peut pas trouver ou récupérer ces haricots, il lance un NoSuchBeanDefinitionException .

ci-dessous sont des raisons simples pourquoi un BeanFactory (ou classes connexes) ne serait pas en mesure de trouver un haricot et comment vous pouvez vous assurer qu'il fait.


Le haricot n'existe pas, il n'était pas 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 fève pour le type Foo soit par une méthode @Bean , @Component balayage, une définition XML, ou de toute autre manière. Le BeanFactory géré par le AnnotationConfigApplicationContext n'a donc aucune indication quant à l'endroit où obtenir le haricot 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

une exception aurait pu être faite 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 haricot est enregistrée pour Foo à @ComponentScan . Mais le printemps ne sait rien de Bar . Il ne parvient donc pas à trouver un haricot correspondant tout en essayant d'autowire le champ bar de l'instance Foo haricot. Il jette (niché à l'intérieur d'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 y a plusieurs façons d'enregistrer les définitions de haricots.

  • @Bean méthode dans une classe @Configuration ou <bean> en configuration XML
  • @Component (et son méta-annotations, par exemple. @Repository ) à @ComponentScan ou <context:component-scan ... /> en XML
  • manuellement GenericApplicationContext#registerBeanDefinition
  • manuellement BeanDefinitionRegistryPostProcessor

...et de plus en plus.

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

Une erreur commune est d'enregistrer les haricots 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 enregistrer deux haricots 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 vouliez. Ce qui nous mène à...

si vous utilisez à la fois des configurations XML et des configurations basées sur des annotations, assurez-vous d'importer l'une de l'autre. XML fournit

<import resource=""/>

tandis que Java fournit le @ImportResource annotation.

peut-être un haricot correspondant, mais trouvé 2 (ou plus)

il y a des moments où vous avez besoin de plusieurs haricots 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 haricots DataSource pour gérer les connexions à chacun. Pour l'exemple (simplifié), le

@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 {}

lancers

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 fèves enregistrées selon les méthodes @Bean satisfaisaient à l'exigence de BeanFactory#getBean(Class) , ie. tous les deux implémentent DataSource . Dans cet exemple, Spring n'a pas de mécanisme pour faire la différence ou établir un ordre de priorité entre les deux. Mais de tels mécanismes existent.

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

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

l'extrait précédent ne lancerait pas l'exception et retournerait plutôt le haricot mysql .

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 la documentation . Alors que @Autowired est principalement utilisé pour autowire par type, @Qualifier vous permet d'autowire par nom. Par exemple,

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

peut maintenant être injecté comme

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

Sans objet. @Resource est également une option.

utilisant le mauvais nom de haricot

tout comme il existe de multiples façons d'enregistrer les haricots, il y a aussi plusieurs façons de les nommer.

@Bean a name

le nom de cette fève, ou si pluriel, Alias pour cette fève. Si la gauche non spécifié le nom du haricot 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 pour un haricot et name peut être utilisé pour créer un ou plus d'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 composante logique, à être transformé en haricot de printemps dans le cas d'un composant autodétecté.

si ce n'est pas spécifié, un nom de haricot est automatiquement généré pour le type annoté, typiquement la version de cas de chameau inférieure du nom de type.

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

assurez-vous d'utiliser le bon nom lorsque vous autorisez par nom.


cas plus avancés

Profils

Bean definition profils vous permettent d'enregistrer les haricots conditionnellement. @Profile , plus précisément,

Indique qu'un composant est admissible à l'enregistrement lorsqu'un ou plusieurs profils sont actifs.

un profil est un groupe logique nommé qui peut être activé par programmation via ConfigurableEnvironment.setActiveProfiles(java.lang.String...) ou sur le plan déclaratif, en fixant le spring.profiles.active propriété en tant que coentreprise propriété du système, comme variable d'environnement, ou comme contexte de Servlet paramètre dans le web.xml pour les applications web. Les profils peuvent également être activé de manière déclarative dans les essais d'intégration via le @ActiveProfiles annotation.

considérez ces exemples 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 pas de profils actifs et jeter un NoSuchBeanDefinitionException pour un Foo bean. Comme le profil StackOverflow n'était pas actif, le haricot n'était pas enregistré.

à la place, si j'initialise le ApplicationContext en enregistrant le profil approprié

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

la fève est enregistrée et peut être retournée/injectée.

AOP Proxies

Printemps utilise AOP proxies beaucoup de choses à mettre en œuvre un comportement. Quelques exemples inclure:

pour ce faire, le ressort a deux options:

  1. utilisez la classe Proxy de JDK pour créer une instance d'une classe dynamique à l'exécution qui n'implémente que les interfaces de votre bean et délègue toutes les invocations de méthode à une instance de bean réelle.
  2. Utiliser CGLIB proxies pour créer une instance d'une classe dynamique lors 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éthode à une instance de haricot réelle.

Prenez cet exemple de JDK proxies (réalisé par @EnableAsync 's default 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, le printemps tente de trouver un haricot de type HttpClientImpl que nous nous attendons à trouver parce que le type est clairement annoté avec @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

a enveloppé la fève HttpClientImpl et l'a exposée à travers un objet Proxy qui ne met en œuvre 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 programmer aux interfaces . Quand vous ne pouvez pas, vous pouvez dire à Spring D'utiliser des proxies CGLIB. Par exemple, avec @EnableAsync , vous pouvez définir proxyTargetClass à true . Annotations similaires ( EnableTransactionManagement , etc.) ont des attributs similaires. Le XML aura également des options de configuration équivalentes.

ApplicationContext Hierarchies-Spring MVC

Spring vous permet de construire ApplicationContext instances avec d'autres ApplicationContext instances en tant que parents, en utilisant ConfigurableApplicationContext#setParent(ApplicationContext) . Un contexte enfant aura accès à des haricots dans le contexte parent, mais l'inverse n'est pas vrai. Ce post va dans le détail lorsque cela est utile, en particulier dans Spring MVC.

dans une application MVC de printemps typique, vous définissez deux contextes: l'un pour l'application entière (la racine) et l'autre spécifiquement pour la DispatcherServlet (routing, handler methods, controllers). 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 MVC de printemps est de déclarer la configuration WebMVC dans le contexte racine avec @EnableWebMvc annoté @Configuration classes ou <mvc:annotation-driven /> en XML, mais le @Controller les haricots dans le contexte de servlet. étant donné que le contexte root ne peut pas atteindre le contexte servlet pour trouver des haricots, aucun gestionnaire n'est enregistré et toutes les requêtes échouent avec 404s. vous ne verrez pas de NoSuchBeanDefinitionException , mais l'effet est le même.

assurez-vous que vos haricots sont inscrits dans le contexte approprié, c'est à dire. où ils peuvent être trouvés par les haricots enregistrés pour WebMVC ( HandlerMapping , HandlerAdapter , ViewResolver , ExceptionResolver , etc.). La meilleure solution est d'isoler correctement les haricots. Le DispatcherServlet est responsable de l'acheminement et du traitement des demandes, de sorte que toutes les demandes doivent être replacées dans leur contexte. Le ContextLoaderListener , qui charge le contexte racine, devrait initialiser tous les beans le reste de vos besoins d'application: services, dépôts, etc.

tableaux, collections et cartes

haricots de certains types connus sont manipulés de manière spéciale au printemps. Pour exemple, si vous avez essayé d'injecter un tableau de MovieCatalog dans un champ

@Autowired
private MovieCatalog[] movieCatalogs;

Spring trouvera tous les haricots du type MovieCatalog , les enveloppera dans un tableau, et injectera ce tableau. Ceci est décrit dans la documentation de ressort discutant @Autowired . Un comportement similaire s'applique aux cibles d'injection Set , List et Collection .

pour une cible d'injection Map , Le ressort se comportera aussi de cette façon si le type de clé est String . Par exemple, si vous avez

@Autowired
private Map<String, MovieCatalog> movies;

Spring trouvera tous les haricots du 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 juste déclarer un haricot de ces types de collecte comme

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

et les injecter

@Autowired
private List<Foo> foos;

dans cet exemple, le printemps échouerait avec un NoSuchBeanDefinitionException parce qu'il n'y a pas de haricots Foo dans votre contexte. Mais vous ne vouliez pas un haricot Foo , vous vouliez un haricot List<Foo> . avant le printemps 4.3, vous devez utiliser @Resource

Pour les haricots qui sont eux-mêmes définis comme une collection/carte ou un tableau type, @Resource est une solution fine, se référant à la spécifique collection ou un tableau de haricot par un nom unique. Cela dit, au 4.3 , les types collection / map et array peuvent être appariés à travers le printemps @Autowired type algorithme d'appariement ainsi, aussi longtemps que l'élément l'information sur le type est conservée dans les signatures de type @Bean ou la collecte des hiérarchies d'héritage. Dans ce cas, les valeurs qualifiées peuvent être utilisé pour sélectionner parmi les collections dactylographiées, comme indiqué dans le paragraphe précédent.

cela fonctionne pour le constructeur, setter, et injection de terrain.

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

Toutefois, il ne sera pas pour @Bean méthodes, c'est à dire.

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

ici, le ressort ignore n'importe quel @Resource ou @Autowired annotant la méthode, parce que c'est une @Bean méthode, et ne peut donc pas appliquer le comportement décrit dans le documentation. Cependant, vous pouvez utiliser le langage D'Expression de printemps (SpEL) pour se référer aux haricots par leur nom. Dans l'exemple ci-dessus, vous pouvez utiliser

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

pour faire référence à la fève nommée fooList et injecter cela.

63
répondu Sotirios Delimanolis 2018-09-26 14:13:41