spring依赖注入

468 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

一、什么是依赖注入?

IOC容器的职责有两个

业务对象的构建管理和业务对象间的依赖绑定

依赖注入是我们可以用来实现 IoC 的一种模式,其中被反转的控制是设置对象的依赖关系。

将对象与其他对象连接起来,或将对象“注入”到其他对象中,是由程序完成的,而不是由对象本身完成的。以下是我们如何在传统编程中创建对象依赖项:

/**
在这个例子中,我们需要在Store类本身中实例化Item接口的实现。
*/
public class Store {
    private Item item;
 
    public Store() {
        item = new ItemImpl1();    
    }
}

通过使用 DI,我们可以重写示例,而无需指定我们想要的Item的实现:

public class Store {
    private Item item;
    public Store(Item item) {
        this.item = item;
    }
}

二、依赖注入的三种方式

(一)基于构造函数

基于构造函数的依赖注入的情况下,容器将调用带有参数的构造函数,每个参数代表我们要设置的依赖项。

Spring 主要按类型解析每个参数,然后是属性名称。

  1. 注解方式
@Configuration
public class AppConfig {
    
    @Bean
    public Item item1() {
        return new ItemImpl1();
    }
    @Bean
    public Store store() {
        return new Store(item1());
    }
}

@configuration注释表示该类是一个配置类,试用java配置替代xml配置

我们使用@Bean注解在方法中定义bean。如果我们未指定自定义名称,则Bean名称将默认为方法名称。

对于带有默认单例范围的bean,Spring首先检查Bean的缓存实例是否已存在,如果它没有创建新的字段。如果我们使用的原型范围,容器会返回每个方法调用的新bean实例。

从 Spring 4.3 开始,具有单个构造函数的类可以省略 @Autowired注释。

  1. xml配置方式
<bean id="item1" class="org.baeldung.store.ItemImpl1" /> 
<bean id="store" class="org.baeldung.store.Store"> 
  <constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" /> 
</bean>

(二)基于setter方法

对于基于SATTER的注入,容器将在调用NO-Argument构造函数或No-Argument静态工厂方法以实例化Bean之后调用我们类的Setter方法。让我们使用注释创建此配置:

@Service
public class UserService {
    private Wolf3Bean wolf3Bean;
    
    @Autowired  //通过setter方法实现注入
    public void setWolf3Bean(Wolf3Bean wolf3Bean) {
        this.wolf3Bean = wolf3Bean;
    }
}

xml方式

<bean id="store" class="org.baeldung.store.Store">
    <property name="item" ref="item1" />
</bean>

(三)基于属性

在基于属性的依赖注入,我们可以通过用@Autowired注释标记它们来注入依赖项:

public class Store {
    @Autowired
    private Item item; 
}

@Autowrite 和 @Resource 以及 @Qualifier 注解的区别

注入一个 Bean 可以通过 @Autowrite,也可以通过 @Resource 注解来注入,这两个注解有什么区别呢?

@Autowrite:通过类型去注入,可以用于构造器、set参数、属性注入。当我们注入接口时,其所有的实现类都属于同一个类型,所以就没办法知道选择哪一个实现类来注入。

@Resource:默认通过名字注入,不能用于构造器和参数注入。如果通过名字找不到唯一的 Bean,则会通过类型去查找。如下可以通过指定 name 或者 type 来确定唯一的实现:

@Resource(name = "wolf2Bean",type = Wolf2Bean.class)
 private IWolf iWolf;

而 @Qualifier 注解是用来标识合格者,当 @Autowrite 和 @Qualifier 一起使用时,就相当于是通过名字来确定唯一一个对象

@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;

@Qualifier特殊用途

@Resource 注解又是不能用在参数中,下面的这种情况就需要使用 @Qualifier 注解来确认唯一实现了(比如在配置多数据源的时候就经常使用 @Qualifier 注解来实现):

@Component
public class InterfaceInject2 {
    @Bean
    public MyElement test(@Qualifier("wolf1Bean") IWolf iWolf){
        return new MyElement();
    }
}

@Primary

在某些情况下,我们需要注册多个相同类型的 bean我们使用 @Primary 给一个 bean 更高的优先级。

在这个例子中,我们有Employee类型的JohnEmployee()TonyEmployee() bean :

@Configuration
public class Config {

    @Bean
    public Employee JohnEmployee() {
        return new Employee("John");
    }

    @Bean
    public Employee TonyEmployee() {
        return new Employee("Tony");
    }
}

如果我们尝试运行应用程序,Spring 会抛出 NoUniqueBeanDefinitionException

要访问具有相同类型的 bean,我们通常使用 @Qualifier(“beanName”) 注释。

我们将它与@Autowired一起应用在注入点。在我们的例子中,我们在配置阶段选择了 bean,所以 @Qualifier不能在这里应用。我们可以通过以下链接了解更多关于 @Qualifier注解的信息。

为了解决这个问题,Spring 提供了 @Primary注释。

  1. 将@Primary与@Bean 一起使用

让我们看一下配置类:

@Configuration
public class Config {

    @Bean
    public Employee JohnEmployee() {
        return new Employee("John");
    }

    @Bean
    @Primary
    public Employee TonyEmployee() {
        return new Employee("Tony");
    }
}


我们用 @Primary 标记 TonyEmployee () bean 。Spring 将优先于JohnEmployee()注入TonyEmployee() bean 。

现在,让我们启动应用程序上下文并从中获取Employee bean:

AnnotationConfigApplicationContext context
  = new AnnotationConfigApplicationContext(Config.class);

Employee employee = context.getBean(Employee.class);
System.out.println(employee);

/**
    运行程序后得到Employee{name='Tony'}
**/

2. 将@Primary与@Component一起使用

让我们看一下以下场景,有两个类实现下面接口

public interface Manager {
    String getManagerName();
}

@Component
public class DepartmentManager implements Manager {
    @Override
    public String getManagerName() {
        return "Department manager";
    }
}

@Component
@Primary
public class GeneralManager implements Manager {
    @Override
    public String getManagerName() {
        return "General manager";
    }
}

当下面的service使用Manager接口时,注入的是 @Primary 标记的 GeneralManager

@Service
public class ManagerService {

    @Autowired
    private Manager manager;

    public Manager getManager() {
        return manager;
    }
}
ManagerService service = context.getBean(ManagerService.class);
Manager manager = service.getManager();
System.out.println(manager.getManagerName());

三、Autowired注解的实现原理

依靠@Autowired注解实现属性注入主要利用了java中的反射原理,spring在创建bean的过程中,对bean的属性进行填充,判断属性是否有@Autowired注解,如果有这个注解,spring就会帮忙在容器中找属性对用的bean对象,再利用反射原理,对属性进行赋值。

  1. @Autowired注解的实现是通过后置处理器AutowiredAnnotationBeanPostProcessor类的postProcessPropertyValues()方法实现的。
  2. 当自动装配时,从容器中如果发现有多个同类型的属性时,@Autowired注解会先根据类型判断,然后根据@Primary、@Priority注解判断,最后根据属性名与beanName是否相等来判断,如果还是不能决定注入哪一个bean时,就会抛出NoUniqueBeanDefinitionException异常。

\