Problem description
A colleague reported an error while developing a new functional test. Basically, when using @AutoWired injection, a class has two beans, one called A and one called B, and Spring does not know which bean injection to use.
He doesn’t declare it. He doesn’t know that this class has two beans. He says he wrote the same as everyone else.
OK, so let’s analyze it.
Problem analysis
Premise: @autowired is auto-assembled byType (byType)
When automatic injection is done by default using only the @AutoWired annotation, there must be one and only one candidate Bean matching in the Spring container.
Note the following when using the @autowired annotation:
-
When no matching Bean is found, the Spring container throws a BeanCreationException indicating that it must have at least one matching Bean.
-
If more than one candidate Bean exists in the Spring context, a BeanCreationException is thrown;
-
A BeanCreationException is also thrown if no candidate Bean exists in the Spring context.
So when using the @Autowired annotation, the following conditions must be met:
-
There are candidate beans of this type in the container
-
The container contains only one candidate Bean of that type
To explore problems
What does that mean? We speak in code.
Create the entity
First, we create an entity class Student:
public class Student{
private String name;
//getter and setter...
}
Copy the code
Constructing multiple beans
We then create multiple instances of Student in the Spring container as follows:
We implemented multiple beans of one type in the Spring configuration file via XML configuration files.
Student = Student; student02 = Student; Student = Student; student02 = Student; student02 = Student;
<bean id="student" class="com.autowiredtest.entity.Student">
<property name="name" value="Little red"/>
</bean>
<bean id="student02" class="com.autowiredtest.entity.Student">
<property name="name" value="Xiao Ming"/>
</bean>
Copy the code
We can also implement multiple beans of one type by using configuration classes + annotations:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class StudentConfiguration{
@Bean
Student student03(a){
Student student = new Student();
student.setName("Wah");
return student;
}
@Bean
Student student04(a){
Student student = new Student();
student.setName("Xiao ling");
returnstudent; }}Copy the code
Of course, the StudentConfiguration configuration class needs to be scanned by the annotation scanner. So now the Student class has four beans, Student, student02, student03, and student04.
(Note: There are two ways to implement multiple beans of a type.)
Use Student’s bean
Declare variables of type Student in controller and inject with @autoWired
@Controller
public class AutowiredTestController{
@Autowired
private Student student;
@RequestMapping("/AutowiredTest")
@ResponseBody
public String loanSign(a){
String docSignUrl = "ok";
System.out.println("-------------- to print ------------");
System.out.println(student.getName());
System.out.println("-------------- Print finished ------------");
returndocSignUrl; }}Copy the code
(Here’s a simple Spring MVC demo to verify the problem.)
Run the web project, no error during, visit http://localhost:8080/AutowiredTest, the console output:
Instead of throwing a BeanCreationException on call, it runs normally, printing a little red to indicate that the bean with id student was injected.
Bold guess: Multiple beans are automatically matched with the bean ID based on the Student variable name!
When @autowired private Student Student; when
Our Student variable name is Student, so when Spring injects it, if there are more than one bean, we will default to the container to find the bean whose id is Student.
verify
Student02, @autoWired Private Student student02
Restart, and access http://localhost:8080/AutowiredTest, the console output:
So our wild guess was right! The Spring version used here is 4.2.0.release.
Permanent link to this article: juejin.cn/post/684490…
Bold guess: inconsistent with the above, is that version compatible with this problem?
verify
Make it a little bit lower. First, change the Spring version to 2.5 (@autoWired first appeared in this version) where @responseBody@Configuration and @bean are no longer available (later versions only), so now you have two beans, Student and student02.
Then start the project, not an error, go to http://localhost:8080/AutowiredTest, an error:
Console error message:
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
Copy the code
Console error output diagram:
Let’s increase the version number to test which version number is starting to be compatible with the problem.
This is the release of the version, approximated by dichotomy.
So it’s safe to assume that Spring 3.x is starting to accommodate this issue and make it more human.
So the rules for using @autowired are about to change:
-
There are candidate beans of this type in the container
-
A container can have multiple candidate beans of this type (after Spring 3.x)
-
After Spring 3.x, when using @autowired alone, the variable name must be the same as one of the beans of that type (@autowired private Student Student; , student is the id of one of the beans.)
-
If the third rule is violated, a BeanCreationException is thrown
-
Spring 3.x can have only one bean before it throws a BeanCreationException
What if we wanted to customize the variable name?
What if WHEN I create a variable I want to customize the variable name? E.g. @autowired private Student stu; For some ides, a warning or an error will be returned immediately after writing.
Other ides are not so smart, such as Eclipse. Well, we’ll have to wait for the test to find out.
Back to the subject, how do you customize a variable name?
There are two methods:
@autoWired and @Qualifier
We can use @Qualifier in conjunction with @AutoWired to solve these problems. The opposite of failing to find a type-matching Bean is that the Spring container will also throw a BeanCreationException at assembly time (after Spring 3.x) if it has multiple candidate beans.
Spring allows us to specify the name of the injected Bean through the @Qualifier annotation, thus disambiguating it. So when @AutoWired is used in conjunction with @Qualifier, the auto-injected policy is changed from byType to byName.
@Autowired
@Qualifier("student")
private Student stu;
Copy the code
Spring will then find the bean with id student to assemble.
In addition, when it is not certain that a Bean of a class is in the Spring container, you can use @AutoWired (required = false) where you need to inject beans of that class automatically. This tells Spring that no error is reported if a matching Bean is not found. (Required defaults to true)
@Autowired(required = false)
public Student stu
Copy the code
BeanCreationException = BeanCreationException = BeanCreationException
2, use @resource (name = “XXX “) annotation
conclusion
After all this analysis, what is the cause of my colleague’s problem? If you look closely at the code, you can see that the bean id is lowercase and the bean ID is lowercase with other suffixes. The variable name he uses is not lowercase, but just the class name (it’s hard to see that it’s written in uppercase), so it doesn’t match any beans. This is the result of careless work and has led to an analysis of @AutoWired injection compatibility.
Ps: why aren’t the @autowired and @Qualifier annotations combined?
While @Autowired can annotate member variables, methods, and constructors, @Qualifier’s annotation objects are member variables, method inputs, and constructors. Because of the annotation object differences, Spring does not unify @Autowired and @Qualifier into one annotation class.
Use the @qualifier annotation for member variables
public class Boss {
@Autowired
private Car car;
@Autowired
@Qualifier("office")
privateOffice office; ... }Copy the code
Use the @qualifier annotation for constructor variables
public class Boss {
private Car car;
private Office office;
@Autowired
public Boss(Car car , @Qualifier("office")Office office){
this.car = car;
this.office = office ; }}Copy the code