控制反转和容器(二)

接上文[url]http://sarin.iteye.com/blog/593207[/url]
[img]http://dl.iteye.com/upload/attachment/205806/13b82d4e-38e9-37e2-8c06-b81284c68baa.jpg[/img]
public interface Injectable {
public void inject(Map<String,Object> components);
}

public class ChartService implements Injectable {
private ChartGenerator chartGenerator;
public void inject(Map<String, Object> components) {
chartGenerator = (ChartGenerator) components.get("chartGenerator");
}
… … …
}

public class Container {
… … …
public Container() {
components = new HashMap<String, Object>();
ChartGenerator chartGenerator = new PieChartGenerator();
components.put("chartGenerator", chartGenerator);
ChartService chartService = new ChartService();
components.put("chartService", chartService);
chartService.inject(components);
}
… … …
}

接口注入的缺点非常明显,就是组件必须实现特定接口,而这个接口是特定于容器的,所以组件对容器产生依赖,一旦脱离容器,组件不能重用。这就是“侵入式”的设计,容器“侵入式”注入组件,由于这个原因,绝大部分IoC容器不支持接口注入。
构造方法注入,这是大部分IoC容器支持的方式,使用构造方法注入时,容器通过显式定义的构造方法传入参数,此时Java编译器不会给这个类增加默认的构造方法了,这在使用时要注意,一般是人为定义一个默认的构造方法。使用构造方法注入时,我们不会遗漏任何的依赖,依赖一旦注入将不能被更改,不会因为无意修改而造成问题。
[img]http://dl.iteye.com/upload/attachment/205808/732bdb6e-b356-3b63-aadc-afea65bb7ba9.jpg[/img]
public class ChartService {
private ChartGenerator chartGenerator;
public ChartService() {// 默认构造方法
}
public ChartService(ChartGenerator chartGenerator) {// 重载构造方法
this.chartGenerator = chartGenerator;
}
… … …
}

public class Container {
… … …
public Container() {
… … …
ChartService chartService = new ChartService(chartGenerator);
components.put("chartService", chartService);
}
… … …
}

但是构造方法也有自己的局限,setter方法注入时类中的setXXX()方法名会告诉你正在注入的依赖。使用构造方法时,只能通过参数的位置来确定参数。而且组件的参数过多时,那么构造方法的参数列表将会非常冗余。
setter方法注入,这种方法非常易于使用,也很简单,绝大多数Java IDE也都支持自动生成setter方法,所以这是一种非常流行的方法。
[img]http://dl.iteye.com/upload/attachment/205820/a2f406b5-5d7d-3ce6-82ce-eb175ec83349.jpg[/img]
public class ChartService {
private ChartGenerator chartGenerator;
public void setChartGenerator(ChartGenerator chartGenerator) {
this.chartGenerator = chartGenerator;
}
… … …
}

public class Container {
… … …
public Container() {
… … …
ChartService chartService = new ChartService();
chartService.setChartGenerator(chartGenerator);
components.put("chartService", chartService);
}
… … …
}

setter方式的注入也存在一些小问题,如果组件使用者忘记给组件注入它所需的依赖,将会导致NullPointerException异常,这个异常的查找是非常麻烦,因为它埋藏的很深。不过Spring的IoC容器在组件初始化时提供对特定依赖的检查。同时,setter注入的另外一个缺点就是安全性,必须对此做出处理,因为在第一次注入之后除非自己实现安全机制来阻止再次调用setter方法,否则依赖仍然可能会因为setter方法的调用而被二次修改,这种无意的修改将会带来无法预知的后果,而它也是埋藏很深的一种BUG。
至此我们已经彻底理解依赖注入的原理了,但是代码都是写死后来做测试的,下面我们使用XML文件来进行动态依赖注入。
所用依赖注入方式为setter注入。不使用properties是因为java.util.Properties类继承了HashTable类,而HashTable是无序的,而依赖注入是有顺序的,那么使用properties文件就可能产生问题。
实验时需要Apache Commons组件Beanutils和Logging,还有DOM4J来解析XML。DOM4J是也是Hibernate进行配置文件解析所依赖的第三方类库,使用方便。
[img]http://dl.iteye.com/upload/attachment/205822/c37710ba-aca9-3d23-ab32-1aa57b1f13a1.jpg[/img]
package v5; 
import java.io.File;
import java.util.*;
import org.apache.commons.beanutils.PropertyUtils;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
public class Container {
private Map<String, Object> components;
public Container() {
components = new HashMap<String, Object>();
try {
File f = new File("src/v5/components.xml");
Document document = new SAXReader().read(f);
Element root = document.getRootElement();
Element component;
Iterator iterator = root.elementIterator("component");
Map<String, String> properties = new TreeMap<String, String>();// TreeMap是有序的,不会造成依赖注入时顺序颠倒
while (iterator.hasNext()) {// 遍历节点,获得所需元素的值
component = (Element) iterator.next();
properties.put(component.elementText("key"), component
.elementText("value"));
}
System.out.println(properties);
for (Map.Entry entry : properties.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
processEntry(key, value);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void processEntry(String key, String value) throws Exception {
String[] parts = key.split("\\.");
if (parts.length == 1) {// 新组件定义
Object compnent = Class.forName(value).newInstance();
components.put(parts[0], compnent);
} else {// 依赖注入
Object component = components.get(parts[0]);
Object reference = components.get(value);
PropertyUtils.setProperty(component, parts[1], reference);
}
}
public Object getComponent(String id) {
return components.get(id);
}
}

<?xml version="1.0" encoding="UTF-8" ?> 
<components>
<component>
<key>chartGenerator</key>
<value>v5.PieChartGenerator</value>
</component>
<component>
<key>chartService</key>
<value>v5.ChartService</value>
</component>
<component>
<key>chartService.chartGenerator</key>
<value>chartGenerator</value>
</component>
</components>
按顺序注入依赖,不能颠倒顺序。
至此,我们模拟了依赖注入的整个过程,那么后面对Spring的学习效率将会大幅提高。一家之言,仅供参考。希望对学习者有用。(全篇完)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值