结合配置文件、反射完善控制反转(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. package IoC_DI.use_reflect; 
  2.  
  3. import java.lang.reflect.Method; 
  4.  
  5. public class BeanUtil { 
  6.  
  7.     /** 
  8.      * 利用反射进行依赖注入 
  9.      * @param bean 需要注入外部依赖的主体类实例 
  10.      * @param fieldName 需要注入的字段名 
  11.      * @param fieldRef 被注入的组件实例 
  12.      * @throws Exception 
  13.      */ 
  14.     public static void setProperty(Object bean, String fieldName, 
  15.             Object fieldRef) throws Exception { 
  16.  
  17.         // 获取主体类的完整名称 
  18.         String className = getClassName(bean); 
  19.  
  20.         // 获取主体类的所有 Method 
  21.         Class beanClass = Class.forName(className); 
  22.         Method[] methods = beanClass.getMethods(); 
  23.  
  24.         // 准备对应 setter()方法的完整名称 
  25.         String setterName = "set" + fieldName.substring(01).toUpperCase() 
  26.                 + fieldName.substring(1, fieldName.length()); 
  27.  
  28.         // 遍历找到对应 setter 方法,并调用 invoke()方法进行注入 
  29.         for (Method m : methods) { 
  30.             if (m.getName().equals(setterName)) { 
  31.                 m.invoke(bean, fieldRef); 
  32.                 System.out.println("已调用 " + m.getName() + "() 向 " + className 
  33.                         + " 注入 " + getClassName(fieldRef)); 
  34.                 return
  35.             } 
  36.         } 
  37.         System.out.println(">>注入失败: " + className + "类中不存在" + fieldName 
  38.                 + "字段对应的setter()方法 ..."); 
  39.     } 
  40.  
  41.     /** 
  42.      * 根据 Object 实例获取类的完整名称 
  43.      * @param o 
  44.      * @return 
  45.      */ 
  46.     private static String getClassName(Object o) { 
  47.         if (o == null) { 
  48.             System.out.println("传入的 Object 实例为 null ..."); 
  49.             return null
  50.         } 
  51.         String fullName = o.toString(); 
  52.         String className = fullName.substring(0, fullName.indexOf("@")); 
  53.         return className; 
  54.     } 

 

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

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

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

 

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

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

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

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来操作 .propertie配置文件的。而我,最初也是按照其建议使用这个包的,可是运行时总是抛出NoSuchMethodException 异常Property 'reportGenerator' has no setter method in class 'class IoC_DI.use_reflect.ReportService'Eclipse自动生成的setter未能解决该问题,自己查看commons-beanutils 包对应类的源代码也没无果。猜测问题可能出在commons-beanutils 包对应类好像使用了麻烦的描述符来查找 setter 方法。最后还是自己实现一下更加轻快:-D

 


本文转自 xxxx66yyyy 51CTO博客,原文链接:http://blog.51cto.com/haolloyin/463673,如需转载请自行联系原作者
相关文章
|
2月前
|
XML Java 数据格式
从六个方面读懂IoC(控制反转)和DI(依赖注入)
在一开始学习 Spring 的时候,我们就接触 IoC 了,作为 Spring 第一个最核心的概念,我们在解读它源码之前一定需要对其有深入的认识,对于初学Spring的人来说,总觉得IOC是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring IOC的理解。
24 2
|
4月前
|
XML Java 数据格式
深入理解 Spring IoC 和 DI:掌握控制反转和依赖注入的精髓
在本文中,我们将介绍 IoC(控制反转)和 DI(依赖注入)的概念,以及如何在 Spring 框架中实现它们。
67 0
|
29天前
|
容器
02_IOC控制反转 DI依赖注入
02_IOC控制反转 DI依赖注入
27 0
|
8月前
|
XML Java 测试技术
springIOC(控制反转)和DI(依赖注入)
springIOC(控制反转)和DI(依赖注入)
56 0
|
5月前
|
Java 容器 Spring
[javaweb]——spring框架之控制反转(IOC)与依赖注入(DI)
[javaweb]——spring框架之控制反转(IOC)与依赖注入(DI)
|
9月前
|
前端开发 JavaScript Java
浅谈Spring的相关概念性问题 IOC DI AOP 工厂模式 单例
浅谈Spring的相关概念性问题 IOC DI AOP 工厂模式 单例
106 0
|
XML 设计模式 Java
简单理解什么是Spring中的IOC控制反转和DI依赖注入,Spring对象的三种创建方式
简单理解什么是Spring中的IOC控制反转和DI依赖注入,Spring对象的三种创建方式
128 0
简单理解什么是Spring中的IOC控制反转和DI依赖注入,Spring对象的三种创建方式
|
XML 设计模式 前端开发
IOC控制反转 + DI依赖注入
一种思想,两种实现方式 IOC (Inversion of Control):控制反转,是一种概念和思想,指由Spring容器完成对象创建和依赖注入 核心业务:(a)对象的创建 (b)依赖的注入 2种实现方式 基于xml实现IOC 基于注解实现IOC 基于xml的IOC在前3篇Spring博客中简单探讨过了,后面将探讨基于注解的IOC
|
XML Java 程序员
Spring的艺术(二):控制反转(IOC)和依赖注入(DI)的完美实现
IOC叫做控制反转,从本质上讲,IOC就是把原本由程序员创建的对象的这个动作交给Spring去实现,程序员无需再去管理对象的创建,这种方式可以大大减少系统的偶尔性。 没有IOC之前,对象的创建和对象间的依赖关系都完全编码在程序中,使用IOC之后,对象的创建由程序自己控制,使得程序解耦合。 IOC并不是一种技术,他是一种思想,即控制权反转的思想,DI(依赖注入)则是Spring实现IOC的方法。 Spring容器在初始化时根据配置文件或元数据创建和组织对象存入容器中,需要使用时再从IOC容器中获取。