加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

结合配置文件、反射完善控制反转(IoC)、依赖注入(DI)

发布时间:2020-12-13 22:45:24 所属栏目:百科 来源:网络整理
导读:结合配置文件、反射完善控制反转(IoC)、依赖注入(DI)http://www.jb51.cc/article/p-xqlccmci-bs.html 接前面 2 篇“ 演进式例解控制反转( IoC )、依赖注入( DI ) 之一 ”和“ 演进式例解控制反转( IoC )、依赖注入( DI ) 之二 ”的例子继续往下

结合配置文件、反射完善控制反转(IoC)、依赖注入(DI)http://www.52php.cn/article/p-xqlccmci-bs.html


接前面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反射、注入工具类代码如下,请详看注释:

 
 
  1. packageIoC_DI.use_reflect;
  2. importjava.lang.reflect.Method;
  3. publicclassBeanUtil{
  4. /**
  5. *利用反射进行依赖注入
  6. *@parambean需要注入外部依赖的主体类实例
  7. *@paramfieldName需要注入的字段名
  8. *@paramfieldRef被注入的组件实例
  9. *@throwsException
  10. */
  11. publicstaticvoidsetProperty(Objectbean,StringfieldName,
  12. ObjectfieldRef)throwsException{
  13. //获取主体类的完整名称
  14. StringclassName=getClassName(bean);
  15. //获取主体类的所有Method
  16. ClassbeanClass=Class.forName(className);
  17. Method[]methods=beanClass.getMethods();
  18. //准备对应setter()方法的完整名称
  19. StringsetterName="set"+fieldName.substring(0,1).toUpperCase()
  20. +fieldName.substring(1,fieldName.length());
  21. //遍历找到对应setter方法,并调用invoke()方法进行注入
  22. for(Methodm:methods){
  23. if(m.getName().equals(setterName)){
  24. m.invoke(bean,fieldRef);
  25. System.out.println("已调用"+m.getName()+"()向"+className
  26. +"注入"+getClassName(fieldRef));
  27. return;
  28. }
  29. }
  30. System.out.println(">>注入失败:"+className+"类中不存在"+fieldName
  31. +"字段对应的setter()方法...");
  32. }
  33. /**
  34. *根据Object实例获取类的完整名称
  35. *@paramo
  36. *@return
  37. */
  38. privatestaticStringgetClassName(Objecto){
  39. if(o==null){
  40. System.out.println("传入的Object实例为null...");
  41. returnnull;
  42. }
  43. StringfullName=o.toString();
  44. StringclassName=fullName.substring(0,fullName.indexOf("@"));
  45. returnclassName;
  46. }
  47. }

对于原来的容器 Container 类,也需要相应的修改,主要体现在:

? Container 初始化时加载外部 .properties 配置文件,不再构造器中硬编码实例化各个组件并进行依赖注入。

? Container 加载 .properties 配置文件之后自己解析该文件内容,即遍历其中的所有键-值条目,决定如何处理组件定义、依赖注入。

在这个例子中,我将配置文件命名为bean_config.properties 其内容即为前面给出的那样。

修改后的 Container.java 详细代码如下:

 
 
  1. classContainer{
  2. //以键-值对形式保存各种所需组件Bean
  3. privatestaticMap<String,Object>beans;
  4. publicContainer(){
  5. System.out.println("1...开始初始化Container...");
  6. beans=newHashMap<String,Object>();
  7. try{
  8. Propertiesprops=newProperties();
  9. props.load(newFileInputStream("bean_config.properties"));
  10. for(Map.Entryentry:props.entrySet()){
  11. Stringkey=(String)entry.getKey();
  12. Stringvalue=(String)entry.getValue();
  13. //处理key-value,进行依赖属性的注入
  14. this.handleEntry(key,value);
  15. }
  16. }catch(Exceptione){
  17. e.printStackTrace();
  18. }
  19. ////创建、保存具体的报表生起器
  20. //ReportGeneratorreportGenerator=newPDFGenerator();
  21. //beans.put("reportGenerator",reportGenerator);
  22. //
  23. ////获取、管理ReportService的引用
  24. //ReportServicereportService=newReportService();
  25. ////注入上面已创建的具体ReportGenerator实例
  26. //reportService.setReportGenerator(reportGenerator);
  27. //beans.put("reportService",reportService);
  28. System.out.println("5...结束初始化Container...");
  29. }
  30. /**
  31. *根据key-value处理配置文件,从中获取bean及其依赖属性并注入
  32. *@paramkey
  33. *@paramvalue
  34. *@throwsException
  35. */
  36. privatevoidhandleEntry(Stringkey,Stringvalue)throwsException{
  37. String[]keyParts=key.split(".");
  38. if(keyParts.length==1){
  39. //组件定义:利用反射实例化该组件
  40. Objectbean=Class.forName(value).newInstance();
  41. beans.put(keyParts[0],bean);
  42. }else{
  43. //依赖注入:获取需要bean的主体,以及被注入的实例
  44. Objectbean=beans.get(keyParts[0]);
  45. ObjectfiledRef=beans.get(value);
  46. BeanUtil.setProperty(bean,keyParts[1],filedRef);
  47. }
  48. }
  49. publicstaticObjectgetBean(Stringid){
  50. System.out.println("最后获取服务组件...getBean()-->"+id+"...");
  51. returnbeans.get(id);
  52. }
  53. }
根据以上具体配置文件,运行得到结果如下:

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读