Дискуссии, вызванные использованием Spring Framework автопроводки @Autowired

Spring

описание проблемы

Коллега сообщил об ошибке при разработке нового функционального теста.Вообще говоря, при использовании @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 ;     
     }     
}