Что такое исключение NoSuchBeanDefinitionException и как его исправить?
Пожалуйста, объясните следующее об исключении NoSuchBeanDefinitionException весной:
- Что это значит? При каких условиях она будет выброшена?
- Как я могу предотвратить это?
этот пост предназначен для исчерпывающего Q&A о вхождениях NoSuchBeanDefinitionException в приложениях, использующих Spring.
1 ответ:
The javadoc of
NoSuchBeanDefinitionExceptionобъясняетИсключение, возникающее, когда
BeanFactoryзапрашивается экземпляр bean для которой он не может найти определения. Это может указывать на несуществующий Боб, неуникальный боб или вручную зарегистрированный синглетный экземпляр без соответствующего определения Боба.A
Ниже приведены простые причины, по которымBeanFactoryэто в основном абстракция, представляющая инверсиюSpring контейнера управления . Он подвергает бобы внутреннему воздействию и внешне, к вашему применению. Когда он не может найти или получить эти бобы, он бросаетNoSuchBeanDefinitionException.BeanFactory(или связанные классы) не смогут найти Боб и как вы можете убедиться, что это так.
Боба не существует, он не был зарегистрирован
В приведенном ниже примере
@Configuration public class Example { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class); ctx.getBean(Foo.class); } } class Foo {}Мы не зарегистрировали определение Боба для типа
Fooни через@Beanметод,@Componentсканирование, определение XML, или любым другим способом. Поэтому уBeanFactory, управляемогоAnnotationConfigApplicationContext, нет указания, где получить Боб, запрошенныйgetBean(Foo.class). Фрагмент выше бросаетException in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.Foo] is definedАналогичным образом исключение могло быть вызвано при попытке удовлетворить зависимость
@Autowired. Например,@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 { }Здесь определение Боба регистрируется для
Fooчерез@ComponentScan. Но весна ничего не знает оBar. Поэтому он не может найти соответствующий Боб, пытаясь автоматически подключить полеbarэкземпляр БобаFoo. Он бросает (вложенный в aUnsatisfiedDependencyException)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)}Существует несколько способов регистрации определений бобов.
- Метод
@Beanв классе@Configurationили<bean>в конфигурации XML@Component(и его мета-аннотации, напр.@Repository) через@ComponentScanили<context:component-scan ... />в XML- вручную через
GenericApplicationContext#registerBeanDefinition- вручную через
BeanDefinitionRegistryPostProcessor...и еще.
Убедитесь, что бобы вы ожидание должным образом зарегистрировано.
Распространенной ошибкой является регистрация бобов несколько раз, т. е. смешивание вариантов выше для одного и того же типа. Например, я мог бы иметь
@Component public class Foo {}И XML-конфигурация с
Такая конфигурация будет регистрировать два боба типа<context:component-scan base-packages="com.example" /> <bean name="eg-different-name" class="com.example.Foo />Foo, один с именемfooи другой с именемeg-different-name. Убедитесь, что вы случайно не зарегистрировали больше бобов, чем хотели. Что и приводит нас к этому...Если вы используя конфигурации на основе XML и аннотаций, убедитесь, что вы импортируете одну из другой. XML предоставляет
<import resource=""/>В то время как Java обеспечивает
@ImportResourceаннотация.Ожидался один совпадающий боб, но найдено 2 (или более)
Бывают случаи, когда вам нужно несколько бобов для одного и того же типа (или интерфейса). Например, ваше приложение может использовать две базы данных, экземпляр MySQL и экземпляр Oracle. В таком случае у вас будет два боба
DataSourceдля управления связи с каждым из них. Для (упрощенного) примера, следующее@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 {}Броски
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Поскольку оба Боба, зарегистрированные с помощью методов
@Bean, удовлетворяли требованиюBeanFactory#getBean(Class), то есть. они оба реализуютDataSource. В этом примере Spring не имеет механизма для дифференциации или приоритизации между ними. Но такие механизмы существуют.Вы могли бы использовать
@Primary(и его эквивалент в XML), как описано в документации и в этом посте. С этим изменением@Bean(name = "mysql") @Primary public DataSource mysql() { return new MySQL(); }Предыдущий фрагмент не будет вызывать исключение и вместо этого вернет Боб
mysql.Вы также можете использовать
@Qualifier(и его эквивалент в XML), чтобы иметь больше контроля над процессом выбора бобов, как описано в документации . В то время как@Autowiredв основном используется для автоматического подключения по типу,@Qualifierпозволяет использовать автоматическое подключение по имени. Например,@Bean(name = "mysql") @Qualifier(value = "main") public DataSource mysql() { return new MySQL(); }Теперь можно вводить как
@Qualifier("main") // or @Qualifier("mysql"), to use the bean name private DataSource dataSource;Без проблем.
@Resourceэто тоже вариант.Использование неправильного имени Боба
Точно так же, как существует множество способов регистрации бобов, существует также множество способов их именования.
Имя этого боба или, если множественное число, псевдонимы для этого боба. Если осталось неопределенное имя компонента - это имя аннотированного метода. Если указано, то имя метода: игнорируемый.
<bean>имеет атрибутidдля представления уникального идентификатора для Боба иnameможет использоваться для создания одного или нескольких псевдонимов, недопустимых в идентификаторе (XML).
@Componentи его мета-аннотации имеютvalueЗначение может указывать на предложение для логического имени компонента, чтобы превратиться в пружинный боб в случае автоопределенного компонента.
Если это оставленное неуказанным, имя Боба автоматически генерируется для аннотированного типа, как правило, версия имени типа в нижнем регистре верблюда.
@Qualifier, Как упоминалось ранее, позволяет добавлять дополнительные псевдонимы к Бобу.Убедитесь, что вы используете правильное имя при автопроводке по имени.
Более продвинутые случаи
Профили
Профили определения бобов позволяют регистрировать бобы условно.
@Profile, в частности,Рассмотрим примеры, в которых свойствоУказывает, что компонент имеет право на регистрацию, когда один или активны и другие заданные профили.
Профиль-это именованная логическая группировка, которая может быть активирована программно через
ConfigurableEnvironment.setActiveProfiles(java.lang.String...)или декларативно установив свойствоspring.profiles.activeв качестве JVM системное свойство, как переменная окружения или как контекст сервлета параметр в сети.xml для веб-приложений. Профили также могут быть активируется декларативно в интеграционные тесты с помощью@ActiveProfilesаннотация.spring.profiles.activeне задано.@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 { }Это не покажет никаких активных профилей и бросит
NoSuchBeanDefinitionExceptionдля БобаFoo. Поскольку профильStackOverflowне был активен, Боб не был зарегистрирован.Вместо этого, если я инициализирую
ApplicationContextпри регистрации соответствующего профиляAnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("StackOverflow"); ctx.register(Example.class); ctx.refresh();Боб зарегистрирован и может быть возвращен/введен.
АОП Прокси
Spring используетAOP прокси много для реализации расширенного поведения. Некоторые примеры включают:
- управление транзакциями с
@Transactional- кэширование с
@Cacheable- планирование и асинхронное выполнение с
@Asyncи еще@ScheduledДля достижения этой цели у Spring есть два варианта:
- используйте JDK прокси-класс для создания экземпляра динамического класса во время выполнения, которыйреализует только интерфейсы вашего Бина и делегирует все вызовы методов фактическому экземпляру Бина.
- используйте проксиCGLIB для создания экземпляра динамического класса во время выполнения, который реализует как интерфейсы, так и конкретные типы целевого компонента и делегирует все вызовы методов фактическому экземпляру компонента.
Возьмите этот пример прокси JDK (достигнуто через
@EnableAsyncпо умолчаниюproxyTargetClassизfalse)Здесь Spring пытается найти Боб типа@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()); } }HttpClientImpl, который мы ожидаем найти, потому что тип явно аннотирован@Component. Однако вместо этого мы получаем исключениеException in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.HttpClientImpl] is definedПружина обернула Боб
HttpClientImplи выставила его через объектProxy, который только реализуетHttpClient. Так что вы можете получить его с помощьюctx.getBean(HttpClient.class) // returns a dynamic class: com.example.$Proxy33 // or @Autowired private HttpClient httpClient;Всегда рекомендуется программировать на интерфейсы . Когда ты не можешь, вы можете сказать Spring использовать прокси CGLIB. Например, с помощью
@EnableAsync, вы можете установитьproxyTargetClassКtrue. Аналогичные аннотации (EnableTransactionManagementи др.) имеют сходные атрибуты. XML также будет иметь эквивалентные параметры конфигурации.
ApplicationContextиерархии-Spring MVCSpring позволяет создавать экземпляры
ApplicationContextс другими экземплярамиApplicationContextв качестве родителей, используяConfigurableApplicationContext#setParent(ApplicationContext). Дочерний контекст будет иметь доступ к бобам в Родительском контексте, но не наоборот истинный. этот пост подробно описывает, когда это полезно, особенно в весеннем MVC.В типичном приложении Spring MVC вы определяете два контекста: один для всего приложения (корневой) и один специально для
DispatcherServlet(маршрутизация, методы обработчиков, контроллеры). Вы можете получить более подробную информацию здесь:Это также очень хорошо объяснено в официальной документации, здесь .
Распространенной ошибкой в конфигурациях Spring MVC является объявление конфигурации WebMVC в корневом контексте с
@EnableWebMvcаннотированными@Configurationклассами или<mvc:annotation-driven />в XML, но@Controllerбобы в контексте сервлета. поскольку корневой контекст не может проникнуть в контекст сервлета, чтобы найти какие-либо бобы, обработчики не регистрируются, и все запросы завершаются с ошибкой 404s. вы не увидитеNoSuchBeanDefinitionException, но эффект тот же самый.Убедитесь, что ваши бобы зарегистрированы в соответствующем контексте, т. е. где их можно найти по бобам, зарегистрированным для WebMVC (
HandlerMapping,HandlerAdapter,ViewResolver,ExceptionResolver, и т.д.). Лучшее решение-правильно изолировать бобы.DispatcherServletотвечает за маршрутизацию и обработку запросов, поэтому все связанные бобы должны входить в его контекст.ContextLoaderListener, который загружает корневой контекст, должен инициализировать любые бобы, необходимые для остальной части приложения: службы, хранилища и т. п.Массивы, коллекции и карты
Бобы некоторых известных типов обрабатываются весной особым образом. Например, если вы попытаетесь ввести массив
MovieCatalogв полеВесна найдет все бобы типа@Autowired private MovieCatalog[] movieCatalogs;MovieCatalog, обернет их в массив и введет этот массив. Это описано в весенней документации, обсуждающей@Autowired. Подобное поведение применимо и КSet,List, иCollectionцели для инъекций.Для цели впрыска
Mapпружина также будет вести себя подобным образом, если тип ключаString. Например, если у вас есть@Autowired private Map<String, MovieCatalog> movies;Spring найдет все бобы типа
MovieCatalogи добавит их в качестве значений вMap, где соответствующий ключ будет их именем Боба.Как описано ранее, если нет бобов запрошенного типа, Spring бросит
NoSuchBeanDefinitionException. Иногда, однако, вы просто хотите объявить Боб этих типов коллекций как@Bean public List<Foo> fooList() { return Arrays.asList(new Foo()); }И ввести их
@Autowired private List<Foo> foos;В этом примере Spring потерпит неудачу с
NoSuchBeanDefinitionException, потому что в вашем контексте нет бобовFoo. Но вы не хотели БобаFoo, Вы хотели БобаList<Foo>. до весны 4.3 вам придется использовать@ResourceДля бобов, которые сами определены как коллекция / карта или массив Тип,
@Resourceявляется прекрасным решением, относящимся к конкретному Боб коллекции или массива по уникальному имени. Вот и все, по состоянию на 4.3, типы коллекций / карт и массивов могут быть сопоставлены с помощью Spring's@Autowiredалгоритм подбора типов также, пока элемент информация о типе сохраняется в@Beanсигнатурах возвращаемого типа или иерархии наследования коллекций. В этом случае значения квалификатора могут используется для выбора среди однотипных коллекций, как описано в разделе предыдущий пункт.Это работает для конструктора, сеттера и инжекции поля.
@Resource private List<Foo> foos; // or since 4.3 public Example(@Autowired List<Foo> foos) {}Однако это не удастся для
@Beanметодов, т. е.@Bean public Bar other(List<Foo> foos) { new Bar(foos); }Здесь Spring игнорирует любые
@Resourceили@Autowiredаннотации метода, потому что это метод@Bean, и поэтому не может применять поведение, описанное в документации. Однако вы можете использовать язык выражений Spring (SpEL), чтобы ссылаться на бобы по их имени. В приведенном выше примере вы можете использовать@Bean public Bar other(@Value("#{fooList}") List<Foo> foos) { new Bar(foos); }Обратиться к Бобу с именем
fooListи ввести его.