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

回顾:

上一篇文章演进式例解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. #definebeans
  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. #defineDI
  7. DI.targetProxy.target=target
  8. DI.targetProxy.beforeAdvice=logBeforeAdvice
  9. DI.targetProxy.afteradvice=logAfteradvice

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

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

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

 
 
  1. /**
  2. *报表生成的公共接口
  3. */
  4. interfaceReportCreator{
  5. publicvoidgetHtmlReport(Stringpath);
  6. publicvoidgetPdfReport(Stringpath);
  7. }
  8. /**
  9. *用于根据【本地】文件路径生成报表
  10. */
  11. classLocalReportCreatorimplementsReportCreator{
  12. publicvoidgetHtmlReport(Stringpath){
  13. System.out.println("根据【本地】文件生成【HTML】格式的报表...");
  14. }
  15. publicvoidgetPdfReport(Stringpath){
  16. System.out.println("根据【本地】文件生成【PDF】格式的报表...");
  17. }
  18. }
  19. /**
  20. *用于根据【远程】文件路径生成报表
  21. */
  22. classRemoteReportCreatorimplementsReportCreator{
  23. publicvoidgetHtmlReport(Stringpath){
  24. System.out.println("根据【远程】文件生成【HTML】格式的报表...");
  25. }
  26. publicvoidgetPdfReport(Stringpath){
  27. System.out.println("根据【远程】文件生成【PDF】格式的报表...");
  28. }
  29. }
 
 
  1. /**
  2. *标识性接口,关键在于分离before、after横切关注点
  3. */
  4. interfaceAdvice{
  5. }
  6. /**
  7. *before横切关注点,即在原方法调用【之前】被调用
  8. */
  9. interfaceBeforeAdviceextendsAdvice{
  10. publicvoidbefore();
  11. }
  12. /**
  13. *after横切关注点,即在原方法调用【之后】被调用
  14. */
  15. interfaceAfteradviceextendsAdvice{
  16. publicvoidafter();
  17. }

 
 
  1. /**
  2. *用户自定义具体实现的before横切关注点
  3. */
  4. classLogBeforeAdviceimplementsBeforeAdvice{
  5. publicvoidbefore(){
  6. System.out.println("原业务方法调用【之前】先打印日志...");
  7. }
  8. }
  9. classLogAfteradviceimplementsAfteradvice{
  10. publicvoidafter(){
  11. System.out.println("原业务方法调用【之后】再打印日志...");
  12. }
  13. }

 
 
  1. /**
  2. *代理提供者:实现invoke方法,构造具有横切关注点的动态代理对象
  3. */
  4. classProxyHandlerimplementsInvocationHandler{
  5. privateObjecttarget;//被代理的目标对象
  6. privateAdvicebeforeAdvice;//before横切关注点
  7. privateAdviceafteradvice;//after横切关注点
  8. publicProxyHandler(){
  9. //空构造方法
  10. }
  11. /**
  12. *3个setter方式的用于依赖注入
  13. */
  14. publicvoidsetTarget(Objecttarget){
  15. this.target=target;
  16. }
  17. publicvoidsetBeforeAdvice(AdvicebeforeAdvice){
  18. this.beforeAdvice=beforeAdvice;
  19. }
  20. publicvoidsetAfteradvice(Adviceafteradvice){
  21. this.afteradvice=afteradvice;
  22. }
  23. /**
  24. *实现invoke()方法添加before、after关注点以实现AOP
  25. */
  26. publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
  27. throwsThrowable{
  28. //在被代理对象业务方法前后添加横切关注点方法
  29. this.aspect(this.beforeAdvice,"before");
  30. Objectresult=method.invoke(this.target,args);
  31. this.aspect(this.afteradvice,"after");
  32. returnresult;
  33. }
  34. /**
  35. *依据是否注入横切关注点来决定before、after的调用
  36. */
  37. privatevoidaspect(Adviceadvice,StringaspectName)throwsException{
  38. if(advice!=null){
  39. Classc=advice.getClass();
  40. Method[]methods=c.getmethods();
  41. for(Methodm:methods){
  42. if(aspectName.equals(m.getName())){
  43. //以null参数调用已实现的before、after方法
  44. methods[0].invoke(advice,null);
  45. }
  46. }
  47. }
  48. }
  49. /**
  50. *静态工厂方法,用于获取已注入before、after的动态代理实例
  51. */
  52. publicObjectgetProxy(Objecttarget){
  53. ClasstargetClass=target.getClass();
  54. /*
  55. *loader:目标类的类加载器interfaces:目标类已实现的接口handler:
  56. *转发方法调用调用处理类实例,这里是当前Handler
  57. */
  58. ClassLoaderloader=targetClass.getClassLoader();
  59. Class[]interfaces=targetClass.getInterfaces();
  60. //创建并返回动态代理类实例
  61. returnProxy.newProxyInstance(loader,interfaces,this);
  62. }
  63. }

 
 
  1. /**
  2. *工厂类:从外部读取配置信息创建并注入所需对象
  3. */
  4. classProxyFactory{
  5. //代理对象的提供者
  6. privateProxyHandlerhandler;
  7. //被代理对象
  8. privateObjecttarget;
  9. //所有组件类的集合
  10. privateMap<String,Object>beans;
  11. /**
  12. *读取外部配置文件初始化所有组件间的关系
  13. */
  14. publicProxyFactory(StringconfigFile){
  15. beans=newHashMap<String,Object>();
  16. try{
  17. Propertiesprops=newProperties();
  18. props.load(newFileInputStream(configFile));
  19. //bean声明及依赖注入的key-value对
  20. Map<String,String>beanKV=newHashMap<String,String>();
  21. Map<String,String>diKV=newHashMap<String,String>();
  22. for(Map.Entryentry:props.entrySet()){
  23. Stringkey=(String)entry.getKey();
  24. Stringvalue=(String)entry.getValue();
  25. if(key.startsWith("Bean")){
  26. beanKV.put(key,value);
  27. }elseif(key.startsWith("DI")){
  28. diKV.put(key,value);
  29. }
  30. }
  31. //一定要先处理bean声明再进行依赖注入
  32. this.processkeyvalue(beanKV);
  33. this.processkeyvalue(diKV);
  34. }catch(Exceptione){
  35. e.printstacktrace();
  36. }
  37. }
  38. /**
  39. *处理key-value
  40. */
  41. privatevoidprocesskeyvalue(Map<String,String>map)throwsException{
  42. for(Map.Entryentry:map.entrySet()){
  43. Stringkey=(String)entry.getKey();
  44. Stringvalue=(String)entry.getValue();
  45. this.handleEntry(key,value);
  46. }
  47. }
  48. /**
  49. *利用工具类BeanUtil处理key-value对,即创建或注入bean
  50. */
  51. privatevoidhandleEntry(Stringkey,Stringvalue)throwsException{
  52. String[]keyParts=key.split("\\.");
  53. Stringtag=keyParts[0];
  54. if("Bean".equals(tag)){
  55. //组件定义:利用反射实例化该组件
  56. Objectbean=Class.forName(value).newInstance();
  57. System.out.println("组件定义:"+bean.getClass().getName());
  58. beans.put(keyParts[1],bean);
  59. }elseif("DI".equals(tag)){
  60. //依赖注入:获取需要bean的主体,以及被注入的实例
  61. Objectbean=beans.get(keyParts[1]);
  62. ObjectfieldRef=beans.get(value);
  63. System.out.println("依赖注入:"+bean.getClass().getName()+
  64. "."+fieldRef.getClass().getName());
  65. BeanUtil.setProperty(bean,keyParts[2],fieldRef);
  66. }
  67. }
  68. /**
  69. *针对Factory已创建的Target获取代理对象
  70. */
  71. publicObjectgetProxy(StringproxyName,StringtargetNanme){
  72. Objecttarget=this.beans.get(targetNanme);
  73. if(target!=null){
  74. this.handler=(ProxyHandler)this.beans.get(proxyName);
  75. returnthis.handler.getProxy(target);
  76. }
  77. returnnull;
  78. }
  79. }

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

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

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

 
 
  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. #definebeans
  2. Bean.target=AOP.aop_di.RemoteReportCreator
  3. Bean.targetProxy=AOP.aop_di.ProxyHandler
  4. Bean.logBeforeAdvice=AOP.aop_di.LogBeforeAdvice
  5. #defineDI
  6. DI.targetProxy.target=target
  7. 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实现(加修改)

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...