回顾:
在上一篇文章“演进式例解AOP:Java 动态代理”中用一个打印报表的例子很简单地温习了一下 Java 中的动态代理实现,其实最终目的如标题,即利用动态代理结合之前写的关于控制反转(IoC)容器、依赖注入(DI)、读外部配置文件,来集中式地、简单地模拟一下Spring中所具有的IoC、DI、AOP功能。
前面相关的文章有:其一:引入容器,Service Locator、其二:引入IoC,DI 、其三:结合配置文件完善DI。以下仍旧是用“演进式例解AOP:Java 动态代理”中的报表生成的需求来进行模拟实现。
问题描述:
与前面相同,即:开发一个能够根据本地文件路径、远程文件路径生成HTML、PDF格式的报表的应用。由于不同操作系统下的文件路径有不同的路径分隔符,因此这里存在一个特殊要求:接收到文件路径生成报表之前必须验证该文件路径的合法性。这里假设该应用系统可以根据需要决定是否在报表生成【之前、之后】进行日志记录(这里强调了before、after 是为了配合AOP的前置通知、后置通知进行模拟,而验证功能一般只是前置)。
注意这里的特殊要求是:要通过外部配置文件更加灵活地决定日志记录的使用与否,这种额外添加功能的特点是AOP横切关注点灵活的体现。而前面的动态代理只是硬编码到具体实现当中去了。
解决方案:
既然已经讲明需要读取外部的配置文件了,那么就和之前的“结合配置文件完善DI”类似,通过一个 BeanUtil 工具类来负责根据配置文件中的组件声明、组件间关系来分别创建对象或执行依赖注入动作。
问题在于:日志记录这种额外功能(Crosscutting Concern,即横切关注点)如何可选择性地添加到具体的应用系统当中去。
注:关于横切关注点的我也没有深入探究(毕竟能力、经验非常有限),只了解有这么一回事及其简单应用场景而已(两次读过《冒号课堂》一书又给忘了…但极力推荐本书),暂时先理解为需要额外添加的功能即可(例如日志记录),以下可能会时不时用到这概念。
实现方法:
为了使得这种before(前置)、after(后置)添加的额外功能更加通用,这里利用Java中的OO概念来抽象出Advice(通知)这一源自AOP的重要概念。关于Advice的调用执行(即横切关注点的执行),是在 ProxyHandler 类中的invoke() 方法利用了Java的动态代理技术来实现的:当beforeAdvice或afterAdvice被注入之后,则相对应地调用;若未注入Advice,则效果如普通方法调用一样。
另外,这里的外部配置文件依然采用 .properties 格式,即 Key-Value 形式,包含组件声明、组件间依赖注入关系,具体如下(注意其中组件名与具体实现代码相关):
- #definebeans
- Bean.target=AOP.aop_di.RemoteReportCreator
- Bean.targetProxy=AOP.aop_di.ProxyHandler
- Bean.logBeforeAdvice=AOP.aop_di.LogBeforeAdvice
- Bean.logAfterAdvice=AOP.aop_di.LogAfterAdvice
- #defineDI
- DI.targetProxy.target=target
- DI.targetProxy.beforeAdvice=logBeforeAdvice
- DI.targetProxy.afterAdvice=logAfterAdvice
好像下面的类图结构、具体代码实现然而让人更加容易明白,这里就不再说得更复杂难理解了。注意其中绿色背景的“AOP_DI 核心结构”是指类比Spring 框架,其他的则为框架使用者自定义(例如ReportCreator 类结构)或必须实现的子类(例如LogBeforeAdvice 子类)。对了,我还没画时序图呢,有时序图肯定会更加容易直观地理解。
依据具体需求和分析,设计类图框架如下:

完整的简单代码实现如下:
- interfaceReportCreator{
- publicvoidgetHtmlReport(Stringpath);
- publicvoidgetPdfReport(Stringpath);
- }
- classLocalReportCreatorimplementsReportCreator{
- publicvoidgetHtmlReport(Stringpath){
- System.out.println("根据【本地】文件生成【HTML】格式的报表...");
- }
- publicvoidgetPdfReport(Stringpath){
- System.out.println("根据【本地】文件生成【PDF】格式的报表...");
- }
- }
- classRemoteReportCreatorimplementsReportCreator{
- publicvoidgetHtmlReport(Stringpath){
- System.out.println("根据【远程】文件生成【HTML】格式的报表...");
- }
- publicvoidgetPdfReport(Stringpath){
- System.out.println("根据【远程】文件生成【PDF】格式的报表...");
- }
- }
- interfaceAdvice{
- }
- interfaceBeforeAdviceextendsAdvice{
- publicvoidbefore();
- }
- interfaceAfterAdviceextendsAdvice{
- publicvoidafter();
- }
- classLogBeforeAdviceimplementsBeforeAdvice{
- publicvoidbefore(){
- System.out.println("原业务方法被调用【之前】先打印日志...");
- }
- }
- classLogAfterAdviceimplementsAfterAdvice{
- publicvoidafter(){
- System.out.println("原业务方法被调用【之后】再打印日志...");
- }
- }
- classProxyHandlerimplementsInvocationHandler{
- privateObjecttarget;
- privateAdvicebeforeAdvice;
- privateAdviceafterAdvice;
- publicProxyHandler(){
- }
- publicvoidsetTarget(Objecttarget){
- this.target=target;
- }
- publicvoidsetBeforeAdvice(AdvicebeforeAdvice){
- this.beforeAdvice=beforeAdvice;
- }
- publicvoidsetAfterAdvice(AdviceafterAdvice){
- this.afterAdvice=afterAdvice;
- }
- publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
- throwsThrowable{
- this.aspect(this.beforeAdvice,"before");
- Objectresult=method.invoke(this.target,args);
- this.aspect(this.afterAdvice,"after");
- returnresult;
- }
- privatevoidaspect(Adviceadvice,StringaspectName)throwsException{
- if(advice!=null){
- Classc=advice.getClass();
- Method[]methods=c.getMethods();
- for(Methodm:methods){
- if(aspectName.equals(m.getName())){
- methods[0].invoke(advice,null);
- }
- }
- }
- }
- publicObjectgetProxy(Objecttarget){
- ClasstargetClass=target.getClass();
- ClassLoaderloader=targetClass.getClassLoader();
- Class[]interfaces=targetClass.getInterfaces();
- returnProxy.newProxyInstance(loader,interfaces,this);
- }
- }
- classProxyFactory{
- privateProxyHandlerhandler;
- privateObjecttarget;
- privateMap<String,Object>beans;
- publicProxyFactory(StringconfigFile){
- beans=newHashMap<String,Object>();
- try{
- Propertiesprops=newProperties();
- props.load(newFileInputStream(configFile));
- Map<String,String>beanKV=newHashMap<String,String>();
- Map<String,String>diKV=newHashMap<String,String>();
- for(Map.Entryentry:props.entrySet()){
- Stringkey=(String)entry.getKey();
- Stringvalue=(String)entry.getValue();
- if(key.startsWith("Bean")){
- beanKV.put(key,value);
- }elseif(key.startsWith("DI")){
- diKV.put(key,value);
- }
- }
- this.processKeyValue(beanKV);
- this.processKeyValue(diKV);
- }catch(Exceptione){
- e.printStackTrace();
- }
- }
- privatevoidprocessKeyValue(Map<String,String>map)throwsException{
- for(Map.Entryentry:map.entrySet()){
- Stringkey=(String)entry.getKey();
- Stringvalue=(String)entry.getValue();
- this.handleEntry(key,value);
- }
- }
- privatevoidhandleEntry(Stringkey,Stringvalue)throwsException{
- String[]keyParts=key.split(".");
- Stringtag=keyParts[0];
- if("Bean".equals(tag)){
- Objectbean=Class.forName(value).newInstance();
- System.out.println("组件定义:"+bean.getClass().getName());
- beans.put(keyParts[1],bean);
- }elseif("DI".equals(tag)){
- Objectbean=beans.get(keyParts[1]);
- ObjectfieldRef=beans.get(value);
- System.out.println("依赖注入:"+bean.getClass().getName()+
- "."+fieldRef.getClass().getName());
- BeanUtil.setProperty(bean,keyParts[2],fieldRef);
- }
- }
- publicObjectgetProxy(StringproxyName,StringtargetNanme){
- Objecttarget=this.beans.get(targetNanme);
- if(target!=null){
- this.handler=(ProxyHandler)this.beans.get(proxyName);
- returnthis.handler.getProxy(target);
- }
- returnnull;
- }
- }
- publicclassBeanUtil{
- publicstaticvoidsetProperty(Objectbean,StringfieldName,
- ObjectfieldRef)throwsException{
- StringclassName=getClassName(bean);
- ClassbeanClass=Class.forName(className);
- Method[]methods=beanClass.getMethods();
- StringsetterName="set"+fieldName.substring(0,1).toUpperCase()
- +fieldName.substring(1,fieldName.length());
- for(Methodm:methods){
- if(m.getName().equals(setterName)){
- m.invoke(bean,fieldRef);
- System.out.println("已调用"+m.getName()+"()向"+className
- +"注入"+getClassName(fieldRef));
- return;
- }
- }
- System.out.println(">>注入失败:"+className+"类中不存在"+fieldName
- +"字段对应的setter()方法...");
- }
- privatestaticStringgetClassName(Objecto){
- if(o==null){
- System.out.println("传入的Object实例为null...");
- returnnull;
- }
- StringfullName=o.toString();
- StringclassName=fullName.substring(0,fullName.indexOf("@"));
- returnclassName;
- }
- }
- publicclassAOP_DI{
- publicstaticvoidmain(String[]args){
- ProxyFactoryproxyFactory=newProxyFactory("config.properties");
- ReportCreatorreportCreator=(ReportCreator)proxyFactory
- .getProxy("targetProxy","target");
- reportCreator.getHtmlReport("http://code.google.com/file/...");
- }
- }
根据最前面的配置文件信息,运行可得以下结果:
- 组件定义:AOP.aop_di.ProxyHandler
- 组件定义:AOP.aop_di.LogBeforeAdvice
- 组件定义:AOP.aop_di.LogAfterAdvice
- 组件定义:AOP.aop_di.RemoteReportCreator
- 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.LogBeforeAdvice
- 已调用setBeforeAdvice()向AOP.aop_di.ProxyHandler注入AOP.aop_di.LogBeforeAdvice
- 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.LogAfterAdvice
- 已调用setAfterAdvice()向AOP.aop_di.ProxyHandler注入AOP.aop_di.LogAfterAdvice
- 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.RemoteReportCreator
- 已调用setTarget()向AOP.aop_di.ProxyHandler注入AOP.aop_di.RemoteReportCreator
- 原业务方法被调用【之前】先打印日志...
- 根据【远程】文件生成【HTML】格式的报表...
- 原业务方法被调用【之后】再打印日志...
修改配置文件,即去掉其中的LogAfterAdvice的注入,如下:
- #definebeans
- Bean.target=AOP.aop_di.RemoteReportCreator
- Bean.targetProxy=AOP.aop_di.ProxyHandler
- Bean.logBeforeAdvice=AOP.aop_di.LogBeforeAdvice
- #defineDI
- DI.targetProxy.target=target
- DI.targetProxy.beforeAdvice=logBeforeAdvice
运行结果如下:
- 组件定义:AOP.aop_di.ProxyHandler
- 组件定义:AOP.aop_di.LogBeforeAdvice
- 组件定义:AOP.aop_di.RemoteReportCreator
- 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.LogBeforeAdvice
- 已调用setBeforeAdvice()向AOP.aop_di.ProxyHandler注入AOP.aop_di.LogBeforeAdvice
- 依赖注入:AOP.aop_di.ProxyHandler.AOP.aop_di.RemoteReportCreator
- 已调用setTarget()向AOP.aop_di.ProxyHandler注入AOP.aop_di.RemoteReportCreator
- 原业务方法被调用【之前】先打印日志...
- 根据【远程】文件生成【HTML】格式的报表...
小结:
似乎勉强达到了前面我所说的目标:利用动态代理结合之前写的关于控制反转(IoC)容器、依赖注入(DI)、读外部配置文件,来集中式地、简单地模拟一下Spring中所具有的IoC、DI、AOP功能。
其实问题存在不少,而且在我实现代码时也遇到一些问题,但现在篇幅已经挺长的了(不知道有没谁会坚持看完?哈…),可能会另外写一篇总结来记录一些我记忆较深刻的方面,我觉得其中更重要的是:写这几篇文章的意图是啥呢?从哪里得到思路的?:-D
瞧瞧以下您感兴趣的、相关的内容 ^_^
★演进式例解AOP:Java 动态代理
★演进式例解控制反转(IoC)、依赖注入(DI)之一
★演进式例解控制反转(IoC)、依赖注入(DI)之二
★结合配置文件、反射完善控制反转(IoC)、依赖注入(DI)
?装饰模式(Decorator)与动态代理的强强联合
?(Dynamic Proxy)动态代理模式的Java实现
?(Factory Method)工厂方法模式的Java实现
?Java RMI 框架的工厂方法模式实现
?(Mediator)中介者模式的Java实现(加修改) (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|