Spring依赖注入的几种方式(注解)

12 篇文章 0 订阅

注:本文是 https://blog.csdn.net/duke_ding2/article/details/125643206 的继续。

环境

  • Ubuntu 22.04
  • IntelliJ IDEA 2022.1.3
  • JDK 17.0.3
  • Spring 5.3.21

准备

创建Maven项目 test0828_2

修改 pom.xml 文件,添加依赖:

        ......
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.21</version>
        </dependency>
        ......

src/main/resources 目录下创建 applicationContext.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<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持有Axe
package 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和@Resource

  • @Value :相当于 <property/> 元素的 value 属性;
  • @Resource :相当于 <property/> 元素的 ref 属性;

注:要使用 @Resource 注解,需要添加如下依赖:

......
        <!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
......

setter方法注入

修改后的 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

@Autowired 可以修饰setter方法,普通方法,实例变量,构造器等。

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的策略。 SteelAxeStoneAxe 都满足条件,所以报错了。保留二者之一就不报错了。

总结:

  • 如果只有一个类满足条件,则以该bean为参数运行setter方法;
  • 如果有多个类满足条件,则会报异常 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"的区别在于:前者如果找不到满足条件的类,会报错,而后者不会报错。

要想不报错,可以考虑如下方法:

方法1( required = false )
@Autowired(required = false)

注:该方法对构造器注入无效,仍然会报错。

当然,如果调用 person.useAxe() 方法,还是会报NPE错。

方法2(@Nullable)

添加 @Nullable 注解。

  • 对于setter方法注入:
    @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错。

有多个类满足条件

要想在有多个类满足条件时不报错,需要挑选其中一个,可以考虑如下方法:

方法1(@Primary)

使用 @Primary 注解,在冲突时,会优先使用该注解所修饰的类。

@Component
@Primary
public class SteelAxe implements Axe{
......

该方法对于setter方法注入、实例变量注入、构造器注入都有效。

方法2(@Qualifier)

在注入时,使用 @Qualifier 注解指定类。

  • 对于setter方法注入:
    @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和@Lazy

  • @DependsOn :强制初始化其它bean。比如 AB 都是Spring的bean,它们之间并没有依赖注入的关系,而 A 调用了 B 的方法,显然 B 应该在 A 之前初始化;
  • @Lazy :指定Spring初始化时,是否初始化该bean,比如 @Lazy(true)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值