I. 组件装配

Spring 中通过注解配置 Bean 教程中讲述了如何通过注解的方式来配置 Bean, 这一节教程则是要介绍如和通过注解的方式来装配 Bean 与 Bean 之间的关系

组件装配<context:component-scan> 元素会自动注册 AutowiredAnnotationBeanPostProcessor 实例,该实例可以自动装配具有 @Autowired@Resource, 和 @Inject 注解的属性

下面介绍一下使用各个注解来装配 Bean 的细节

II. 使用 @Autowired 自动装配 Bean

@Autowired 注解自动装配具有兼容类型的单个 Bean 属性:

  • 构造器, 普通字段(即使是非 public), 一切具有参数的方法都可以应用@Authwired 注解
  • 默认情况下, 所有使用 @Authwired 注解的属性都需要被设置. 当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Authwired 注解的 required 属性为 false
  • 默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作. 此时可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称
  • @Authwired 注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配.
  • @Authwired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean.
  • @Authwired注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值

1. 使用 @Autowired 注解配置 Bean 代码示例

我们把上一节中的 repository, service 和 controller 的例子使用 Autowire 注解来改造一下:

在 UserService 类中, 使用 Autowire 注解来装配一个 UserRepository

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public void add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

然后在 UserController 中, 使用 Autowire 注解来装配一个 UserService

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public void execute(){
        System.out.println("UserController execute...");
        userService.add();
    }
}

然后通过 ApplicationContext 去获取 UserController 的 bean 实例

public class AnnotationDemo {
    public static void main(String[] args) {
        ApplicationContext actx = new ClassPathXmlApplicationContext("config/annotation-beans.xml");

        UserController userController = (UserController) actx.getBean("userController");
        System.out.println(userController);
        userController.execute();
    }
}

得到结果如下:

org.lovian.spring.bean.annotation.controller.UserController@101df177
UserController execute...
UserService add...
User Repository saved.

我们可以看到通过调用 UserController 的 execute 方法,每一层的方法依次都被调用了,说明通过 Autowire 注解配置的 bean 都被 IOC 容器所管理并且自动装配了。

2. 通过构造器方式和@Autowired来装配 bean

但是,直接把 @Autowired 注解配置到字段属性上已经不是 Spring team 所推荐的:

Spring Team Recommands :Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".

所以我们将上面的装配方式做一下修改,通过构造器的方式来配置,代码如下:

UserService类

@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

UserController类

@Controller
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void execute(){
        System.out.println("UserController execute...");
        userService.add();
    }
}

然后我们再执行一遍 main 函数就可以发现, 得到的结果和之前直接在字段上使用 @Autowired 的结果是一样的。

3.如果自动装配的 Bean 不存在

有一种情况,当使用了 @Autowired 注解来配置 bean, 但是这个 bean 实际上并不存在于 IOC 容器中, 那么这是就需要在 @Autowired 上 使用 required = fasle属性

修改 UserRepositoryImpl 类:

@Repository(value = "userRepository")
public class UserRepositoryImpl implements UserRepository {

    @Autowired(required = false)
    private TestObject testObject;

    public void save() {
        System.out.println("User Repository saved.");
        System.out.println(testObject);
    }
}

这样 IOC 容器会把容器中的 bean 装配给 userRepository,然后我们修改配置文件,将 testObject 类从 IOC 容器中过滤掉:

    <context:component-scan base-package="org.lovian.spring.bean.annotation">
        <context:exclude-filter type="regex" expression="org.lovian.spring.bean.annotation.TestObject"/>
    </context:component-scan>

然后执行 main 方法,得到结果如下:

org.lovian.spring.bean.annotation.controller.UserController@12028586
UserController execute...
UserService add...
User Repository saved.
null

可以看到,testObject bean 打印结果为 null, 也就是说, 即使 IOC 容器中没有这个 Bean, 也并不影响程序的运行,bean 就被初始化成 null。

4.如果有两个相同接口的实现类

我们从上面的例子可以看出来, Sping IOC 容器会自动扫描指定包中的class,但是如果一个接口,有两个实现类,比如说,UserRepository 接口有两个实现类 UserRepositoryImpl 和 UserJdbcRepository, 那么在 UserService 中,@Autowired 是怎么装配的。

  • 通过 @Autowired 修饰的字段名字或者构造函数的参数名指定 bean
  • 通过 @Qualifier 指定 bean

我们先增加一个 UserJdbcRepository的实现类

package org.lovian.spring.bean.annotation.repository;

import org.springframework.stereotype.Repository;

/**
 * Author: PENG Zhengshuai
 * Date: 4/22/18
 * lovian.org
 */

@Repository
public class UserJdbcRepository implements UserRepository{

    public void save() {
        System.out.println("UserJdbcRepository saved....");
    }
}

那么在 UserService 中,我们要装配 UserRepository:

@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userJdbcRepository) {
        this.userRepository = userJdbcRepository;
    }

    public void add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

注意这里的构造器的参数名是 userJdbcRepository, 而不是 userRepository,那么打印结果是:

org.lovian.spring.bean.annotation.controller.UserController@11c20519
UserController execute...
UserService add...
UserJdbcRepository saved....

可以发现这里 IOC 容器给 UserService 装配的 UserRepository 的实例是 UserJdbcRepository 的 bean。如果这里参数名用 userRepository 的话,那么 IOC 容器将给其装配名字叫 userRepository 的 Bean。

同理,如果是用 @Autowired 来修饰字段的话,那么字段的名字和 bean 的名称做匹配

@Service
public class UserService {

    @Autowired
    private UserRepository userJdbcRepository;

    public void add(){
        System.out.println("UserService add...");
        userJdbcRepository.save();
    }
}

这样,IOC 容器给 UserService 装配的也是 UserJdbcRepository 的实例。

还可以通过 @Qualifier来指定要装配的 bean 的名称:

@Service
public class UserService {

    @Autowired
    @Qualifier("userJdbcRepository")
    private UserRepository userRepository;

    public void add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

以及用@Qualifier修饰构造函数中的参数:

@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public UserService(@Qualifier("userJdbcRepository") UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

这样装配的结果都是把 UserJdbcRepository 装配给 UserService

III. 使用 @Resource 或者 @Inject 自动装配 Bean

  • Spring 还支持 @Resource@Inject 注解,这两个注解和 @Autowired 注解的功用类似
  • @Resource 注解要求提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 Bean 的名称
  • @Inject@Autowired 注解一样也是按类型匹配注入的 Bean, 但没有 reqired 属性
  • 建议使用@Autowired注解

Share this on