美文网首页
DI(依赖注入)

DI(依赖注入)

作者: macchiato漂浮 | 来源:发表于2019-11-04 01:33 被阅读0次

一、依赖注入

DI(Dependency Injection 依赖注入)等同于IOC控制反转。使用DI让有依赖关系的对象解耦,同时使得代码更加简洁。依赖注入有两种方式:基于构造方法注入 & 基于setter注入。

1. 基于构造方法注入:

基于构造方法注入时,容器调用带参构造方法,将每个参数作为依赖注入给对象。对象并不依赖容器的任何接口,基类和注解,仅仅作为一个普通的POJO。带参静态工厂方法和构造方法注入同理,参数会被当作依赖注入。

构造参数注入顺序

构造方法注入时,如果参数没有歧义也不存在相互依赖,则注入顺序与参数定义顺序一致。使用方式及配置如下:

public class ExampleBean {

    private AnotherBean beanOne;
    private int i;

    public ExampleBean(
        AnotherBean anotherBean, int i) {
        this.beanOne = anotherBean;
        this.i = i;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>
    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
构造器参数匹配的三种方式:
  • 构造参数类型匹配:

当参数引用的是另一个bean,对于容器这个bean的类型是已知的,因此可以顺利匹配并注入。但如果参数是简单类型,如String类型的,容器并没有我们想象的智能,这时就需要我们在配置中指定参数的类型。实际编码中IDE也会提示错误信息。

这种情况可以在XML的 <bean/> 定义中,指定参数类型如下:

<bean id="someBody" class="com.example.di.SomeBody">
    <constructor-arg type="java.lang.String" value="wang"/>
    <constructor-arg type="int" value="25"/>
</bean>
  • 构造参数index匹配:
<bean id="someBody" class="com.example.di.SomeBody">
    <constructor-arg index="0" value="wang"/>
    <constructor-arg index="1" value="25"/>
</bean>
  • 构造参数名称匹配:
<bean id="someBody" class="com.example.di.SomeBody">
    <constructor-arg name="name" value="wang"/>
    <constructor-arg name="age" value="25"/>
</bean>
静态工厂方法注入:

对于静态工厂方法注入和构造注入同理,使用方式及配置如下:

public class ExampleBean {

    private ExampleBean(...) {
        ...
    }

    public static ExampleBean createInstance (
        AnotherBean anotherBean, int i) {
        ExampleBean eb = new ExampleBean (...);
        return eb;
    }
}
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>

2. 基于setter方法注入:

setter注入是指容器调用构造或工厂方法实例化bean之后,再通过调用setter方法来注入依赖。使用方式及配置如下:

public class ExampleBean {

    private AnotherBean beanOne;
    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>
    <property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>

3. 构造方法注入 VS setter注入:

实际中可以结合两种方式,使用构造方法注入强制依赖项,同时使用setter注入可选依赖项。对于强制依赖项的注入,也可以在setter方法上使用@Required注解,但仍推荐通过编程式构造方法来达到目的。

Spring推荐使用构造注入,优点有三:
  • 使用构造注入保证组件不可变:当属性被final修饰时,可以通过构造方法注入,并能保证属性不变;
  • 使用构造注入保证依赖不为空:构造注入时如果依赖不存在,容器会报错;相较而言setter有可能注入空依赖,使用依赖时就需要判断其非空性。
  • 使用构造注入保证依赖被完全初始化:向构造传参时要保证参数不为空,所以会先调用依赖的构造方法使其先完成实例化;
setter注入也不是没有优点,比如:
  • setter注入可以对类重新注入重新配置;
  • 当产生循环依赖时,可以使用setter注入代替构造器注入,避免强制注入循环依赖时产生BeanCurrentlyInCreationException异常。

二、使用depends-on

通常我们会通过将需要依赖的bean定义为属性,并通过XML中的ref属性指定依赖的bean。但是如果两者没有强依赖关系,只是一个要先于另一个初始化,就可以通过depends-on来指定创建的先后关系。同时先创建的后销毁。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

三、懒加载beans

spring容器在加载时会检查配置问题,如引用不存在的bean、循环依赖等。默认情况下,容器会在启动时创建和配配置单例bean,这样能尽快发现配置错误。但是当不需要这种预实例化行为时,我们也可以通过将bean定义为懒加载模式使其在被使用时才创建。

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>

但是当一个懒加载bean被单例bean依赖时,仍然会在容器启动时先创建依赖项,即使它是懒加载的。

四、自动装配

除了通过ref手动设置bean依赖项,spring也允许使用autowire来自动装配bean。好处在于不再需要显示指定依赖,并且自动装配可以随着bean的演变自动更新配置,无需手动修改配置。

1. 自动装配的四种模式

在基于XML的配置中,通过<bean/>autowire属性来指定自动装配的模式。包括四种模式:

  • no:不自动装配(默认值)。意味着必须手动通过ref指定依赖;
  • byName:通过属性名自动装配。容器会去查找和属性名称相同的bean将其注入;
  • byType:通过属性类型自动装配。容器会去查找和属性类型相同的bean,如果有唯一可用的bean则注入,如果有多个会抛出异常,如果没有则不设置此属性;
  • constructor:和byType模式同理,不过是针对构造器参数;

2. 自动装配的缺陷:

  • 会被显示装配覆盖;无法注入简单类型属性;
  • 不如显示装配精确;
  • 自动装配时如果匹配了多个可依赖项,会导致异常;

如果一定要使用自动装配,可以通过以下方式避免多匹配:
给依赖项设置autowire-candidate属性为false,避免被匹配(仅对byType起作用);
给某依赖项设置primary为true,使其优先匹配;

五、方法注入

在依赖注入中假设一种场景:singleton bean A 依赖 prototype bean B,并希望在每次调用A的方法时创建一个新的B实例。但是因为A是单例的所以只会被创建一次,因此只有一次机会设置B属性。

解决这种问题我们需要牺牲一部分控制反转特性。通过让A实现ApplicatiocContextAware接口使得A可以感知容器,并通过getBean("B")向容器请求新的B实例。但是这种方式使得代码可以感知容器并且于Spring框架耦合,因此并不是好的实现。

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

查找方法注入:

查找方法注入是解决以上问题的一种方式。通过CGLIB字节码动态生成CommandManager的子类并重写createCommand方法。

public abstract class CommandManager {
    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
    protected abstract Command createCommand();
}
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
</bean>

<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

不需要实现ApplicationContextAware接口,定义一个创建bean的方法,如果是抽象方法,动态生成的子类会实现此方法,如果是具体方法,则会被子类重写;被依赖的bean也需要指定为protoType的,否则每次会返回同一个对象;如果使用注解代替XML配置,则需要在createCommand()方法上使用@Lookup("myCommand")

相关文章

  • 开源项目的依赖注入

    开源项目的依赖注入 依赖注入概念 依赖注入(DI:Dependency Injection): 依赖注入方式: B...

  • DI(依赖注入)

    一、依赖注入 DI(Dependency Injection 依赖注入)等同于IOC控制反转。使用DI让有依赖关系...

  • 初识Spring架构

    对Spring的了解 依赖注入DI(Dependency injection) DI分为依赖和注入 那怎么将对象注...

  • Dagger2常用注解诠释

    依赖注入 控制反转(IoC)与依赖注入(DI)浅谈依赖注入理解依赖注入(IOC)和学习Unity Gradle配置...

  • Angular 依赖注入

    Angular 依赖注入 依赖注入:Dependency Injection 简称DI 控制反转:Inversio...

  • Spring IOC容器

    由于Spring是采用依赖注入(DI)的方式来实现IOC,所以本文将IOC和依赖注入(DI)等同看待,主要讨论依赖...

  • Dependency injection(DI,依赖注入)和IO

    解释一下(DI,依赖注入)和IOC(Inversion of control,控制反转)? 依赖注入DI是一个程序...

  • 控制反转

    什么是控制反转(IOC),什么是依赖注入(DI)?问题:什么是控制反转(IOC),什么是依赖注入(DI)?IOC:...

  • 浅谈ASP.NET Core中的DI

    DI的一些事 传送门马丁大叔的文章 什么是依赖注入(DI: Dependency Injection)? 依赖注入...

  • 第三章 在spring中引入IoC和DI

    概念关系 控制反转(IoC) VS 依赖注入(DI) 控制反转可以分为两种子类型:依赖注入(DI)和依赖查找 1....

网友评论

      本文标题:DI(依赖注入)

      本文链接:https://www.haomeiwen.com/subject/bwmlbctx.html