Spring依赖注入


Spring依赖注入

Spring的IOC(Inversion Of Control 控制反转)是通过DI(Dependency Injection 依赖注入)实现的,依赖注入在Spring主要有以下几种方式:

依赖注入方式

一、基于XML的依赖注入

基于XML的依赖注入,指的是修改XML配置文件来实现依赖注入,主要包括setter设值注入和构造注入,另外还有一种自动注入,自动注入主要是针对引用类型属性的注入。

我们对XML依赖注入的讨论基于以下类和测试:

public class Staff {
    private String name;
    private int age;
    private Company company;

    public Staff() { }

    public Staff(String name, int age, Company company) {
        this.name = name;
        this.age = age;
        this.company = company;
    }

    public void setName(String name) { this.name = name; }

    public void setAge(int age) { this.age = age; }

    public void setCompany(Company company) { this.company = company; }
    
    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", company=" + company +
                '}';
    }
}
public class Company {
    private String name;
    private String address;

    public Company() { }

    public Company(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public void setName(String name) { this.name = name; }

    public void setAddress(String address) { this.address = address; }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
public class AppTest {
    @Test
    public void Test() {
        String resource = "ApplicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(resource);
        Staff staff = (Staff) context.getBean("Staff");
        System.out.println(staff);
    }
}

1、设值注入

使用

设值注入通过<property>标签完成,它有namevalueref属性。

<property>属性:

name: 属性名

value: 简单类型值

ref: 引用类型值


  • 对简单类型(基本类型 + String)的属性,使用namevalue属性:

    <property name="属性名" value="属性值"/>
    
  • 对引用类型的属性,使用nameref属性:

    <property name="属性名" ref="引用类型<bean>id属性值"/>
    

示例

setter

测试

setter测试

原理

我们在StaffCompany的无参构造函数和各属性setter方法中打印一些提示信息,以便于我们观察整个注入流程:

public class Staff {
    private String name;
    private int age;
    private Company company;

    public Staff() {
        System.out.println("Staff's non-arg constructor."); //提示信息
    }

    public Staff(String name, int age, Company company) {
        this.name = name;
        this.age = age;
        this.company = company;
    }

    public void setName(String name) {
        System.out.println("Staff's setName " + name); //提示信息
        this.name = name;
    }

    public void setAge(int age) {
        System.out.println("Staff's setAge " + age); //提示信息
        this.age = age;
    }

    public void setCompany(Company company) {
        System.out.println("Staff's setCompany " + company); //提示信息
        this.company = company;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", company=" + company +
                '}';
    }
}
public class Company {
    private String name;
    private String address;

    public Company() {
        System.out.println("Company's non-arg constructor."); //提示信息
    }

    public Company(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public void setName(String name) {
        System.out.println("Company's setName " + name); //提示信息
        this.name = name;
    }

    public void setAddress(String address) {
        System.out.println("Company's setAddress " + address); //提示信息
        this.address = address;
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

  • Spring会先调用bean的无参构造函数,然后调用属性的setter方法,完成注入

运行观察:

设值注入过程

对象的创建:

If no circular dependencies exist, when one or more collaborating beans are being injected into a dependent bean, each collaborating bean is totally configured prior to being injected into the dependent bean. This means that, if bean A has a dependency on bean B, the Spring IoC container completely configures bean B prior to invoking the setter method on bean A. In other words, the bean is instantiated (if it is not a pre-instantiated singleton), its dependencies are set, and the relevant lifecycle methods (such as a configured init method or the InitializingBean callback method) are invoked.


  • Spring是通过将"set"和<property>标签中的name属性(首字符变大写)拼接形成的方法名来调用setter方法,例如:
<property name="age" value="19"/> <!--调用setAge方法-->
<property name="company" ref="Company"/> <!--调用setCompany方法-->

此时我们向Staff的<bean>标签中再添加一个<property>标签:

any

Staff类中增加setAny方法(但没有any属性):

setAny

运行观察:

调用setAny

2、构造注入

使用

构造注入通过<constructor-arg>标签完成。它有nameindexvalueref属性。

<constructor-arg>属性:

name: 形参名

index: 形参位置, 从0开始

value: 简单类型值

ref: 引用类型值

  • 对简单类型:

    <constructor-arg name="形参名" value="形参值"/>
    
    <constructor-arg index="形参位置,从0开始" value="形参值"/>
    
  • 对引用类型:

    <constructor-arg name="形参名" ref="引用类型<bean>id属性值"/>
    
    <constructor-arg index="形参位置,从0开始" ref="引用类型<bean>id属性值"/>
    

示例

constructor示例

测试

constructor测试

原理

我们在StaffCompany的有参构造函数中打印一些提示信息,以便于我们观察整个注入流程:

public class Staff {
    private String name;
    private int age;
    private Company company;

    public Staff() { }

    public Staff(String name, int age, Company company) {
    	System.out.println("Staff's arg constructor."); //提示信息
        this.name = name;
        this.age = age;
        this.company = company;
    }

    public void setName(String name) { this.name = name; }

    public void setAge(int age) { this.age = age; }

    public void setCompany(Company company) { this.company = company; }
    
    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", company=" + company +
                '}';
    }
}
public class Company {
    private String name;
    private String address;

    public Company() { }

    public Company(String name, String address) {
        System.out.println("Company's arg constructor."); //提示信息
        this.name = name;
        this.address = address;
    }

    public void setName(String name) { this.name = name; }

    public void setAddress(String address) { this.address = address; }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

  • Spring会调用对应的有参构造函数,完成注入

运行观察:

constructor过程

TODO:至于对象的创建顺序,这里挖一个坑,以后再填。


3、自动注入

自动注入是针对引用类型属性的注入。当我们的bean中引用类型的数据过多时,就会出现下面这种画风:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="xxx" class="xxx.xxx.xxx">
    	<property name="..." ref="..."/>
        <property name="..." ref="..."/>
        <property name="..." ref="..."/>
        <property name="..." ref="..."/>
        ...
    </bean>

    <bean id="xxx" class="xxx.xxx.xxx">
        ...
    </bean>
    
    <bean id="xxx" class="xxx.xxx.xxx">
        ...
    </bean>

    <bean id="xxx" class="xxx.xxx.xxx">
        ...
    </bean>
    ...
</beans>

ref引用的都是其他<bean>,那我们有什么办法让Spring自己在配置文件中搜索相应的<bean>,并注入到相应属性当中去呢?Spring提供了byNamebyType两种方法。


准备工作

  • 为了完成此部分的分析,我们添加一个Company的子类InternetCompany

    public class InternetCompany extends Company {
        public InternetCompany() {
            System.out.println("InternetCompany's non-arg constructor");
        }
        
        public InternetCompany(String name, String address) {
            super(name, address);
            System.out.println("InternetCompany's arg constructor");
        }
    }
    
  • 类型同源:

    • 相同类型
    • A是B的父类
    • A是接口,B是其实现类

使用

  • byName

    byName通过在<bean>标签中将autowire属性设为byName实现,Spring会查找和属性同名的<bean>标签,如果类型同源则注入。因为有<bean>标签id属性和类属性同名的限制,所以如果有多个同源<bean>也无所谓。

  • byType

    byType通过在<bean>标签中将autowire属性设为byType实现,Spring会查找同源bean,并注入。不可以有多个同源<bean>标签。


示例

  • byName
byName
  • byType
byType

测试

  • byName
byName结果!
  • byType
byType结果

二、基于注解的依赖注入

常用注解:

  • @Component (@Repository、@Service、@Controller)
  • @Value
  • @Autowired
  • @Qualifier
  • @Resource

我们先讲解这几个注解的用法,然后通过示例展示。

一、@Component

@Repository: 放在dao的实现类上面, 创建dao对象

@Service: 放在service的实现类上面, 创建service对象

@Controller: 放在控制器类上面, 创建控制器对象

@Component:

  • 作用:创建对象, 等同于<bean>标签
  • 属性:value值等同于<bean>标签的id值,若未设置value值,则使用首字母小写的类名
  • 位置:放在除以上类的上面,一般是util类

二、@Value

@Value:

  • 作用:简单类型赋值
  • 属性:value值即为属性值
  • 位置:
    • 在简单属性定义上面,无需setter方法
    • 在setter方法上面,调用setter方法

三、@Autowired

@Autowired:

  • 作用:引用类型赋值,自动注入。支持byName和byType,默认是byType

  • 属性:required

    • 默认为true,表示必须存在满足条件的bean,不然就产生异常;
    • 如果为false,则不要求必须存在满足条件的bean
  • 位置:

    • 在引用类型属性定义上面,无需setter方法
    • 在setter方法上面,调用setter方法

四、@Qualifier

@Qualifier

  • 作用:和@Autowired配合完成byName功能
  • 属性:value,引用的其他bean的id值
  • 位置:
    • 在引用类型属性定义上面,和@Autowired无先后顺序,无需setter方法
    • 在setter方法上面,和@Autowired无先后顺序,调用setter方法

五、@Resource

@Resource

  • 作用:引用类型赋值,自动注入。默认先byName,后byType

  • 属性:

    • name,只要指定了name的值,就只采用byName
    • type,采用byType
  • 位置:

    • 在引用类型属性定义上面,无需setter方法
    • 在setter方法上面,调用setter方法

六、示例

Company:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "Company") // Company
public class Company {
    @Value("Google") // @Value 简单属性上
    private String name;
    @Value("California") // @Value 简单属性上
    private String address;

    public Company() {
        System.out.println("Company's non-arg constructor.");
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

Staff示例1:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "Staff") // @Component
public class Staff {
    @Value(value = "小明") // @Value 简单属性上
    private String name;
    @Value(value = "19") // @Value 简单属性上
    private int age;
    
    @Autowired // @Autowired 默认byType
    private Company company;

    public Staff() {
        System.out.println("Staff's non-arg constructor.");
    }

    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", company=" + company +
                '}';
    }
}

Staff示例2:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "Staff") // @Component
public class Staff {
    @Value(value = "小明") // @Value 简单属性上
    private String name;
    private int age;
    
    @Autowired // @Autowired
    @Qualifier(value = "Company") // @Qualifier 和上面的@Autowired 完成byName
    private Company company;

    public Staff() {
        System.out.println("Staff's non-arg constructor.");
    }
	
    @Value(value = "19") // @Value 简单属性setter方法上
    public void setAge(int age) {
        System.out.println("Staff's setAge " + age);
        this.age = age;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", company=" + company +
                '}';
    }
}

Staff示例3:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component(value = "Staff") // @Component
public class Staff {
    @Value(value = "小明") // @Value 简单属性上
    private String name;
    private int age;

    @Resource // @Resource默认先byName再byType
    private Company company;

    public Staff() {
        System.out.println("Staff's non-arg constructor.");
    }

    @Value(value = "19") // @Value 简单属性setter方法上
    public void setAge(int age) {
        System.out.println("Staff's setAge " + age);
        this.age = age;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", company=" + company +
                '}';
    }
}

示例1、2、3结果:

constructor示例

> Annotation injection is performed before XML injection. Thus, the XML configuration overrides the annotations for properties wired through both approaches.
posted @ 2020-10-15 17:24  YuHover  阅读(143)  评论(0)    收藏  举报