Что такое исключение 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
и ввести его.