Spring之IOC的注入方式

在java中,要使用一个对象,必须先创建一个实例,但是有了IOC之后,对象的创建与销毁都交给了IOC容器,不用我们手动创建,而是直接从IOC容器中获取,达到了解耦的效果。IOC是一种思想,在Spring中,实现IOC的方式是DI(依赖注入),本文会介绍Spring依赖注入的几种方式。

Spring的依赖注入

对象,在Spring中叫做bean,即使是最简单的应用,也需要多个bean共同协作。依赖注入是指对象之间的依赖关系,一起协作的其他对象,通过构造器的参数、工厂方法的参数创建的对象,或者构造函数、工厂方法创建的对象来设置属性。所以容器的工作实际上就是创建bean并注入依赖关系。Spring中的DI方式主要有两种,构造器注入和Setter注入。

项目准备

新建一个maven项目,JDK版本1.8,引入Spring的核心依赖

<dependencies>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-core</artifactId>
           <version>${spring.version}</version>
       </dependency>

       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>${spring.version}</version>
       </dependency>

       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-beans</artifactId>
           <version>${spring.version}</version>
       </dependency>
</dependencies>
构造器注入
  1. 新建一个User类作为注入的例子,添加get,set方法,重写toString方法,添加两个参数不同的构造器。
public class User {
    private String id;

    private String name;

    private Integer age;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. xml配置如下,有三种不同的写法
    在resource下面新建application-constructor.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--构造方法注入-->
    <!--使用index属性来显式指定构造参数的索引-->
    <bean id="user1" class="org.spring.ioc.entity.User">
        <constructor-arg index="0" value="1234"/>
        <constructor-arg index="1" value="spring"/>
    </bean>

    <!--使用type属性显式指定简单类型的构造器参数类型,这里对应的是User类中传入name,age的构造器-->
    <bean id="user2" class="org.spring.ioc.entity.User">
        <constructor-arg type="java.lang.String" value="spring"/>
        <constructor-arg type="java.lang.Integer" value="20"/>
    </bean>

    <!--也可以使用构造器参数命名来指定值的类型-->
    <bean id="user3" class="org.spring.ioc.entity.User">
        <constructor-arg name="id" value="1234"/>
        <constructor-arg name="name" value="spring"/>
    </bean>
</beans>

在bean的constructor-arg元素下进行指定,constructor-arg顾名思义就是构造器参数的意思,其中包括了三个属性配置
- index 是一个索引顺序,对应构造器参数的索引,根据索引进行注入
- type 构造器的参数类型,可以通过类型进行匹配注入
- name 构造器参数名,根据名称进行匹配注入
3. 验证
现在可以启动Spring容器来验证bean是否注入成功

public class Main {
    private final static String APPLICATION = "classpath:application-*.xml";

    public static void main(String[] args) {
      //加载xml配置文件
      ApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION);
      constructorInject(context);
    }

    private static void constructorInject(ApplicationContext context) {
        //获取bean实例,传入的参数值为xml中配置的id
        User user1 = (User) context.getBean("user1");
        System.out.println(user1.toString());
        User user2 = (User) context.getBean("user2");
        System.out.println(user2.toString());
        User user3 = (User) context.getBean("user3");
        System.out.println(user3.toString());
    }
}

输出结果如下:

User{id='1234', name='spring', age=null}
User{id='null', name='spring', age=20}
User{id='1234', name='spring', age=null}

可以看出我们定义的User对象已经成功交给Spring容器管理

Setter注入

Setter注入也需要在xml中进行配置,在调用了无参的构造方法或者无参的静态工厂方法实例化bean之后,容器通过回调bean的setter方法来完成setter注入。
1. 接下来新建Blog,Author两个类,添加get,set方法,重写toString方法:
Blog类:

public class Blog {
    private String name;

    private String content;

    private Long date;

    private Author author;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Long getDate() {
        return date;
    }

    public void setDate(Long date) {
        this.date = date;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                ", date=" + date +
                ", author=" + author +
                '}';
    }
}

Author类:

public class Author {
    private String name;

    private Integer age;

    private String url;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public String toString() {
        return "Author{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", url='" + url + '\'' +
                '}';
    }
}
  1. xml配置
    setter注入是通过在bean下面配置property元素来完成的,在resource下面新建application-setter.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--setter注入-->
    <bean id="blog" class="org.spring.ioc.entity.Blog">
        <property name="name" value="spring-ioc"/>
        <property name="content" value="spring"/>
        <property name="date" value="1520232449944"/>
        <property name="author" ref="author"/>
    </bean>

    <bean id="author" class="org.spring.ioc.entity.Author">
        <property name="name" value="luoliang"/>
        <property name="age" value="18"/>
        <property name="url" value="https://luoliangdsga.github.io"/>
    </bean>
</beans>

配置很简单,通过制定property元素的name和value属性,设置变量名对应的值,其中author属性引用的另一个bean,所以使用了ref属性。

  1. 验证
public class Main {
    private final static String APPLICATION = "classpath:application-*.xml";

    public static void main(String[] args) {
        //加载xml配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION);
        setterInject(context);
    }

    private static void setterInject(ApplicationContext context) {
        Blog blog = (Blog) context.getBean("blog");
        System.out.println(blog.toString());
    }
}

结果如下:

Blog{name='spring-ioc', content='spring', date=1520232449944, author=Author{name='luoliang', age=18, url='https://luoliangdsga.github.io'}}

所有我们配置的属性都注入成功。

总结

我们可以混合使用构造器注入和Setter注入,最佳实践是强制性依赖关系时使用构造器注入,可选的依赖关系时使用Setter注入,在setter注入中可以使用@Required注解让属性成为必须的依赖项。
有很多小伙伴会觉得很奇怪,明明使用注解进行配置依赖更加的简单。不否认,SpringBoot推出之后,Spring已经不再推荐xml配置,而是提倡java配置和注解,但是xml配置是Spring的基础。正是因为传统的Spring应用xml配置太过于复杂,才会出现SpringBoot这门技术来解决这些问题,一个技术的兴起是有各种原因的。SpringBoot虽然解决了配置复杂的问题,但是对于刚入门的人来说,不知道其中的细节,这可能并不是一个好的开始。
上面用到的ApplicationContext,它所管理的beans支持构造函数注入和setter注入,在一些依赖已经使用构造器注入之后它还支持setter注入。我们也可以用BeanDefinition的形式配置依赖,它能根据指定的PropertyEditor实现将属性从一种格式转化为另外一种格式。但是,在日常的开发中我们不会直接以编程的方式去创建bean,而是采用上面所讲的xml配置创建bean,或者是通过注解(即@Component,@Service等注解类),或者基于@Configuration类的@Bean方法。本质上这些资源会转换成BeanDefinition的实例并且用于加载整个Spring IoC容器实例。所以,不管我们在传统Spring应用还是SpringBoot中,使用Spring的IOC,它的原理都是不会变的。

源码

上面所用到的代码我已放在我的github上,欢迎star,一起学习,共同进步,如有不对之处,欢迎指出。