EUAdvancer

Spring-依赖注入(DI)与装配(Wiring)

bg
依赖注入(DI)与装配(Wiring)的技术总结

依赖注入的好处

在传统应用中,多个类互相协作,每个对象管理与自己相互协作的对象的引用,这就导致了代码的高度耦合以及测试困难的问题。而通过依赖注入的模式,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象时进行设定。这意味着,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。

依赖注入(DI)和控制反转(IOC)

  • 创建被调用者的工作不再由调用者来完成,因此称为控制反转。
  • 创建被调用者实例的工作通常由 IOC 容器来完成,然后注入给调用者,因此也称为依赖注入。

总的来说,它们是同个思想的不同角度的描述

依赖注入的方式

依赖注入有两种注入方式,分别为 构造器注入setter注入 。我们可以模拟一个人打电话的过程来体现注入过程。

首先声明 Mobile 类,它具有打电话功能

1
2
3
4
5
public class Mobile{
void dialUp(long phoneNumber){
System.out.printf("Call number: " + phoneNumber);
}
}

接着使用 构造器注入 创建 Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Person{
private Mobile mobile;

/*
public Person(){ // 传统方式new
this.mobile = new Mobile();
}
*/


public Person(Mobile mobile){ // 构造器注入
this.mobile = mobile;
}

public void call(long number){
this.mobile.dialUp(number);
}
}

这里我们也可以选择 setter注入

1
2
3
4
5
6
7
8
9
10
11
class Person{
private Mobile mobile;

public void setMobile(Mobile mobile){ // setter注入
this.mobile = mobile;
}

public void call(long number){
this.mobile.dialUp(number);
}
}

现在问题就变为,如何将 Mobile 对象传给 Person 呢?这就涉及到了 装配

装配

创建应用组件之间协作的行为通常称为装配(wiring)

装配主要分为两步:首先将 bean 注册到 Spring 容器,然后通过 Spring 容器对具有依赖关系的 bean 进行注入。也就是说,开发人员需要告诉 Spring 要创建哪些 bean 且如何装配。因此依赖注入的本质就是装配,装配是依赖注入的具体行为。装配主要有三种机制:

  • XML 中进行显式配置
  • Java 中进行显式配置
  • 组件扫描和自动装配

组件扫描和自动装配

组件扫描( component scanning ):Spring 会自动发现应用上下文所创建的 bean

简单来说,组件扫描就是自动寻找组件类,然后将其注册到 IOC 容器中。(IOC容器将会负责创建 bean,装配 bean 等)

自动装配(autowiring):Spring 自动满足 bean 之间的依赖

自动装配通过注解让 Spring 自动将具有依赖关系的 bean 注入

声明组件类

构造型注解标注的类将会被 组件扫描 自动寻找到,在这里,@Component 标注 Mobile 类代表它将会作为一个组件类。

构造型注解包括:@Controller@Component@Service@Repository

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.zz.autowiring;

import org.springframework.stereotype.Component;

/**
* Created by zhzco on 2017/8/16.
*/

@Component("mobile")
public class Mobile {
void dialUp(long phoneNumber){
System.out.printf("Call number: " + phoneNumber);
}
}

启动组件扫描

  • 通过 @ComponentScan 开启组件扫描,默认扫描与配置类相同的包(在这里默认扫描 cn.zz.autowiring 包下的组件类)。如果想扫描不同的包下的组件类,可以设置 basePackageClasses 属性来指定类,这些类所在的包将会作为组件扫描的基础包。(在这里则会将 Mobile 类和 Person 类所在的包作为基础包)
  • @Configuration 则代表这个类为配置类,等价于在 XML 中配置 beans ,这里用 java 代码配置
1
2
3
4
5
6
7
8
9
10
11
12
package cn.zz.autowiring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* Created by zhzco on 2017/8/16.
*/

@Configuration
@ComponentScan(basePackageClasses = {Mobile.class, Person.class})
public class ApplicationConfig {
}

到了现在,Mobile 已经被 Spring 容器管理了,接着我们需要将 Mobile bean 注入 Person

自动装配

首先,我们同样添加 @Component 注解将 Person 类设置为组件类来让它被 Spring 容器管理,接着因为它用到了 Mobile 对象,所以我们通过自动装配注解来注入 Mobile bean

常用的自动装配注解有以下几种:@Autowired@Resource@Inject@Qualifier@Named

  • @AutowiredbyType 的,在使用这个注解时,必须有匹配的 bean ,否则会抛出异常,当然也可以设置属性 required=false 来允许 bean 不被装配上
  • @ResourcebyName 的,当找不到与名称匹配的 bean 时才按照类型进行装配
  • @Autowired@Resource 注解可以用在属性上面,setter 方法上以及构造器上。如果设置在属性上,则不需要 setter 方法了(就像上面的写法,比较简洁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.zz.autowiring;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
* Created by zhzco on 2017/8/16.
*/

@Component("person")
public class Person {

@Resource(name = "mobile")
private Mobile mobile;

public void call(long number){
this.mobile.dialUp(number);
}
}

到了这里,我们在装配 Person bean 时会自动注入一个 Mobile bean

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import cn.zz.autowiring.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = cn.zz.autowiring.ApplicationConfig.class) // 加载配置类
public class AutowiringApplicationTests {
@Resource(name = "person")
private Person person; // 自动装配

@Test
public void contextLoads() {
person.call(123456789);
}
}

在Java中进行显式配置

使用 Java 显示配置和之前的区别在于:前面通过启动组件扫描(配置类添加 @ComponentScan 注解)自动寻找组件类(带有 @Component 的类)来隐式注册 bean,而 Java 显示配置不需要这两个注解,直接通过 @Bean 注解来显式注册 bean

配置类显式注册bean

PersonMobile 不再需要 @Component 注解

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
package cn.zz.javaconfig;

/**
* Created by zhzco on 2017/8/16.
*/

public class Mobile {
void dialUp(long phoneNumber){
System.out.printf("Call number: " + phoneNumber);
}
}


package cn.zz.javaconfig;

/**
* Created by zhzco on 2017/8/16.
*/

public class Person {

private Mobile mobile;

public Person(Mobile mobile){ // 构造器注入
this.mobile = mobile;
}

public void call(long number){
this.mobile.dialUp(number);
}
}

接着我们通过 @Configuration 声明配置类,并在类方法上添加 @Bean 注解来显式告诉 Spring 这个方法返回一个 bean,该 bean 要注册到 IOC 容器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.zz.javaconfig.config;

import cn.zz.javaconfig.Mobile;
import cn.zz.javaconfig.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Created by zhzco on 2017/8/16.
*/

@Configuration
public class ApplicationConfig {
@Bean
public Mobile mobile(){
return new Mobile();
}

@Bean
public Person person(Mobile mobile){ // 构造器注入
return new Person(mobile);
}
}

这样,Person beanMobile bean 都已经注册到 IOC 容器中了,并且我们在装配 Person bean 时会自动注入一个 Mobile bean

验证

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
import cn.zz.javaconfig.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;


/**
* Created by zhzco on 2017/8/16.
*/

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = cn.zz.javaconfig.ApplicationConfig.class) // 加载配置类
public class JavaconfigApplicationTests {
@Resource(name = "person")
private Person person; // 自动装配

@Test
public void contextLoads() {
person.call(123456789);
}
}

在XML中进行显式配置

代码

SpringDemo/springbootwiring