结合依赖注入与AOP简单模拟实现Spring框架

简介:

问题描述:

与前面相同,即:开发一个能够根据本地文件路径、远程文件路径生成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



本文转自 xxxx66yyyy 51CTO博客,原文链接:http://blog.51cto.com/haolloyin/476026,如需转载请自行联系原作者

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
12天前
|
存储 安全 Java
事件的力量:探索Spring框架中的事件处理机制
事件的力量:探索Spring框架中的事件处理机制
26 0
|
22天前
|
缓存 Java Spring
Spring 框架中 Bean 的生命周期
Spring 框架中 Bean 的生命周期
30 1
|
1月前
|
开发框架 安全 Java
Spring 框架:企业级应用开发的强大工具
在当今数字化时代,企业级应用开发的需求日益增长。为了满足这一需求,开发者们需要一款功能强大、易于使用的开发框架。Spring 框架作为 Java 领域的领先者,为企业级应用开发提供了全面的解决方案。本文将深入探讨 Spring 框架的各个方面,包括其历史、核心模块、优势以及应用场景。
24 0
|
1月前
|
监控 Java 开发者
Spring AOP动态代理
Spring AOP动态代理
43 1
|
1月前
|
Java Spring 容器
Spring的AOP失效场景详解
Spring的AOP失效场景详解
99 0
|
1月前
|
人工智能 JSON 前端开发
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
|
27天前
|
设计模式 Java Maven
Spring Aop 底层责任链思路实现-springaopdi-ceng-ze-ren-lian-si-lu-shi-xian
Spring Aop 底层责任链思路实现-springaopdi-ceng-ze-ren-lian-si-lu-shi-xian
33 1
|
20天前
|
XML Java Maven
Spring之Aop的注解使用
Spring之Aop的注解使用
|
20天前
|
开发框架 安全 Java
探索 Spring 框架:企业级应用开发的强大工具
探索 Spring 框架:企业级应用开发的强大工具
19 1
|
26天前
|
Java Spring
Spring 如何实现 AOP
Spring 如何实现 AOP
17 0

热门文章

最新文章