回顾:

在上一篇文章“演进式例解AOP:Java 动态代理”中用一个打印报表的例子很简单地温习了一下 Java 中的动态代理实现,其实最终目的如标题,即利用动态代理结合之前写的关于控制反转(IoC)容器、依赖注入(DI)、读外部配置文件,来集中式地、简单地模拟一下Spring中所具有的IoCDIAOP功能

前面相关的文章有:其一:引入容器,Service Locator其二:引入IoC,DI 其三:结合配置文件完善DI。以下仍旧是用“演进式例解AOP:Java 动态代理”中的报表生成的需求来进行模拟实现。

 

问题描述:

与前面相同,即:开发一个能够根据本地文件路径、远程文件路径生成HTMLPDF格式的报表的应用。由于不同操作系统下的文件路径有不同的路径分隔符,因此这里存在一个特殊要求:接收到文件路径生成报表之前必须验证该文件路径的合法性这里假设该应用系统可以根据需要决定是否在报表生成【之前之后】进行日志记录(这里强调了beforeafter 是为了配合AOP的前置通知、后置通知进行模拟,而验证功能一般只是前置)。

注意这里的特殊要求是:要通过外部配置文件更加灵活地决定日志记录的使用与否,这种额外添加功能的特点是AOP横切关注点灵活的体现。而前面的动态代理只是硬编码到具体实现当中去了。

 

解决方案:

既然已经讲明需要读取外部的配置文件了,那么就和之前的“结合配置文件完善DI”类似,通过一个 BeanUtil 工具类来负责根据配置文件中的组件声明、组件间关系来分别创建对象或执行依赖注入动作

问题在于:日志记录这种额外功能(Crosscutting Concern,即横切关注点)如何可选择性地添加到具体的应用系统当中去。

注:关于横切关注点的我也没有深入探究(毕竟能力、经验非常有限),只了解有这么一回事及其简单应用场景而已(两次读过《冒号课堂》一书又给忘了但极力推荐本书),暂时先理解为需要额外添加的功能即可(例如日志记录),以下可能会时不时用到这概念。

 

实现方法:

为了使得这种before(前置)、after(后置)添加的额外功能更加通用,这里利用Java中的OO概念来抽象出Advice(通知)这一源自AOP的重要概念。关于Advice的调用执行(即横切关注点的执行),是在 ProxyHandler 类中的invoke() 方法利用了Java的动态代理技术来实现的:当beforeAdviceafterAdvice被注入之后,则相对应地调用;若未注入Advice,则效果如普通方法调用一样。

 

另外,这里的外部配置文件依然采用 .properties 格式,即 Key-Value 形式,包含组件声明、组件间依赖注入关系,具体如下(注意其中组件名与具体实现代码相关):

  1. # define beans 
  2. Bean.target=AOP.aop_di.RemoteReportCreator 
  3. Bean.targetProxy=AOP.aop_di.ProxyHandler 
  4. Bean.logBeforeAdvice=AOP.aop_di.LogBeforeAdvice 
  5. Bean.logAfterAdvice=AOP.aop_di.LogAfterAdvice 
  6.  
  7. # define DI 
  8. DI.targetProxy.target=target 
  9. DI.targetProxy.beforeAdvice=logBeforeAdvice 
  10. DI.targetProxy.afterAdvice=logAfterAdvice 

好像下面的类图结构、具体代码实现然而让人更加容易明白,这里就不再说得更复杂难理解了。注意其中绿色背景的“AOP_DI 核心结构”是指类比Spring 框架,其他的则为框架使用者自定义(例如ReportCreator 类结构)或必须实现的子类(例如LogBeforeAdvice 子类)。对了,我还没画时序图呢,有时序图肯定会更加容易直观地理解。

 

依据具体需求和分析,设计类图框架如下:

 

完整的简单代码实现如下:

  1. /** 
  2.  * 报表生成的公共接口 
  3.  */ 
  4. interface ReportCreator { 
  5.     public void getHtmlReport(String path); 
  6.  
  7.     public void getPdfReport(String path); 
  8.  
  9. /** 
  10.  * 用于根据【本地】文件路径生成报表 
  11.  */ 
  12. class LocalReportCreator implements ReportCreator { 
  13.  
  14.     public void getHtmlReport(String path) { 
  15.         System.out.println("根据【本地】文件生成【HTML】格式的报表 ..."); 
  16.     } 
  17.  
  18.     public void getPdfReport(String path) { 
  19.         System.out.println("根据【本地】文件生成【PDF】格式的报表 ..."); 
  20.     } 
  21.  
  22. /** 
  23.  * 用于根据【远程】文件路径生成报表 
  24.  */ 
  25. class RemoteReportCreator implements ReportCreator { 
  26.  
  27.     public void getHtmlReport(String path) { 
  28.         System.out.println("根据【远程】文件生成【HTML】格式的报表 ..."); 
  29.     } 
  30.  
  31.     public void getPdfReport(String path) { 
  32.         System.out.println("根据【远程】文件生成【PDF】格式的报表 ..."); 
  33.     } 
  1. /** 
  2.  * 标识性接口,关键在于分离before、after横切关注点 
  3.  */ 
  4. interface Advice { 
  5.  
  6.  
  7. /** 
  8.  * before 横切关注点,即在原方法调用【之前】被调用 
  9.  */ 
  10. interface BeforeAdvice extends Advice { 
  11.     public void before(); 
  12.  
  13. /** 
  14.  * after 横切关注点,即在原方法调用【之后】被调用 
  15.  */ 
  16. interface AfterAdvice extends Advice { 
  17.     public void after(); 

 

  1. /** 
  2.  * 用户自定义具体实现的before横切关注点 
  3.  */ 
  4. class LogBeforeAdvice implements BeforeAdvice { 
  5.  
  6.     public void before() { 
  7.         System.out.println("原业务方法被调用【之前】先打印日志..."); 
  8.     } 
  9.  
  10. class LogAfterAdvice implements AfterAdvice { 
  11.  
  12.     public void after() { 
  13.         System.out.println("原业务方法被调用【之后】再打印日志..."); 
  14.     } 

 

  1. /** 
  2.  * 代理提供者:实现invoke方法,构造具有横切关注点的动态代理对象 
  3.  */ 
  4. class ProxyHandler implements InvocationHandler { 
  5.     private Object target; // 被代理的目标对象 
  6.     private Advice beforeAdvice;// before 横切关注点 
  7.     private Advice afterAdvice; // after 横切关注点 
  8.  
  9.     public ProxyHandler() { 
  10.         // 空构造方法 
  11.     } 
  12.  
  13.     /** 
  14.      * 3个setter方式的用于依赖注入 
  15.      */ 
  16.     public void setTarget(Object target) { 
  17.         this.target = target; 
  18.     } 
  19.  
  20.     public void setBeforeAdvice(Advice beforeAdvice) { 
  21.         this.beforeAdvice = beforeAdvice; 
  22.     } 
  23.  
  24.     public void setAfterAdvice(Advice afterAdvice) { 
  25.         this.afterAdvice = afterAdvice; 
  26.     } 
  27.  
  28.     /** 
  29.      * 实现invoke()方法,添加before、after关注点以实现AOP 
  30.      */ 
  31.     public Object invoke(Object proxy, Method method, Object[] args) 
  32.             throws Throwable { 
  33.  
  34.         // 在被代理对象业务方法前后添加横切关注点方法 
  35.         this.aspect(this.beforeAdvice, "before"); 
  36.         Object result = method.invoke(this.target, args); 
  37.         this.aspect(this.afterAdvice, "after"); 
  38.  
  39.         return result; 
  40.     } 
  41.  
  42.     /** 
  43.      * 依据是否注入横切关注点来决定before、after的调用 
  44.      */ 
  45.     private void aspect(Advice advice, String aspectName) throws Exception { 
  46.         if (advice != null) { 
  47.             Class c = advice.getClass(); 
  48.             Method[] methods = c.getMethods(); 
  49.             for (Method m : methods) { 
  50.                 if (aspectName.equals(m.getName())) { 
  51.                     // 以null参数调用已实现的before、after方法 
  52.                     methods[0].invoke(advice, null); 
  53.                 } 
  54.             } 
  55.         } 
  56.     } 
  57.  
  58.     /** 
  59.      * 静态工厂方法,用于获取已注入before、after的动态代理实例 
  60.      */ 
  61.     public Object getProxy(Object target) { 
  62.         Class targetClass = target.getClass(); 
  63.         /* 
  64.          * loader: 目标类的类加载器 interfaces: 目标类已实现的接口 handler: 
  65.          * 转发方法调用的调用处理类实例,这里是当前Handler 
  66.          */ 
  67.         ClassLoader loader = targetClass.getClassLoader(); 
  68.         Class[] interfaces = targetClass.getInterfaces(); 
  69.  
  70.         // 创建并返回动态代理类实例 
  71.         return Proxy.newProxyInstance(loader, interfaces, this); 
  72.     } 

 

  1. /** 
  2.  * 工厂类:从外部读取配置信息创建并注入所需对象 
  3.  */ 
  4. class ProxyFactory { 
  5.  
  6.     // 代理对象的提供者 
  7.     private ProxyHandler handler; 
  8.     // 被代理对象 
  9.     private Object target; 
  10.     // 所有组件类的集合 
  11.     private Map<String, Object> beans; 
  12.  
  13.     /** 
  14.      * 读取外部配置文件初始化所有组件间的关系 
  15.      */ 
  16.     public ProxyFactory(String configFile) { 
  17.         beans = new HashMap<String, Object>(); 
  18.  
  19.         try { 
  20.             Properties props = new Properties(); 
  21.             props.load(new FileInputStream(configFile)); 
  22.  
  23.             // bean 声明及依赖注入的 key-value 对 
  24.             Map<String, String> beanKV = new HashMap<String, String>(); 
  25.             Map<String, String> diKV = new HashMap<String, String>(); 
  26.  
  27.             for (Map.Entry entry : props.entrySet()) { 
  28.                 String key = (String) entry.getKey(); 
  29.                 String value = (String) entry.getValue(); 
  30.  
  31.                 if (key.startsWith("Bean")) { 
  32.                     beanKV.put(key, value); 
  33.                 } else if (key.startsWith("DI")) { 
  34.                     diKV.put(key, value); 
  35.                 } 
  36.             } 
  37.              
  38.             // 一定要先处理 bean 声明再进行依赖注入 
  39.             this.processKeyValue(beanKV); 
  40.             this.processKeyValue(diKV); 
  41.  
  42.         } catch (Exception e) { 
  43.             e.printStackTrace(); 
  44.         } 
  45.     } 
  46.  
  47.     /** 
  48.      *  处理 key-value  
  49.      */ 
  50.     private void processKeyValue(Map<String, String> map) throws Exception { 
  51.         for (Map.Entry entry : map.entrySet()) { 
  52.             String key = (String) entry.getKey(); 
  53.             String value = (String) entry.getValue(); 
  54.             this.handleEntry(key, value); 
  55.         } 
  56.     } 
  57.  
  58.     /** 
  59.      * 利用工具类 BeanUtil 处理key-value对,即创建或注入bean 
  60.      */ 
  61.     private void handleEntry(String key, String value) throws Exception { 
  62.         String[] keyParts = key.split("\\."); 
  63.  
  64.         String tag = keyParts[0]; 
  65.         if ("Bean".equals(tag)) { 
  66.             // 组件定义:利用反射实例化该组件 
  67.             Object bean = Class.forName(value).newInstance(); 
  68.             System.out.println("组件定义:" + bean.getClass().getName()); 
  69.             beans.put(keyParts[1], bean); 
  70.  
  71.         } else if ("DI".equals(tag)) { 
  72.             // 依赖注入:获取需要bean的主体,以及被注入的实例 
  73.             Object bean = beans.get(keyParts[1]); 
  74.             Object fieldRef = beans.get(value); 
  75.             System.out.println("依赖注入:" + bean.getClass().getName() +  
  76.                     "." + fieldRef.getClass().getName()); 
  77.             BeanUtil.setProperty(bean, keyParts[2], fieldRef); 
  78.         } 
  79.     } 
  80.  
  81.     /** 
  82.      *  针对Factory已创建的Target获取代理对象 
  83.      */ 
  84.     public Object getProxy(String proxyName, String targetNanme) { 
  85.         Object target = this.beans.get(targetNanme); 
  86.         if (target != null) { 
  87.             this.handler = (ProxyHandler) this.beans.get(proxyName); 
  88.             return this.handler.getProxy(target); 
  89.         } 
  90.         return null
  91.     } 
  92.  

 

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

 

  1. // 测试 
  2. public class AOP_DI { 
  3.     public static void main(String[] args) { 
  4.         // 初始化环境配置 
  5.         ProxyFactory proxyFactory = new ProxyFactory("config.properties"); 
  6.         // 获取被代理后的Target对象 
  7.         ReportCreator reportCreator = (ReportCreator) proxyFactory 
  8.                 .getProxy("targetProxy""target"); 
  9.         // 使用被代理后的target对象提供的服务 
  10.         reportCreator.getHtmlReport("http://code.google.com/file/..."); 
  11.     } 

 

根据最前面的配置文件信息,运行可得以下结果:

  1. 组件定义:AOP.aop_di.ProxyHandler 
  2. 组件定义:AOP.aop_di.LogBeforeAdvice 
  3. 组件定义:AOP.aop_di.LogAfterAdvice 
  4. 组件定义:AOP.aop_di.RemoteReportCreator 
  5. 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.LogBeforeAdvice 
  6. 已调用 setBeforeAdvice() 向 AOP.aop_di.ProxyHandler 注入 AOP.aop_di.LogBeforeAdvice 
  7. 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.LogAfterAdvice 
  8. 已调用 setAfterAdvice() 向 AOP.aop_di.ProxyHandler 注入 AOP.aop_di.LogAfterAdvice 
  9. 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.RemoteReportCreator 
  10. 已调用 setTarget() 向 AOP.aop_di.ProxyHandler 注入 AOP.aop_di.RemoteReportCreator 
  11. 原业务方法被调用【之前】先打印日志... 
  12. 根据【远程】文件生成【HTML】格式的报表 ... 
  13. 原业务方法被调用【之后】再打印日志... 

修改配置文件,即去掉其中的LogAfterAdvice的注入,如下:

  1. # define beans 
  2. Bean.target=AOP.aop_di.RemoteReportCreator 
  3. Bean.targetProxy=AOP.aop_di.ProxyHandler 
  4. Bean.logBeforeAdvice=AOP.aop_di.LogBeforeAdvice 
  5.  
  6. # define DI 
  7. DI.targetProxy.target=target 
  8. DI.targetProxy.beforeAdvice=logBeforeAdvice 

运行结果如下:

  1. 组件定义:AOP.aop_di.ProxyHandler 
  2. 组件定义:AOP.aop_di.LogBeforeAdvice 
  3. 组件定义:AOP.aop_di.RemoteReportCreator 
  4. 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.LogBeforeAdvice 
  5. 已调用 setBeforeAdvice() 向 AOP.aop_di.ProxyHandler 注入 AOP.aop_di.LogBeforeAdvice 
  6. 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.RemoteReportCreator 
  7. 已调用 setTarget() 向 AOP.aop_di.ProxyHandler 注入 AOP.aop_di.RemoteReportCreator 
  8. 原业务方法被调用【之前】先打印日志... 
  9. 根据【远程】文件生成【HTML】格式的报表 ... 

小结:

似乎勉强达到了前面我所说的目标:利用动态代理结合之前写的关于控制反转(IoC)容器、依赖注入(DI)、读外部配置文件,来集中式地、简单地模拟一下Spring中所具有的IoCDIAOP功能。

其实问题存在不少,而且在我实现代码时也遇到一些问题,但现在篇幅已经挺长的了(不知道有没谁会坚持看完?哈),可能会另外写一篇总结来记录一些我记忆较深刻的方面,我觉得其中更重要的是:写这几篇文章的意图是啥呢?从哪里得到思路的?:-D

 

瞧瞧以下您感兴趣的、相关的内容 ^_^

演进式例解AOPJava 动态代理

演进式例解控制反转(IoC)、依赖注入(DI

演进式例解控制反转(IoC)、依赖注入(DI之二

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

 

装饰模式(Decorator)与动态代理的强强联合 

Dynamic Proxy)动态代理模式的Java实现

Factory Method)工厂方法模式的Java实现

Java RMI 框架的工厂方法模式实现

Mediator)中介者模式的Java实现(加修改)