Spring Framework的一些思考——依赖注入

本文连接:https://www.jianshu.com/p/df19a9ec8268
本文作者:gks09@qq.com

什么是依赖注入?


依赖注入(DI)是指对象定义它们自身依赖的过程。此过程可以通过配置构造方法的参数来定义依赖,也可以通过配置工厂方法的参数来获得依赖,还可以在对象实例化后配置属性来定义依赖。

Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean.

——Spring官方文档7.4.1 Dependency Injection

一般来说我们所需要的对象(可以是POJO,也可以不是POJO)会有诸多依赖(例如一个User对象有name、sex等属性,一个query服务对象依赖于数据库连接管理对象等),不使用Spring框架的时候,我们会在代码中使用new关键字来产生新对象;使用Spring框架后,可以通过xml配置、Java代码配置等方式,直接产生新对象并交由Spring框架(IoC container)统一管理。

原先对所有对象的控制权在于我们,我们在代码逻辑中控制对象;现在所有的对象配置好后,在项目执行阶段就完全交由Spring框架管理,包括对象的实例化、对象销毁、多个对象之间相互依赖等。这就是控制反转(Inversion of Control,IOC)。

参考:1、Spring官方文档(7. The IoC container

依赖注入的优缺点?


优点

DI是一种编程思想,其目的是统一的对象和依赖管理。DI的优点是会更易于管理和解耦对象之间的依赖,使得代码更加的简单。对象不再关注依赖,也不需要知道依赖类的位置。这样的话,开发者的类更加易于测试,尤其是当开发者的依赖是接口或者抽象类的情况,开发者可以轻易在单元测试中mock对象。

Code is cleaner with the DI principle and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies, and does not know the location or class of the dependencies. As such, your classes become easier to test, in particular when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.

——Spring官方文档7.4.1 Dependency Injection

缺点

从Stack Overflow上,看到一些讨论,获得认同最多的回答如下:

A couple of points:

  1. DI increases complexity, usually by increasing the number of classes since responsibilities are separated more, which is not always beneficial

  2. Your code will be (somewhat) coupled to the dependency injection framework you use (or more generally how you decide to implement the DI pattern)

  3. DI containers or approaches that perform type resolving generally incur a slight runtime penalty (very negligible, but it's there)

Generally, the benefit of decoupling makes each task simpler to read and understand, but increases the complexity of orchestrating the more complex tasks.

——What are the downsides to using Dependency Injection? [closed]

Stack Overflow的答者认为:

  1. 依赖注入增加了复杂度。有时候为了将不同对象的责任完全划分,会增加对象(类)的数量,并非总是有益。

  2. 代码可能会和依赖注入框架耦合。

  3. 依赖注入容器或实现方法需要进行类型解析,会带来轻微的不足。

目前笔者个人不太能深入理解依赖注入的缺点。个人认为,依赖注入会增加代码量(但好的DI框架可以帮助我们减少代码),在项目开始前需要对不同对象、不同业务进行划分,对开发者有一定的要求。

当然依赖注入的缺点,本身就是一个有争议的话题。没有一种pattern是完美的。

参考:1、What are the downsides to using Dependency Injection? [closed]

结合Spring框架谈DI

结合Spring Framework谈一谈依赖注入,Spring Framework做了很多工作,避免依赖注入和业务代码耦合,有助于我们专注于对象、依赖、业务的设计。Spring帮助我们产生、管理、使用、销毁bean,不需要我们手动去操作bean。不过带来的缺点就是Spring框架较为复杂,上手慢,用得不好会适得其反。

Spring的依赖注入方式


本节主要引用Spring官方文档。

基于构造方法的注入

基于构造函数的依赖注入是由IoC容器来调用类的构造函数,构造函数的参数代表这个Bean所依赖的对象。跟调用带参数的静态工厂方法基本一样。下面的例子展示了一个类通过构造函数来实现依赖注入的。

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

xml的配置如下:

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

另外,bean的参数为简单类型时(如Java基本类型、String等),可以使用type字段进行配置:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

也可以使用index字段进行配置,指明构造函数的参数index:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

同样,也可以使用参数名进行配置:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

需要注意的是,做这项工作的代码必须启用了调试标记编译,这样Spring才可以从构造函数查找参数名称。开发者也可以使用@ConstructorProperties注解来显式声明构造函数的名称,比如如下代码:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

基于setter方法的注入

基于setter函数的依赖注入则是容器会调用Bean的无参构造方法,或者无参数的工厂方法,然后再来调用setter方法来实现的依赖注入。有的时候,会有对象相互依赖的情况,如A依赖B、B也依赖A。这时候无法通过含参的构造方法来注入A和B,需要先调用无参构造方法,然后用过Setter设置属性。


    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

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

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

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

ApplicationContext所管理Bean对于基于构造函数的依赖注入,或者基于Setter方式的依赖注入都是支持的。同时也支持使用setter方式在通过构造函数注入依赖之后再次注入依赖。开发者在BeanDefinition中可以使用PropertyEditor实例来自由选择注入的方式。然而,大多数的开发者并不直接使用这些类,而是跟喜欢XML形式的bean定义,或者基于注解的组件(比如使用@Component@Controller等)或者在配置了@Configuration的类上面使用@Bean的方法。

本质上,使用annotation进行也是使用基于构造方法的注入或基于setter的注入。

如何选择注入方式?

一般来说,我们可以混用基于构造方法的注入和基于setter的注入。Spring建议我们,使用constructor-based dependency injection进行强制依赖的注入,使用setter-based dependency injection 进行可选依赖的注入。当然,可以在Setter方法上面的@Required注解可用来构造必要的依赖。

Constructor-based dependency injection有几点好处:它可以保证注入的依赖是非null的,并且该组件被他人调用时一定是完好的。此外要注意,构造方法不应有过多参数,参数过多说明该对象的功能过复杂,应考虑拆分。

Setter-based dependency injection的好处是,可以进行重配置和重新注入。Setter-based dependency injection应给对象的属性附初始值,否则在使用时需要进行非null检查。

依赖注入的解析过程

Spring容器首先会实例化ApplicationContext,然后依据xml、annotation或java代码进行bean的加载。如果bean依赖于其他bean,则依赖的bean会先加载。

在容器创建后,容器会验证bean的配置,但是bean的属性没有被赋值。单例类型的bean会先预实例化(pre-instantiated)。bean只有被请求时,才会真正创建。

创建bean时,实际上是创建了一个依赖图,容器按此顺序进行bean的创建。

依赖注入的实现方法


待补充

参考文档:


  1. Dependencies

  2. Spring官方文档[7.4.1 Dependency Injection]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351