注:本文是 https://blog.csdn.net/duke_ding2/article/details/125643206
的继续。
创建Maven项目 test0828_2
。
修改 pom.xml
文件,添加依赖:
......
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.21version>
dependency>
......
在 src/main/resources
目录下创建 applicationContext.xml
文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
beans>
在 src/test/java
目录下创建测试:
public class MyTest {
}
创建如下POJO:
Axe
:Axe接口;StoneAxe
:Axe实现类;SteelAxe
:Axe实现类;Person
:Person持有Axepackage pojo;
public interface Axe {
public void chop();
}
package pojo;
public class StoneAxe implements Axe{
public StoneAxe() {
System.out.println("StoneAxe constructor");
}
@Override
public void chop() {
System.out.println("Stone axe!");
}
}
package pojo;
public class SteelAxe implements Axe{
public SteelAxe() {
System.out.println("SteelAxe constructor");
}
@Override
public void chop() {
System.out.println("Steel axe!");
}
}
package pojo;
public class Person {
private String name;
private Axe axe;
public void setAxe(Axe axe) {
this.axe = axe;
}
public void setName(String name) {
this.name = name;
}
public void useAxe() {
System.out.println("I am " + name);
axe.chop();
}
public Person() {
System.out.println("Person constructor");
}
}
要把这几个POJO变成Spring的bean,可以通过XML配置(详见我另一篇文档)。如果不想使用XML配置,也可以用自动搜索的方式。
一方面,在 applicationContext.xml
里,添加如下配置:
<context:component-scan base-package="pojo"/>
表示扫描 pojo
包(及其子包)下的所有类。
另一方面,在这几个POJO加上 @Component
注解,比如:
......
@Component
public class StoneAxe implements Axe{
......
注: @Component
注解有几个变种,比如常见的:
@Controller
@Service
@Repository
创建测试方法如下:
@Test
public void test1() {
var ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("before getBean");
var person = ctx.getBean("person", Person.class);
// person.useAxe();
}
运行测试,结果如下:
Person constructor
SteelAxe constructor
StoneAxe constructor
before getBean
可见,这几个POJO已经被Spring容器管理了。
当然,现在如果调用 person.useAxe()
方法会报NPE错,因为我们还没有把Axe注入到Person里。
本例中,可以从Spring容器中取出ID值为 person
的bean,但我们在设置bean时,其实并没有显式指定其ID值,这是因为缺省的ID值为首字母小写的类名,所以,对于 Person
类,其bean的ID值就是 person
。
也可以显式指定ID值,比如:
......
@Component("chinese")
public class Person {
......
则取出该bean时,要通过ID值 chinese
来取:
var person = ctx.getBean("chinese", Person.class);
下面我们来注入依赖,也就是把Axe注入到Person里。
@Value
:相当于
元素的 value
属性;@Resource
:相当于
元素的 ref
属性;注:要使用 @Resource
注解,需要添加如下依赖:
......
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
......
修改后的 Person
类如下:
package pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class Person {
private String name;
private Axe axe;
@Resource(name="stoneAxe")
public void setAxe(Axe axe) {
this.axe = axe;
}
@Value("Tom")
public void setName(String name) {
this.name = name;
}
public void useAxe() {
System.out.println("I am " + name);
axe.chop();
}
public Person() {
System.out.println("Person constructor");
}
}
现在,就可以调用 person.useAxe()
方法了。
I am Tom
Stone axe!
若 @Resource
不指定 name
属性,则默认值为setter方法名去掉 set
前缀并把首字母小写,本例中默认值为 axe
。
修改后的 Person
类如下:
package pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class Person {
@Value("Tom")
private String name;
@Resource(name="stoneAxe")
private Axe axe;
public void useAxe() {
System.out.println("I am " + name);
axe.chop();
}
public Person() {
System.out.println("Person constructor");
}
}
可见,这种方法更简单,无需setter方法。
@Autowired
可以修饰setter方法,普通方法,实例变量,构造器等。
修改后的 Person
类如下:
package pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Person {
@Value("Tom")
private String name;
private Axe axe;
@Autowired
public void setAxe(Axe axe) {
this.axe = axe;
}
public void useAxe() {
System.out.println("I am " + name);
axe.chop();
}
public Person() {
System.out.println("Person constructor");
}
}
运行测试,报错,这是因为默认采用byType的策略。 SteelAxe
和 StoneAxe
都满足条件,所以报错了。保留二者之一就不报错了。
总结:
No qualifying bean of type 'pojo.Axe' available: expected single matching bean but found 2: steelAxe,stoneAxe
;NoSuchBeanDefinitionException: No qualifying bean of type 'pojo.Axe' available: expected at least 1 bean which qualifies as autowire candidate
;注:在Spring 4.3及以后的版本中,可以省略 @Autowired
注解?(然而我试了并不能……)
这是目前最常见的方式,但是Spring已经不推荐使用了,推荐使用构造器注入。
修改后的 Person
类如下:
package pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Person {
@Value("Tom")
private String name;
@Autowired
private Axe axe;
public void useAxe() {
System.out.println("I am " + name);
axe.chop();
}
public Person() {
System.out.println("Person constructor");
}
}
同样,这种方法更简单,无需setter方法。
当有0个、1个、多个类满足条件时,报错与否的情况与上面的setter方法注入相同。
修改后的 Person
类如下:
package pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Person {
@Value("Tom")
private String name;
private Axe axe;
public void useAxe() {
System.out.println("I am " + name);
axe.chop();
}
@Autowired
public Person(Axe axe) {
this.axe = axe;
}
}
当然,可以把 name
的注入也放到构造器里:
......
// @Value("Jerry")
private String name;
......
@Autowired
public Person(@Value("Tom") String name, Axe axe) {
this.name = name;
this.axe = axe;
}
......
如果在实例变量和构造器两处都注入(比如本例中的注释行),则最终实例变量注入生效(构造在先,实例变量注入在后)。
当有0个、1个、多个类满足条件时,报错与否的情况与上面的setter方法注入相同。
注:在Spring 4.3及以后的版本中,如果只有一个构造方法,则可以省略 @Autowired
注解。(亲测有效)
@Autowired
注解与XML配置的 autowire="byType"
的区别在于:前者如果找不到满足条件的类,会报错,而后者不会报错。
要想不报错,可以考虑如下方法:
@Autowired(required = false)
注:该方法对构造器注入无效,仍然会报错。
当然,如果调用 person.useAxe()
方法,还是会报NPE错。
添加 @Nullable
注解。
@Autowired
public void setAxe(@Nullable Axe axe) {
this.axe = axe;
}
@Autowired
@Nullable
private Axe axe;
@Autowired
public Person(@Nullable Axe axe) {
this.axe = axe;
}
当然,如果调用 person.useAxe()
方法,还是会报NPE错。
要想在有多个类满足条件时不报错,需要挑选其中一个,可以考虑如下方法:
使用 @Primary
注解,在冲突时,会优先使用该注解所修饰的类。
@Component
@Primary
public class SteelAxe implements Axe{
......
该方法对于setter方法注入、实例变量注入、构造器注入都有效。
在注入时,使用 @Qualifier
注解指定类。
@Autowired
// @Qualifier("stoneAxe")
public void setAxe(@Qualifier("stoneAxe")Axe axe) {
this.axe = axe;
}
用 @Qualifier
注解修饰指定的参数。当只有一个参数时,也可以把注解直接放在setter方法上。
@Autowired
@Qualifier("stoneAxe")
private Axe axe;
@Autowired
public Person(@Qualifier("stoneAxe") Axe axe) {
this.axe = axe;
}
注: @Qualifier
注解不能放在构造器方法上,只能修饰指定的参数。
但是,与其使用 @Autowired
+ @Qualifier
的组合,不如直接使用 @Resource
来指定类。注意:前者是Spring的注解,后者是Java自身的注解。
@DependsOn
:强制初始化其它bean。比如 A
与 B
都是Spring的bean,它们之间并没有依赖注入的关系,而 A
调用了 B
的方法,显然 B
应该在 A
之前初始化;@Lazy
:指定Spring初始化时,是否初始化该bean,比如 @Lazy(true)
;