依赖注入(Dependency Injection)

依赖注入是这样一个过程:对象仅通过在构造器参数、工厂方法参数或对象实例(构造器或者工厂方法返回的)中设置属性来定义它们的依赖项,然后容器在创建 Bean 时注入这些依赖项。这个过程从根本上说是 Bean 本身的逆过程,所以叫做 控制反转

  • 首先我们定义一个配置类
@Configuration
public class MyConfigOfDI {
}
  • 并以这个配置类的配置创建一个 IoC 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfDI.class);
  • 接着定义几个 Bean
@Controller
public class BookController {
}

@Service
public class BookService {
}

@Repository
public class BookDao {
}

@Component
public class Application {
}
  • 使用 @ComponenScan 将组件扫描进 IoC 容器

也可以使用其他方式,诸如:@Import 等,可以查看 Spring - 组件(Beans)注册(到 IoC 容器)的几种方式 这篇文章

@Configuration
@ComponentScan(basePackages = "com.keke.web")
public class MyConfigOfDI {}

基于注解的依赖注入

@Autowired

注入原理在 AutowiredAnnotationBeanPostProcessor 中,关于 BeanPostProcessor 的原理可以查看 Spring - IoC 容器之拓展点 BeanPostProcessor

最简单的自动注入:

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;
}

BookService 中定义输出 BookDao 的方法:

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }
}

输出 BookService 中的 BookDao,同时输出容器中的 BookDao

@Test
void testDI() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfDI.class);
    BookService bookService = applicationContext.getBean(BookService.class);
    // 输出 BookService 中的 BookDao
    bookService.print();

    BookDao bookDao = applicationContext.getBean(BookDao.class);
    // 输出容器中的 BookDao
    System.out.println(bookDao);
}

// 输出结果
com.keke.web.dao.BookDao@b7c4869
com.keke.web.dao.BookDao@b7c4869

从输出结果可以看出注入到 BookService 中的 BookDao,就是容器中的 BookDao


@Qualifier

修改 BookDao 的定义,增加一个 label 属性,默认为 1,以区分不同的 Bean

@Repository
public class BookDao {

    private Integer label = 1;

    public BookDao() {
    }

    public Integer getLabel() {
        return label;
    }

    public void setLabel(Integer label) {
        this.label = label;
    }

    @Override
    public String toString() {
        return "BookDao{" +
                "label=" + label +
                '}';
    }
}

在容器中定义另一个类型为 BookDaoBean 名称为 bookDao2,同时设置 label 属性为 2,以区分容器中默认的名称为 bookDaoBean

@Configuration
@ComponentScan(basePackages = "com.keke.web")
public class MyConfigOfDI {

    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel(2);
        return bookDao;
    }
}

此时,输出 BookService 中的 BookDao,应该是 label1Bean

因为 @Autowired 默认是先去容器中查找类型为 BookServiceBean,如果存在多个,再根据属性名称取匹配 Bean 的名称。这里注入的 Bean 名称是 bookDao,所以输出 label1Bean

BookDao{label=1}

如果我们想在 BookService 中注入名称为 bookDao2Bean,可以使用 @Qulifier 注解指定名称

@Service
public class BookService {

    @Qualifier("bookDao2")
    @Autowired
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }
}

// print() 方法输出
BookDao{label=2}

@Primary

当容器中有多个同一类型的 Bean,而我们又没有指定注入的 Bean 的名称时,这时就会报错:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.keke.web.dao.BookDao' available: expected single matching bean but found 2: bookDao,bookDao2

	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1200)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:350)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)

可以使用 @Primary 指定如果存在同一类型的多个 Bean 时,首选哪个:

@Configuration
@ComponentScan(basePackages = "com.keke.web")
public class MyConfigOfDI {

    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel(2);
        return bookDao;
    }
}

bookDao2Bean 上面加了 @Primary 注解,这样在 BookService 使用最简单的 @Autowired 获取

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }
}

// 输出
BookDao{label=2}

因为在 bookDao2 上面注释了 @Primary 注解,所以这里依赖注入时发现多个同一类型的 Bean 其中有 @Primary 的就会首选进行注入

不允许在同一类型的 Bean 上同时存在多个 @Primary 注解

如果仍然想要在 BookService 中注入名称为 bookDaoBean,可以使用 @qualifier 指定 Bean 名称

@Service
public class BookService {

    @Qualifier("bookDao")
    @Autowired
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }
}

// 使用 print 方法输出
BookDao{label=1}

@Resource(jsr-250)

 @Resource 注解虽然是 JavaEE 提供的,Spring 同样支持用它来管理 Bean。默认是按照名称来查询 IoC 容器 中的组件来完成依赖注入的,同时提供了一个 name 属性来自定义要注入的组件名称。

@Service
public class BookService {

	// 首先寻找名称为 bookDao 的组件来完成注入
    @Resource
	// 或者指定要注入的组件的名称
	// @Resource(name = "bookDao2")
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }
}

注意: @Resource 注解不支持 @Primary 注解,同时也没有 @Autowiredrequire 属性来支持非必须注入

@Inject(jsr-330)

Spring 官方文档 中指出,从 Spring 3.0 开始,Spring 支持使用 JSR-330 标准注解来实现依赖注入。

要使用 @Inject 注解,首先需要导入依赖:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

用法同 @AutowiredResource

@Service
public class BookService {

    @Inject
    private BookDao bookDao;

    public void print() {
        System.out.println(bookDao);
    }
}

这里我们在 bookDao2 这个 Bean 上加上 @Primary 注解:

@Configuration
@ComponentScan(basePackages = "com.keke.web")
public class MyConfigOfDI {

    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao bookDao = new BookDao();
        bookDao.setLabel(2);
        return bookDao;
    }
}

输出 BookService 中的 bookDao

BookDao{label=2}

可以看到 @Inject 支持 Spring@Primary 特性,但是没有 @Autowiredrequire 属性


@Autowired 的源码中可以看到,@Autowired 可以标注在 构造器方法参数属性注解类型

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	boolean required() default true;

}

IoC 容器 中定义两个 BeanCarBoss,并将 Car 注入到 Boss 中:

@Component
public class Car {}
@Component
public class Boss {

    private Car car;

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

基于构造器的依赖注入(Spring 源码中用的比较多)

@Component
public class Boss {

    private Car car;

	// 标注在有参构造器上,会自动从容器中获取
    @Autowired
    public Boss(Car car) {
        this.car = car;
    }
}

也可以标注在构造器参数上

@Component
public class Boss {

    private Car car;

    public Boss(@Autowired Car car) {
        this.car = car;
    }
}

也可以省略

@Component
public class Boss {

    private Car car;

    public Boss(Car car) {
        this.car = car;
    }
}

当使用构造器注入且 只有一个 有参构造器的时候,参数会从容器中获取

基于 Setter 方法的依赖注入

@Autowired 标注在方法上面,Spring 容器创建当前对象时,会调用方法,并自动从容器中获取方法参数类型的值

public class Boss {

    private Car car;

    public Car getCar() {
        return car;
    }

	// 标注在 set 方法上面
    @Autowired
    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Boss{" +
                "car=" + car +
                '}';
    }
}

@Autowired 不一定要标注在 set 方法上面,标注在任何方法上都可以,比如下面代码中标注在 initCar 上面。

@Bean 方法中的依赖注入(Spring 源码中用的比较多)

// 参数从容器中获取
@Bean
public Boss boss(Car car) {
    return new Boss();
}

或者:

@Configuration
@Import({Car.class})
public class MyConfigOfDI {

    private Car car;

    @Autowired(required = false)
    void setCar(Car car) {
        this.car = car;
    }


    @Bean
    public Boss boss() {
        Boss boss = new Boss();
        boss.setCar(car);
        return boss;
    }
}

基于配置类中普通方法的依赖注入(Spring 源码中用的比较多)

注意: 如果 @Autowired 中的 required 属性为 false,同时方法中的参数在容器中确实没有,那么这个方法不会执行(在查看 Sping Security 源码时有这个疑惑)。

  • 创建一个普通类 Car
public class Car {}
  • 在配置类中注入这个 Car
@Configuration
public class MyConfigOfDI {

    private Car car;

    @Autowired(required = false)
    void initCar(Car car) {
    	// 程序不会走到这里,在方法参数那里,如果 car 为空的话,就直接结束掉方法了
        this.car = car;
    }
}