Что такое исключение NoSuchBeanDefinitionException и как его исправить?


Пожалуйста, объясните следующее об исключении NoSuchBeanDefinitionException весной:

  • Что это значит?
  • При каких условиях она будет выброшена?
  • Как я могу предотвратить это?

этот пост предназначен для исчерпывающего Q&A о вхождениях NoSuchBeanDefinitionException в приложениях, использующих Spring.

1 34

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. Он бросает (вложенный в a 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)}

Существует несколько способов регистрации определений бобов.

  • Метод @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 имеет name

Имя этого боба или, если множественное число, псевдонимы для этого боба. Если осталось неопределенное имя компонента - это имя аннотированного метода. Если указано, то имя метода: игнорируемый.

<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 прокси много для реализации расширенного поведения. Некоторые примеры включают:

Для достижения этой цели у Spring есть два варианта:

  1. используйте JDK прокси-класс для создания экземпляра динамического класса во время выполнения, которыйреализует только интерфейсы вашего Бина и делегирует все вызовы методов фактическому экземпляру Бина.
  2. используйте проксиCGLIB для создания экземпляра динамического класса во время выполнения, который реализует как интерфейсы, так и конкретные типы целевого компонента и делегирует все вызовы методов фактическому экземпляру компонента.

Возьмите этот пример прокси JDK (достигнуто через @EnableAsync по умолчанию proxyTargetClass из 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 пытается найти Боб типа 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 MVC

Spring позволяет создавать экземпляры 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 и ввести его.