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


    注:本文是 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 文件,添加依赖:

            ......
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
    
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-webmvcartifactId>
                <version>5.3.21version>
            dependency>
            ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    src/test/java 目录下创建测试:

    public class MyTest {
    
    }
    
    • 1
    • 2
    • 3

    创建如下POJO:

    • Axe :Axe接口;
    • StoneAxe :Axe实现类;
    • SteelAxe :Axe实现类;
    • Person :Person持有Axe
    package pojo;
    
    public interface Axe {
        public void chop();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    package pojo;
    
    public class StoneAxe implements Axe{
        public StoneAxe() {
            System.out.println("StoneAxe constructor");
        }
    
        @Override
        public void chop() {
            System.out.println("Stone axe!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    package pojo;
    
    public class SteelAxe implements Axe{
        public SteelAxe() {
            System.out.println("SteelAxe constructor");
        }
    
        @Override
        public void chop() {
            System.out.println("Steel axe!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    要把这几个POJO变成Spring的bean,可以通过XML配置(详见我另一篇文档)。如果不想使用XML配置,也可以用自动搜索的方式。

    一方面,在 applicationContext.xml 里,添加如下配置:

        <context:component-scan base-package="pojo"/>
    
    • 1

    表示扫描 pojo 包(及其子包)下的所有类。

    另一方面,在这几个POJO加上 @Component 注解,比如:

    ......
    @Component
    public class StoneAxe implements Axe{
    ......
    
    • 1
    • 2
    • 3
    • 4

    注: @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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行测试,结果如下:

    Person constructor
    SteelAxe constructor
    StoneAxe constructor
    before getBean
    
    • 1
    • 2
    • 3
    • 4

    可见,这几个POJO已经被Spring容器管理了。

    当然,现在如果调用 person.useAxe() 方法会报NPE错,因为我们还没有把Axe注入到Person里。

    本例中,可以从Spring容器中取出ID值为 person 的bean,但我们在设置bean时,其实并没有显式指定其ID值,这是因为缺省的ID值为首字母小写的类名,所以,对于 Person 类,其bean的ID值就是 person

    也可以显式指定ID值,比如:

    ......
    @Component("chinese")
    public class Person {
    ......
    
    • 1
    • 2
    • 3
    • 4

    则取出该bean时,要通过ID值 chinese 来取:

            var person = ctx.getBean("chinese", Person.class);
    
    • 1

    依赖注入

    下面我们来注入依赖,也就是把Axe注入到Person里。

    @Value和@Resource

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

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

    ......
            
            <dependency>
                <groupId>javax.annotationgroupId>
                <artifactId>javax.annotation-apiartifactId>
                <version>1.3.2version>
            dependency>
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    现在,就可以调用 person.useAxe() 方法了。

    I am Tom
    Stone axe!
    
    • 1
    • 2

    @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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    可见,这种方法更简单,无需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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行测试,报错,这是因为默认采用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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    同样,这种方法更简单,无需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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    当然,可以把 name 的注入也放到构造器里:

    ......
    //    @Value("Jerry")
        private String name;
    ......
        @Autowired
        public Person(@Value("Tom") String name, Axe axe) {
            this.name = name;
            this.axe = axe;
        }
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果在实例变量和构造器两处都注入(比如本例中的注释行),则最终实例变量注入生效(构造在先,实例变量注入在后)。

    当有0个、1个、多个类满足条件时,报错与否的情况与上面的setter方法注入相同。

    注:在Spring 4.3及以后的版本中,如果只有一个构造方法,则可以省略 @Autowired 注解。(亲测有效)

    没有类满足条件

    @Autowired 注解与XML配置的 autowire="byType"的区别在于:前者如果找不到满足条件的类,会报错,而后者不会报错。

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

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

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

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

    方法2(@Nullable)

    添加 @Nullable 注解。

    • 对于setter方法注入:
        @Autowired
        public void setAxe(@Nullable Axe axe) {
            this.axe = axe;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 对于实例变量注入:
        @Autowired
        @Nullable
        private  Axe axe;
    
    • 1
    • 2
    • 3
    • 对于构造器注入:
        @Autowired
        public Person(@Nullable Axe axe) {
            this.axe = axe;
        }
    
    • 1
    • 2
    • 3
    • 4

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

    有多个类满足条件

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

    方法1(@Primary)

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

    @Component
    @Primary
    public class SteelAxe implements Axe{
    ......
    
    • 1
    • 2
    • 3
    • 4

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

    方法2(@Qualifier)

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

    • 对于setter方法注入:
        @Autowired
    //    @Qualifier("stoneAxe")
        public void setAxe(@Qualifier("stoneAxe")Axe axe) {
            this.axe = axe;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    @Qualifier 注解修饰指定的参数。当只有一个参数时,也可以把注解直接放在setter方法上。

    • 对于实例变量注入:
        @Autowired
        @Qualifier("stoneAxe")
        private Axe axe;
    
    • 1
    • 2
    • 3
    • 对于构造器注入:
        @Autowired
        public Person(@Qualifier("stoneAxe") Axe axe) {
            this.axe = axe;
        }
    
    • 1
    • 2
    • 3
    • 4

    注: @Qualifier 注解不能放在构造器方法上,只能修饰指定的参数。

    但是,与其使用 @Autowired + @Qualifier 的组合,不如直接使用 @Resource 来指定类。注意:前者是Spring的注解,后者是Java自身的注解。

    @DependsOn和@Lazy

    • @DependsOn :强制初始化其它bean。比如 AB 都是Spring的bean,它们之间并没有依赖注入的关系,而 A 调用了 B 的方法,显然 B 应该在 A 之前初始化;
    • @Lazy :指定Spring初始化时,是否初始化该bean,比如 @Lazy(true)
  • 相关阅读:
    ZYNQ搭建HP总线从DDR进行PL与PS交互
    【Linux编程Shell自动化脚本】03 shell四剑客(find、sed、grep、awk)
    列表和字典练习
    QWebEngineView添加chrome参数的方法
    RabbitMQ中延迟队列的全方位解析
    工具介绍——第三方软件远程连接(工具:Rustdesk)
    vscode 搜索界面的files to include files to exclude 是什么功能?
    Maven安装详解
    【Java实战项目】【超详细过程】—大饼的图片服务器2
    高等数学(第七版)同济大学 习题9-6 个人解答
  • 原文地址:https://blog.csdn.net/duke_ding2/article/details/126570338