依赖注入
在设计模式与IoC这篇文章中,介绍了Spring基础的三大支柱的两项内容——IoC、Bean。本篇将继续围绕着Bean的创建时的注入方式来介绍Spring的核心思想与设计模式。

天底下所有面向对象的语言都不可能只用一个类来解决问题,即使是最简单的应用程序都存在类与类之间的依存关系。如下面这个人人都理解的组合例子:

class Foo{
private Other other;
public Foo(){
other = new Other();
}
}

class Other{}
在设计模式上关于类的组合与继承的适用性不属于本篇的讨论范围,但是从Spring框架非侵入式的设计思路来看,组合才是使用Spring的正确姿势。

官方将这种组合的关系叫做“依赖注入(DI——Dependency injection)”。从名字上来看这也是一种依托Ioc容器很自然的实现方式——所有的Bean都放置在容器中,然后通过一些配置来告诉容器bean与bean之间的依存关系。一个类除了在内部块中通过new关键字实现一个组合关系,也可以通过构造方法传参或接口方法设置。

由于IoC容器不可能去修改一个类内部的代码,所以类与类的组合方式通过构造方法(Constructor)和set方法(Setter)来实现。此外,Ioc可以根据接口(interface)来注入对应的实现类(class extands interface),所以从设计模式的角度来说,依赖注入的方式很好的规避了标准组合模式中new关键字违反依赖倒置原则的问题。

构造方法注入
直接通过构造方法注入组合数据。

class:

package x.y;

public class A {
private B b;
private C c;
public Foo(B b, C c) {
this.b = b;
this.c = c;
}
}
public class B {}
public class C {}
xml:

<beans>
<bean id="a" class="x.y.A">
<constructor-arg ref="b"/>
<constructor-arg ref="c"/>
</bean>

<bean id="b" class="x.y.B"/>

<bean id="c" class="x.y.C"/>
</beans>
如果是源生类型的参数,可以通过指定类型来注入数据:

package x.y;

public class A {
private int b;
private String c;
public Foo(int b, String c) {
this.b = b;
this.c = c;
}
}
<bean id="a" class="x.y.A">
<constructor-arg type="int" value="1"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
也可以通过索引的方式:

<bean id="a" class="x.y.A">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="42"/>
</bean>
配合@ConstructorProperties注解,还可以直接使用名称来注入:

package x.y;

public class A {
private int b;
private String c;
@ConstructorProperties({"b", "c"})
public Foo(int b, String c) {
this.b = b;
this.c = c;
}
}
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="b" value="1"/>
<constructor-arg name="c" value="42"/>
</bean>
在Debug模式下不用这个注解也可以实现按名字注入,但是千万别这样做。

Set方法注入
package x.y;

public class A {
private B b;
private C c;
private String value;
public void setA(A a){this.a = a;}
public void setB(B b){this.b = b;}
public void setB(String value){this.value = value;}
}
public class B {}
public class C {}
<bean id="a" class="x.y.A">
<property name="b" ref="b"/>
<property name="c" ref="c"/>
<property name="value" value="1"/>
</bean>
<bean id="b" class="x.y.B"/>
<bean id="c" class="x.y.C"/>
使用 Constructor还是Setter?
2种注入方法在使用的过程中我们应该如何选取呢?Spring官方给出的答案是如果注入的数据或bean是一个“必要依赖”那么使用构造方法注入,如果属于配置性的非必须数据,使用Set方法注入。但是在实际应用时,会发现绝大部分注入方式都是通过Setter实现的,包括一些很流行的开源工具,例如下面的druid:

<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/c3p0jdbctemplate"/>
<property name="user" value="admin"/>
<property name="password" value="123456"/>
</bean>
话说你不提供账户和密码能链接到数据库吗?这算必要依赖还是配置性依赖?所以也不用死守这些规则。下面是一些关键性的建议:

数据配置类使用constructor注入的方法来实现,因为这样可以将bean设置为一个不可变对象(immutable objects)。这样结合单例模式能够很好实现享元模式共享数据,结合原型模式可以创建“浅对比”对象(变更则替换)。
如果构造函数要传入的参数太多,证明你的类要完成的责任太多,这个时候用Setter当然比较合理,但是建议回头去看看类当中是不是有可以拆分的功能。给你们推荐一个学习java的平台659270626,趁年轻,使劲拼,给未来的自己一个交代!
Setter注入主要用于可选的依赖关系,如果没有设置值,类应该提供默认值。所以Setter方法应该检查传入值的有效性(not null、not blank等)。
如果出现了循环依赖,其实可以通过一个bean使用setter注入另外一个bean使用constructor注入来解决,不过最好检查一下代码为什么会循环,这是设计模式上的大忌。
最有一个建议最重要。如果用第三方类,别人给什么你只能用什么,没得选。
注入参数
在XML配置中,用来设定注入方式和注入数据的XML标签很多,详细内容就不一一复述了,常规用法可以到官网 Dependencies and configuration in detail  一节了解。这里仅仅说明一些要点:

父子Bean。Ioc容器提供Bean的父子关系配置。父子关系Bean可以进行数据合并,但是很少看见什么地方有实际应用。
<idref>标签和<ref>标签的差异:1)前者只能通过id引入,后者可以通过id或name引入;2)前者可以直接用value属性替换,但是value属性的效率会差很多;3)前者只能适用与当前配置文件或当前容器,后者可以引入任何位置的内容。
当需要设置一个null值时,用<null>标签代替value=""。在执行代码时直接传入一个null。
自动装配
这里所说的自动装配是通过<bean>上的autowire属性实现的功能,与@Autowired注解并不是一回事,但是他的一些参数会影像@Autowired注解的行为。

在有@Autowired注解的情况下,autowire属性现在用得很少。基本上他实现的结果和@Autowired差不多,就是让Ioc容器根据bean的类型或者bean名称等自动将容器中其他能对应得上的bean注入到对于的构造方法或者set方法中。详情了解 Autowiring collaborators。

方法注入
如果每一个Bean都是单例模式,那么我们通过常规的XML配置引用的手段就可以实现所有的依赖组合关系。但是每个bean都有不同的生命周期,常规配置方法很难实现某些应用不同生命周期bean的依赖关系。

第一种方式是通过继承 ApplicationContextAware 类,继承后可以直接使用 applicationContext 的 getBean 接口来获取任何一个 bean。

package x.y;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class BeanManagerFoo implements ApplicationContextAware {

private ApplicationContext applicationContext;

public <T> T getBean(Class<T> type){
return applicationContext.getBean(type);
}

public Object getBean(String id){
return springContext.getBean(id);
}

public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
第二种方法是使用Lookup Method。

Lookup Method的实现思路是使用CGLIB生成了动态代理类并放置到Ioc中代替源生的类。看下面的例子。

首先实现我们的抽象类,抽象的要求至少有一个抽象方法:

package x.y;

public abstract class A {
public String getName() {
B b = this.createB();
return b.getName();
}

protected abstract B createB();
}

public class B {
private String name = "B class";
public String getName(http://www.my516.com){
return this.name;
}
}
然后通过<lookup-method>标签来指定获取bean的方式:

<bean id="b" class="x.y.B" scope="prototype" />

<bean id="a" class="x.y.A">
<lookup-method name="createB" bean="b"/>
</bean>
现在,在调用A.getName方法时都会创建一个新的B类实例。需要注意scope属性,如果修改为singleton则每次都获取同一个B实例。

使用动态代理由于是字节码级别的变换,所有有很多限制需要注意:方法和类都不能用fina关键字;测试用例需要自己实现代理模式,否则抽象类没有实现;

第三种方法是使用委派模式,即我们执行A.compute方法时,实际执行的是被委派的B.reimplement方法。

先定义2个基础类——Origin、Replace:

package x.y
public class Origin {
public int compute(int in1, int in2) {
return in1+in2;
}
}

public class Replace {
public int reimplement(Object o, Method m, Object[] args) {
int in1 = (int)args[0];
int in2 = (int)args[1];
return in1+in2;
}
}
然后定义Spring配置:

<bean id="origin" class="x.y.Origin">
<replaced-method name="compute" replacer="replace">
<arg-type>int</arg-type>
<arg-type>int</arg-type>
</replaced-method>
</bean>

<bean id="replace" class="x.y.Replace"/>
这个时候,在任何时候执行“origin”这个bean的compute方法,实际上都是执行的Replace::reimplement方法。

上面<arg-type/>的参数用全称或简写都可以,例如java.lang.String,使用String,Str都是指向这个类型。

使用委派模式的好处是限制少、灵活,并且不会用到CGLIB这种重量级工具。但是委派之后委派方法的真实参数和被委派方法的参数完全不一样,开发时需要时时刻刻紧跟委派类的结构来修改代码。一旦委派类发生任何修改而没有相应的调整被委派类,可能会出现意想不到的问题。
---------------------

Spring核心——Bean的依赖注入的更多相关文章

  1. Spring进阶之路(1)-Spring核心机制:依赖注入/控制反转

    原文地址:http://blog.csdn.net/wangyang1354/article/details/50757098 我们经常会遇到这样一种情景,就是在我们开发项目的时候经常会在一个类中调用 ...

  2. (转)Spring读书笔记-----Spring核心机制:依赖注入

    Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就 ...

  3. Spring核心机制:依赖注入

    转载:http://www.cnblogs.com/chenssy/ Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因 ...

  4. Spring读书笔记-----Spring核心机制:依赖注入

    spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入.今天就和大家一起来学习一下 依赖注入的基本概念 依赖注入(Dependecy Injection),也称为IoC(I ...

  5. Spring学习-spring核心机制-IOC依赖注入

    转载自:http://www.cnblogs.com/chenssy/archive/2012/11/11/2765266.html 今天复习一下spring两大特性之一:IOC依赖注入,看了一下大佬 ...

  6. 30个类手写Spring核心原理之依赖注入功能(3)

    本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...

  7. Spring核心之IoC——依赖注入

    在J2EE开发平台中,Spring是一种优秀的轻量级企业应用解决方案.Spring倡导一切从实际出发,它的核心技术就是IOC(控制反转)和AOP(面向切面编程)技术.本文用的Spring版本为spri ...

  8. 采用Spring管理Bean和依赖注入

    1. 实例化spring容器和从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] ApplicationContext ctx ...

  9. Srping - bean的依赖注入(Dependency injection)

    目录 1 概述 2 两种基本的依赖注入方式 2.1 构造函数方式 2.2Setter方式 3 其他依赖注入功能 3.1 <ref/>标签引用不同范围的bean 3.2 内部bean 3.3 ...

随机推荐

  1. delphi WaitForSingleObject 示例之一等待另一个进程的结束

    <pre>unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Cont ...

  2. vue打包配置发布路径

    目的:配置路径,让打包后的dist在本地可以打开. 方法:修改build文件夹下边的的webpack.dev.conf.js文件,找到devServer下边的publicPath,这个来源于confi ...

  3. 汇编 “ program has no starting address ”解决方法

           

  4. linux设备驱动第二篇:构造和运行模块

      上一篇介绍了Linux驱动的概念,以及linux下设备驱动的基本分类情况及其各个分类的依据和差异,这一篇我们来描述如何写一个类似hello world的简单测试驱动程序.而这个驱动的唯一功能就是输 ...

  5. Map:template

    ylbtech-Map-Amap|Baidu: 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部   7.返回顶部   8.返回顶部   9. ...

  6. 测开之路四十五:Django之最小程序

    安装Django库 Django最小程序 import sysfrom django.conf.urls import urlfrom django.conf import settingsfrom ...

  7. python web自动化测试框架搭建(功能&接口)——环境搭建

    自动化测试框架一般需要实现以下通用功能 执行前准备 结束后清理 执行步骤输出 执行结果输出 错误.失败截图 测试报告 发送邮件 日志 需要的软件和python第三方库有: 通用: JDK Eclips ...

  8. 用webdriver模仿浏览器 爬取豆瓣python书单

    用webdriver模仿浏览器 爬取豆瓣python书单 其中运用到os 模块 作用是生成文件夹 存储爬取的信息 etree 用于xpath解析内容 详细代码如下 可用我的上一篇博客存取到excel当 ...

  9. 解决Ubuntu与Windows双系统时间不同步问题

    目录 1.Windows修改法 1.1设置UTC 1.2恢复LocalTime 2.Ubuntu修改法 2.1设置LocalTime 2.2恢复UTC 切换系统后,往往发现时间差了8小时.这恰恰是北京 ...

  10. LR 场景设置

    LR 场景设置group:多个脚本按照独立设置模式跑,各个脚本可以单独设置虚拟用户.运行时间scenario:多个脚本之间按照相同模式跑,将总的虚拟用户数按照一定比例分配给各个脚本 schedule ...