?接前面2篇“演进式例解控制反转(IoC)、依赖注入(DI)之一”和“演进式例解控制反转(IoC)、依赖注入(DI)之二”的例子继续往下。
?
回顾:
前面两篇文章虽然渐进式地引出了 IoC 和 DI,但那些都是硬编码在源代码中的,灵活性非常糟糕,每次修改组件依赖的配置之后都得重新编译、部署。
?
问题描述:
如“回顾”所指,如何能够使具体组件依赖的配置脱离源代码存在?需要将这种硬编码的僵化设计改进为可灵活热插拔的方式。
?
解决方案:
可以使用我们常见的运行时读取配置文件来管理组件间的依赖性,然后再结合反射技术实现依赖注入。在 Java 里面,除了 XML 文件还有键-值对形式的 .properties 属性文件可以使用。
?
问题在于,在 .properties文件中定义怎样一种合适的格式来方便程序从中获取组件依赖信息并以此进行注入?
?
在我们这个简单的实现中,对 .properties 文件制定如下两种简单定义:
??普通对象名(首字母小写)=完整类名(含包名),指定应该被反射实例化的类实例,描述一个组件的定义。
??普通对象名.字段名(首字母小写)=该.properties文件中已经定义的组件定义,描述依赖注入的定义。注意有个点 . 哦!
?
于是,可以得出如下配置文件格式,这也是下面例子中要用到的配置文件:
# define a new concrete bean'reportGenerator' reportGenerator=IoC_DI.use_reflect.PDFGenerator? # define a new concrete report service'reportService' reportService=IoC_DI.use_reflect.ReportService? # inject the bean 'reportGenerator' into the 'reportService' reportService.reportGenerator=reportGenerator |
?
实现方法:
在上一篇文章的基础上,因为需要容器加载外部的
.properties
文件进行配置管理,结合反射进行组件实例化、注入,所以在这里要自己实现一个非常简单的
setter
方式的依赖注入工具,称之为
BeanUtil
类。
?
BeanUtil.java反射、注入工具类代码如下,请详看注释:
- package?IoC_DI.use_reflect;?
- ?
- import?java.lang.reflect.Method;?
- ?
- public?class?BeanUtil?{?
- ?
- ?????
- ?
- ?
- ?
- ?
- ?
- ?
- ????public?static?void?setProperty(Object?bean,?String?fieldName,?
- ????????????Object?fieldRef)?throws?Exception?{?
- ?
- ?????????
- ????????String?className?=?getClassName(bean);?
- ?
- ?????????
- ????????Class?beanClass?=?Class.forName(className);?
- ????????Method[]?methods?=?beanClass.getMethods();?
- ?
- ?????????
- ????????String?setterName?=?"set"?+?fieldName.substring(0,?1).toUpperCase()?
- ????????????????+?fieldName.substring(1,?fieldName.length());?
- ?
- ?????????
- ????????for?(Method?m?:?methods)?{?
- ????????????if?(m.getName().equals(setterName))?{?
- ????????????????m.invoke(bean,?fieldRef);?
- ????????????????System.out.println("已调用?"?+?m.getName()?+?"()?向?"?+?className?
- ????????????????????????+?"?注入?"?+?getClassName(fieldRef));?
- ????????????????return;?
- ????????????}?
- ????????}?
- ????????System.out.println(">>注入失败:?"?+?className?+?"类中不存在"?+?fieldName?
- ????????????????+?"字段对应的setter()方法?...");?
- ????}?
- ?
- ?????
- ?
- ?
- ?
- ?
- ????private?static?String?getClassName(Object?o)?{?
- ????????if?(o?==?null)?{?
- ????????????System.out.println("传入的?Object?实例为?null?...");?
- ????????????return?null;?
- ????????}?
- ????????String?fullName?=?o.toString();?
- ????????String?className?=?fullName.substring(0,?fullName.indexOf("@"));?
- ????????return?className;?
- ????}?
- }?
?
对于原来的容器 Container 类,也需要相应的修改,主要体现在:
? Container 初始化时加载外部 .properties 配置文件,不再构造器中硬编码实例化各个组件并进行依赖注入。
? Container 加载 .properties 配置文件之后自己解析该文件内容,即遍历其中的所有键-值条目,决定如何处理组件定义、依赖注入。
?
在这个例子中,我将配置文件命名为bean_config.properties ,其内容即为前面给出的那样。
修改后的 Container.java 详细代码如下:
- class?Container?{?
- ?????
- ????private?static?Map<String,?Object>?beans;?
- ?
- ????public?Container()?{?
- ????????System.out.println("1...开始初始化?Container?...");?
- ?
- ????????beans?=?new?HashMap<String,?Object>();?
- ?????????
- ????????try?{?
- ????????????Properties?props?=?new?Properties();?
- ????????????props.load(new?FileInputStream("bean_config.properties"));?
- ?????????????
- ????????????for(Map.Entry?entry?:?props.entrySet())?{?
- ????????????????String?key?=?(String)entry.getKey();?
- ????????????????String?value?=?(String)entry.getValue();?
- ?????????????????
- ????????????????this.handleEntry(key,?value);?
- ????????????}?
- ????????}?catch?(Exception?e)?{?
- ????????????e.printStackTrace();?
- ????????}?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ????????System.out.println("5...结束初始化?Container?...");?
- ????}?
- ?
- ?????
- ?
- ?
- ?
- ?
- ?
- ????private?void?handleEntry(String?key,?String?value)?throws?Exception?{?
- ????????String?[]?keyParts?=?key.split(".");?
- ?????????
- ????????if(keyParts.length?==?1)?{?
- ?????????????
- ????????????Object?bean?=?Class.forName(value).newInstance();?
- ????????????beans.put(keyParts[0],?bean);?
- ????????}else?{?
- ?????????????
- ????????????Object?bean?=?beans.get(keyParts[0]);?
- ????????????Object?filedRef?=?beans.get(value);?
- ????????????BeanUtil.setProperty(bean,?keyParts[1],?filedRef);?
- ????????}?
- ????}?
- ?????
- ????public?static?Object?getBean(String?id)?{?
- ????????System.out.println("最后获取服务组件...getBean()?-->?"?+?id?+?"?...");?
- ????????return?beans.get(id);?
- ????}?
- }?
?
根据以上具体配置文件,运行得到结果如下:
1...开始初始化 Container ... 2...开始初始化 PDFGenerator ... 3...开始初始化 ReportService ... 4...开始注入 ReportGenerator ... 已调用 setReportGenerator() 向 IoC_DI.use_reflect.ReportService 注入 IoC_DI.use_reflect.PDFGenerator 5...结束初始化 Container ...
最后获取服务组件
...getBean() --> reportService ...
generate an PDF report ... |
?
想要使用其他逐渐,只要修改配置文件中第一个组件定义为:
# define a new concrete bean 'reportGenerator'
reportGenerator=IoC_DI.use_reflect.ExcelGenerator
?
运行结果如下:
1...开始初始化 Container ... 2...开始初始化 ExcelGenerator ... 3...开始初始化 ReportService ... 4...开始注入 ReportGenerator ... 已调用 setReportGenerator() 向 IoC_DI.use_reflect.ReportService 注入 IoC_DI.use_reflect.ExcelGenerator 5...结束初始化 Container ...
最后获取服务组件
...getBean() --> reportService ...
generate an Excel report ... |
?
?
注意:
??在文中的这个例子当中,BeanUtil只是非常简单地实现了setter方式的依赖注入,甚至没有参数检查、异常处理等。
??在 Container 类中的私有辅助方法handleEntry() 中,发现对于组件定义和依赖注入的情况有不同的处理。前者组件定义是在该方法内使用反射进行实例化,并添加到beans当中,如下:
if(keyParts.length == 1) { ??? // 组件定义:利用反射实例化该组件 ??? Object bean = Class.forName(value).newInstance(); ??? beans.put(keyParts[0],bean); } |
?
而对于依赖注入,则委托BeanUtil类来完成反射、实例化并注入,代码如下:
else { ??? // 依赖注入:获取需要bean的主体,以及被注入的实例 ??? Object bean = beans.get(keyParts[0]); ??? Object filedRef = beans.get(value); ??? BeanUtil.setProperty(bean,keyParts[1],filedRef); } |
?
在这里我想说的是,好像这样子的设计有点问题,因为关于反射这种细节实现被分开在两个地方(Container 类和 BeanUtil 类),也就是说 BeanUtil 工具类的功能还不够全面,可以再提供一个方法将上面第一种情况委托给 BeanUtil 来完成,实现职责的统一。
?
后记:
实际上,在《Spring攻略》中作者是使用Apache Commons项目的一个开源工具包commons-beanutils来操作 .properties 配置文件的。而我,最初也是按照其建议使用这个包的,可是运行时总是抛出NoSuchMethodException 异常,Property 'reportGenerator' has no setter method in class 'class IoC_DI.use_reflect.ReportService'。Eclipse自动生成的setter未能解决该问题,自己查看commons-beanutils 包对应类的源代码也没无果。猜测问题可能出在commons-beanutils 包对应类好像使用了麻烦的描述符来查找 setter 方法。最后还是自己实现一下更加轻快:-D
?
回头看看上一篇文章,应该更能帮助理清例子的演进历程:-D
演进式例解控制反转(IoC)、依赖注入(DI)之一
演进式例解控制反转(IoC)、依赖注入(DI)之二
?
以下文章你可能也会感兴趣:
(Factory Method)工厂方法模式的Java实现
Java RMI 框架的工厂方法模式实现
(Mediator)中介者模式的Java实现(加修改)
(Dynamic Proxy)动态代理模式的Java实现
(Template Method)模板方法模式的Java实现