описание проблемы
Коллега сообщил об ошибке при разработке нового функционального теста.Вообще говоря, при использовании @Autowired инъекции класс имеет два bean-компонента, один называется a, а другой называется b, и Spring не знает, какой bean-компонент использовать для внедрения.
В общем, в данном случае, какой бин нужно инжектить, он его не объявлял, он не знал, что в этом классе два бина, он говорил, что это то же самое, что писали другие, и больше ничего не сообщалось.
Хорошо, давайте проанализируем это.
анализ проблемы
Предпосылка: @Autowired автоматически подключается в соответствии с типом (byType)
При использовании только аннотации @Autowired для автоматического внедрения по умолчанию количество подходящих компонентов-кандидатов в контейнере Spring должно быть одним и только одним.
Обратите внимание на следующее при использовании аннотации @Autowired:
-
Когда соответствующий bean-компонент не может быть найден, контейнер Spring выдает исключение BeanCreationException, заявляя, что должен быть хотя бы один соответствующий bean-компонент.
-
BeanCreationException выбрасывается, если в контексте Spring существует более одного компонента-кандидата;
-
Исключение BeanCreationException также выбрасывается, если компонент-кандидат не существует в контексте Spring.
Таким образом, при использовании аннотации @Autowired должны быть выполнены следующие условия:
-
В контейнере есть компоненты-кандидаты этого типа.
-
В контейнере есть только один компонент-кандидат этого типа.
запрос проблемы
Что это обозначает? Мы говорим кодом.
Создать сущность
Во-первых, мы создаем класс сущностей Student :
public class Student{
private String name;
//getter and setter...
}
Создание нескольких bean-компонентов
Затем мы создаем несколько экземпляров Student в контейнере Spring следующим образом:
мы проходимXML-файл конфигурацииспособ реализовать несколько bean-компонентов одного типа в файле конфигурации Spring.
Как показано ниже, создаются два компонента Student, идентификаторы — student и student02, а атрибуты имени соответствующих компонентов — Xiaohong и Xiaoming соответственно.
<bean id="student" class="com.autowiredtest.entity.Student">
<property name="name" value="小红"/>
</bean>
<bean id="student02" class="com.autowiredtest.entity.Student">
<property name="name" value="小明"/>
</bean>
Мы также можем использоватьКласс конфигурации + аннотацияспособ реализации нескольких компонентов типа:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class StudentConfiguration{
@Bean
Student student03(){
Student student = new Student();
student.setName("小华");
return student;
}
@Bean
Student student04(){
Student student = new Student();
student.setName("小玲");
return student;
}
}
Разумеется, этот класс конфигурации StudentConfiguration нужно сканировать сканером аннотаций, то есть пакет, в котором находится этот класс, должен быть добавлен в конфигурацию сканирования пакетов. Итак, теперь в классе Student есть 4 bean-компонента: student, student02, student03 и student04.
(Примечание: здесь есть два способа реализации нескольких bean-компонентов одного типа.)
Использовать студенческий компонент
Объявить переменную типа Student в контроллере и внедрить ее с помощью @Autowired
@Controller
public class AutowiredTestController{
@Autowired
private Student student;
@RequestMapping("/AutowiredTest")
@ResponseBody
public String loanSign(){
String docSignUrl = "ok";
System.out.println("--------------要打印了------------");
System.out.println(student.getName());
System.out.println("--------------打印结束------------");
return docSignUrl;
}
}
(Вот используйте простую небольшую демонстрацию Spring MVC, чтобы проверить эту проблему.)
Запустите веб-проект, за указанный период об ошибках не сообщается, посетите http://localhost:8080/AutowiredTest, вывод консоли:
Это странно? Не соответствует вышесказанному! Здесь 4 экземпляра класса Student, поэтому об ошибке не сообщается.Вместо того, чтобы генерировать исключение BeanCreationException при вызове, он работает нормально и выводит [Xiaohong], указывая на то, что был внедрен bean-компонент с идентификатором студента.
Смелое предположение: когда есть несколько компонентов, идентификатор компонента автоматически сопоставляется в соответствии с именем переменной Student!
То есть: когда@Autowired private Student student;
Время
Наша переменная Student имеет имя student , поэтому, когда Spring внедряет ее, если имеется несколько компонентов, она перейдет к контейнеру, чтобы найти компонент, идентификатор компонента которого по умолчанию является student.
проверять
Измените имя переменной Student на student02,@Autowired private Student student02
Перезапустите и посетите http://localhost:8080/AutowiredTest, вывод консоли:
Точно так же он изменился на Student03, а консоль Student04 выводит Xiaohua, Xiaoling.Так что наша смелая догадка верна! Здесь используется версия Spring 4.2.0.RELEASE.
Постоянная ссылка на эту статью:nuggets.capable/post/684490…
Смелое предположение: несовместимая с вышеизложенным версия совместима с этой проблемой?
проверять
Немного поменяйте версию. Во-первых, изменить версию Spring на 2.5 (@Autowired впервые появился в этой версии), в это время нельзя использовать @ResponseBody, @Configuration и @Bean (можно использовать только более поздние версии), поэтому теперь есть два Bean , студент и студент02.
В это время запустите проект, не сообщая об ошибке, посетите http://localhost:8080/AutowiredTest и сообщите об ошибке:
Сообщение об ошибке консоли:
1 严重: Context initialization failed
2 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'autowiredTestController': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.autowiredtest.entity.Student com.autowiredtest.controller.AutowiredTestController.student02; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]
3 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:231)
4 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:978)
5 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
6 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:485)
7 at java.security.AccessController.doPrivileged(Native Method)
8 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:455)
9 at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
10 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:169)
11 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
12 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:170)
13 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:413)
14 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:735)
15 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:369)
16 at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:332)
17 at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:266)
18 at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:236)
19 at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:126)
20 at javax.servlet.GenericServlet.init(GenericServlet.java:158)
21 at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1279)
22 at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1192)
23 at org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:864)
24 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:134)
25 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
26 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
27 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
28 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
29 at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
30 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
31 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
32 at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
33 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
34 at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2441)
35 at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2430)
36 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
37 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
38 at java.lang.Thread.run(Thread.java:745)
39 Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.autowiredtest.entity.Student com.autowiredtest.controller.AutowiredTestController.student02; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]
40 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:375)
41 at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:61)
42 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:228)
43 ... 35 more
44 Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.autowiredtest.entity.Student] is defined: expected single matching bean but found 2: [student, student02]
45 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveDependency(AbstractAutowireCapableBeanFactory.java:425)
46 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredElement.inject(AutowiredAnnotationBeanPostProcessor.java:361)
47 ... 37 more
График вывода ошибок консоли:
Ключевая информация об исключении: В это время было сообщено об ожидаемой ошибке.Давайте увеличим номер версии, чтобы проверить, какой номер версии совместим с этой проблемой.
Это выпуск версии, аппроксимированной дихотомией.
Без проблем перейти с текущей версии 4.2.0.RELEASE на 3.2.18.RELEASE (последняя версия Spring 3.x), 3.0.0.RELEASE без проблем, 2.5.5 сообщает об ошибке, 2.5. 6 Ошибка, ошибка 2.5.6.SEC03 (последняя версия 2.х).Поэтому можно сделать вывод, что с самого начала Spring 3.x совместим с этой проблемой, тем более человечной.
Таким образом, приведенные выше правила использования @Autowired будут изменены:
-
В контейнере есть компоненты-кандидаты этого типа.
-
Контейнер может содержать несколько компонентов-кандидатов этого типа (после Spring 3.x).
-
После Spring 3.x при использовании только @Autowired имя переменной должно быть таким же, как у одного из нескольких bean-компонентов этого типа (т.
@Autowired private Student student;
, student — это идентификатор одного из нескольких bean-компонентов) -
Если третье правило будет нарушено, будет выброшено исключение BeanCreationException.
-
До Spring 3.x может быть только один bean-компонент, в противном случае генерируется исключение BeanCreationException.
Что, если мы хотим настроить имя переменной?
Что, если я просто хочу настроить имя переменной при создании переменной? Такие как: @Autowired private Student stu; В этом случае для некоторых IDE они будут выдавать предупреждение или сообщать об ошибке сразу после написания этого, см.:
idea говорит вам прямо, что сейчас есть два bean-компонента, один называется student, а другой — student02.Имя переменной, которое вы сейчас пишете, не относится ни к одному из этих двух типов.Если вы напишете его неправильно, это выдаст вам ошибку!Для других IDE это не так умно, как для eclipse. Это можно узнать только при тестировании.
Вернемся к теме, как настроить имя переменной?
Есть 2 метода:
1. @Autowired и @Qualifier
Мы можем использовать @Qualifier с @Autowired для решения этих проблем. Ошибка, противоположная ненахождению bean-компонента, соответствующего типу, заключается в том, что контейнер Spring также выдает исключение BeanCreationException во время сборки (после Spring 3.x), если в контейнере Spring имеется несколько bean-компонентов-кандидатов.
Spring позволяет нам указать имя внедренного bean-компонента с помощью аннотации @Qualifier, что устраняет двусмысленность. Таким образом, когда @Autowired используется в сочетании с @Qualifier, стратегия автоматического внедрения меняется с byType на byName.
@Autowired
@Qualifier("student")
private Student stu;
Таким образом, Spring найдет bean-компонент, идентификатор которого является student для сборки.
Кроме того, когда нет уверенности в том, что контейнер Spring должен иметь bean-компонент определенного класса, его можно использовать там, где необходимо автоматически внедрить bean-компонент этого класса.@Autowired(required = false)
, что эквивалентно указанию Spring не сообщать об ошибке, если соответствующий bean-компонент не найден. (по умолчанию требуется true)
как:
@Autowired(required = false)
public Student stu
Но idea для вас не используется и все равно выдает сообщение об ошибке.Хотя вы можете проигнорировать его и продолжить запуск в это время, BeanCreationException все равно будет сообщено при доступе:
Конечно, при нормальных обстоятельствах использование @Autowired требует Bean-инъекции.Использование автоматической инъекции, но без возможности внедрения, обычно встречается только в среде разработки или тестирования (например, чтобы быстро запустить контейнер Spring, только введение файлов конфигурации Spring для некоторых модулей), поэтому @Autowired(required = false) используется редко.2. Аннотировать с помощью @Resource(name = "xxx")
Это то же самое, что и @Autowired и @Qualifier, и может рассматриваться как их комбинация.Суммировать
После стольких анализов, в чем проблема с моим коллегой? Внимательно изучив код, можно увидеть, что один идентификатор двух bean-компонентов — это имя класса в нижнем регистре, а другой идентификатор — это имя класса в нижнем регистре с добавлением других суффиксов. И имя переменной, которое он использует, — это не имя класса в нижнем регистре, а непосредственно имя класса (сложно найти, что оно написано в верхнем регистре), поэтому оно не может соответствовать ни одному бину. Это вызвано небрежной работой, а также привело к исследованиям и анализу совместимости инъекций @Autowired.
P.S. Почему аннотации @Autowired и @Qualifier не объединены в одну?
@Autowired может аннотировать переменные-члены, методы и конструкторы, а объекты аннотации @Qualifier — это переменные-члены, параметры метода и параметры конструктора. Именно из-за разницы в объектах аннотаций Spring не объединяет @Autowired и @Qualifier в один класс аннотаций.
//对成员变量使用 @Qualifier 注释
public class Boss {
@Autowired
private Car car;
@Autowired
@Qualifier("office")
private Office office;
…
}
//对构造函数变量使用 @Qualifier 注释
public class Boss {
private Car car;
private Office office;
@Autowired
public Boss(Car car , @Qualifier("office")Office office){
this.car = car;
this.office = office ;
}
}