责任链模式进阶与AOP思想的融合与应用.docx
《责任链模式进阶与AOP思想的融合与应用.docx》由会员分享,可在线阅读,更多相关《责任链模式进阶与AOP思想的融合与应用.docx(20页珍藏版)》请在冰点文库上搜索。
责任链模式进阶与AOP思想的融合与应用
责任链模式进阶:
与AOP思想的融合与应用
一.AOP理念与责任链模式的融合
1、AOP理念概述
我们知道,面向对象的三大特性是继承、多态和封装,而封装就要求我们将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。
从编程的角度上说,实质上也就是让不同的类设计不同的方法,这样代码就有组织地分散到一个个的类中去了。
这样做的好处是降低了代码的复杂程度,增强代码可重用性。
但是人们也发现,在分散代码的同时,也增加了代码的冗余性,或者说代码重用的还不够彻底,什么意思呢?
比如说,我们需要在两个类中都需要做日志的功能,按面向对象的设计方法,我们就必须在两个类的方法中都加入日志功能的代码,也许二者对日志的处理是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
也许有人会说,那好办啊,我们可以将这段代码写到一个独立的类的独立的方法里,然后在这两个类中分别调用相关方法。
但是这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。
那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程(Aspect-OrientedProgramming,AOP)。
一般而言,我们把切入到指定类指定方法的代码片段称为切面,而把切面切入的目的地(类或方法)叫切入点。
其中,切面是在AOP思想中引入的一种新的编程单位,它使得横切关注点模块化,这对现有的设计模式产生了非常重大的影响。
根据AOP的理念,我们就可以把多个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
总的来说,AOP实质上只是OOP的补充而已。
OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码,也就是说AOP的出现使得OOP变得立体了。
从技术上来说,AOP基本上是通过代理机制实现的。
AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
2、AOP理念与CoR模式
概括地说,面向对象(Object-Oriented)的分析和设计方法可以将真实世界的实体抽象成类和对象,从而实现了从问题域到软件的转换,这种方法能完美的抽象出问题域中的角色。
但不同的角色可能有着共同的行为,这种共同的行为被称为横切关注点。
利用面向对象的方法不能很好地抽象出横切关注点,从而导致了类间耦合度高、代码复用率低(冗余)等问题。
而AOP的核心思想允许将分散在类中的共同逻辑(行为)分离出来,将OOP不能很好处理地横切关注点抽象在切面之中。
用传统的面向对象方法实现责任链模式虽然能够满足责任链模式要求的一切特征,在应用上也有很多实例,但是仍然存在者一些明显的缺陷和不足。
比如,各个请求处理者除了实现自身应当处理的逻辑外还要实现责任链的结构(即successor属性及其Setter),也就是说,责任链的建立和指派包含在实现角色的类中,并没有抽象出来,这直接导致责任链的指派不够灵活。
AOP思想的精髓能够将横向的关注点分离出来,这大大提高了我们认识世界和抽象世界的能力。
实际上,责任链模式的缺陷主要在于具体实现角色的对象中存在着共同的行为——实现责任链结构的行为,而这些行为并没有被抽象出来,而用AOP改进责任链模式的关键就是要将责任链结构的实现用切面抽象出来,使得各个对象只关注自身必须实现的功能性需求。
实际上,用AOP思想实现责任链模式时仍然保留了Client,Handler和ConcreteHandler三个角色,不同点是增加了实现责任链的切面,即HandlerChain,下图反映了融合AOP思想后的责任链模式(以Filter为例)。
利用AOP理念来改进责任链模式可以准确地分离出责任链模式中不同角色的共同行为。
实际上,无论是JavaWeb中的Filter,还是Struts2中的Interceptor,它们都是责任链模式与AOP思想互相融合的巧妙实践。
二.Filter概述
事实上,ServletAPI还提供了一个重要接口——Filter。
在我们开发Web应用时,若编写的Java类实现了这个接口,那么我们就可以将这个Java类称为一个过滤器(Filter)。
Filter可以认为是Servlet的一种加强版,它主要用于对用户请求进行预处理以及对服务器响应进行后处理,是个典型的处理链。
下面的图示形象地反映了Filter的工作流程。
当客户端发出Web资源的请求时,Web服务器根据应用程序配置文件设置的过滤规则进行检查,若客户请求满足过滤规则,则对客户请求/响应进行过滤(拦截),期间我们可以对请求信息(请求头和请求数据)进行检查或改动,然后对请求放行以便由过滤链中的其他过滤器进行处理,最后把请求/响应交给请求的Web资源(Servlet)处理。
同样地,在这个过程中我们可以修改响应信息,从而完成一定的任务,其工作原理如下图所示
过滤链的好处是,发出请求的客户端并不知道链上的哪一个过滤器将处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任,并且在执行过程中的任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容,这显然可以看作是非纯责任链模式的一种典型实现。
三.AOP、责任链模式与JavaWeb中的Filter
实质上,Filter的实现既体现了AOP的理念,也体现了责任链模式的精髓。
AOP的主要的意图是将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非主导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
以处理中文字符乱码问题为例,它并非是业务逻辑的内容却又分布在各个请求处理器中,所以对于这些内容的处理,我们就可以基于AOP的思想将其提取出来(AOP中的切面),使用Filter进行整体设置。
这种方式相当于对类中的内容做进一步的抽象,使我们的系统更加灵活,更加能应对变化,也进一步提高了代码复用。
此外,Filter的实现体现了责任链模式的精髓,即将请求的发送者与请求的处理者解耦,从而使得系统更灵活,也更容易扩展。
就像Servlet规范对Filter描述的那样,过滤链是由Servlet容器提供给开发者的一种过滤器调用的视图,过滤器使用过滤链去调用链中的下一个过滤器去处理请求,特别地,如果当前过滤器时过滤链中的最后一个过滤器,过滤链将把它交给相应的资源处理器(Servlet)进行处理。
更进一步地说,使用过滤链对请求进行过滤的好处就是,发出请求的客户端并不知道链上的哪一个过滤器将处理这个请求,这使得系统可以在不影响客户端的情况下,动态地重新组织链和分配责任。
并且,在执行过程中的任何时候都可以直接返回结果,也就是说,只要不执行chain.doFilter()就不会对请求放行,也就不会再执行后面的过滤器和请求的内容。
这显然可以看作是非纯责任链模式的一种典型实现。
显然,FilterChain本身就是对责任链切面的抽象,是对传统责任链模式的一个改进,整个Filter机制本身也是AOP思想与责任链模式的融合的最佳实践。
特别地,为了大家更好地理解AOP理念和责任链模式在JavaWeb中的Filter中的应用,我们专门写了一个中文解码过滤器,其实现了对中文乱码的统一处理,无论请求方式是GET还是POST。
在这里,Filter相当于责任链模式中的抽象处理者,而DecodeFilter实现了Filter接口,相当于责任链模式中的具体处理者,特别地,ServletAPI对FilterChain的抽象则是AOP思想的重要体现,也就是将责任链结构的实现用切面(FilterChain)抽象出来了,准确地分离出责任链模式中不同角色的共同行为(责任链的构建与维护)。
/**
*Description:
使用Filter解决GET/POST提交的中文乱码
*@authorrico
*@created2017-3-4上午10:
55:
06
*/
publicclassDecodeFilterimplementsFilter{
/**指定编码方式,默认utf-8(@author:
rico)*/
privateStringencoding;//Filter参数
@Override
publicvoiddestroy(){
this.encoding=null;
}
@Override
publicvoiddoFilter(ServletRequestreq,ServletResponseresponse,
FilterChainchain)throwsIOException,ServletException{
HttpServletRequestrequest=(HttpServletRequest)req;
//重新编码后的请求
HttpServletRequestnewReq=null;
//获取请求方式
Stringmethod=request.getMethod();
if("POST".equalsIgnoreCase(method)){//POST请求的处理方式
request.setCharacterEncoding(encoding);
newReq=request;
}else{//GET请求的处理方式
//匿名内部类:
最终提供给我们的是一个匿名子类对象
newReq=newHttpServletRequestWrapper(request){//HttpServletRequest接口的实现类
//重写对请求参数所有可能的获取方式
@Override
publicStringgetParameter(Stringname){
Stringvalue=super.getParameter(name);
if(value!
=null){
value=this.transCoding(value);
}
returnvalue;
}
//重写对请求参数所有可能的获取方式
@Override
publicString[]getParameterValues(Stringname){
String[]values=super.getParameterValues(name);
if(values==null){
returnvalues;
}
for(inti=0;ivalues[i]=this.transCoding(values[i]);
}
returnvalues;
}
//重写对请求参数所有可能的获取方式
@Override
publicMapgetParameterMap(){
Mapmap=super.getParameterMap();
Mapresult=newHashMap();
Set>entrySet=map.entrySet();
for(Map.Entryset:
entrySet){
Stringname=set.getKey();
String[]values=set.getValue();
for(inti=0;ivalues[i]=values[i];
}
result.put(name,values);
}
returnresult;
}
//代码重用,对中文字符进行解码
publicStringtransCoding(Stringvalue){
try{
value=newString(value.getBytes("iso-8859-1"),
encoding);
}catch(UnsupportedEncodingExceptione){
System.out.println(this.getClass().getName()
+"发生转码错误:
从"+"iso-8859-1"+"到"
+encoding);
e.printStackTrace();
}
returnvalue;
}
};
}
//AOP思想的重要体现,将请求交给其下家继续进行处理,不纯的责任链模式
chain.doFilter(newReq,response);
}
@Override
publicvoidinit(FilterConfigconfig)throwsServletException{
//从配置文件获取编码参数
encoding=config.getInitParameter("encoding");
encoding=encoding==null?
"utf-8":
encoding;
}
}
要想让该过滤器起作用,还必须将其配置到Web中,即需要在web.xml中添加如下描述片段。
需要注意的是,当一个Web应用包含多个过滤器时,WEB容器会根据其注册顺序进行调用,也就是说,在web.xml文件中越靠前,越先被调用。
因此,若想让该过滤器能够对Struts2应用起作用,则必须将其配置到Struts2过滤器前面。
--当一个Web应用包含多个过滤器时,根据其注册顺序进行调用:
在web.xml文件中越靠前,越先被调用-->
--因此,若想让该过滤器能够对Struts2应用起作用,则必须将其配置到Struts2过滤器前面-->
DecodeFilter
cn.edu.tju.rico.filter.DecodeFilter
encoding
utf-8
DecodeFilter
/*
这样,当客户端发出Web资源的请求时,Web容器就会根据应用程序配置文件设置的过滤规则进行检查,由于我们设置的是对所有请求进行过滤(/*),因此我们可以对请求参数进行解码,然后依次通过过滤器链的其他过滤器,最后把请求交给请求的Web资源(Servlet)处理。
需要注意的是,在本案例中,我们只对请求做了预处理,而没有对响应做后处理。
这正好印证了J2EE中对FilterChain的描述:
“FiltersusetheFilterChaintoinvokethenextfilterinthechain,orifthecallingfilteristhelastfilterinthechain,toinvoketheresourceattheendofthechain。
”
四.手动模拟JavaWeb中的过滤器Filter
为了更好的体会AOP思想和责任链模式,我们下面手动模拟了JavaWeb中的过滤器Filter的实现,其所模拟的流程与Filter的作用流程相同,如下图所示。
在本实现中,我们包含一个抽象Filter,三个具体的Filter,包括HTMLFilter,SensitiveFilter和FaceFilter;FilterChain用于对处理链(责任链切面)的抽象。
此外,Request和Response用于对请求消息和响应消息的抽象,Client用于对客户端的抽象,其类图如下所示:
下面给出各抽象模块的具体实现,需要指出的是,本示例参考于马士兵老师对责任链模式的讲解,但对其做了改进,尤其是关于FilterChain的设计改进(只需接收Request和Response),使其与ServletAPI中FilterChain相一致。
1、抽象处理者:
Filter
publicinterfaceFilter{
//每个Filter均为FilterChain的成员,Filter持有FilterChain的引用,以便调用链条中的各处理者
voiddoFilter(Requestrequest,Responseresponse,FilterChainchain);
}
2、具体处理者:
HTMLFilter,SensitiveFilter和FaceFilter
//将请求消息中的"<>"替换成"[]"
publicclassHTMLFilterimplementsFilter{
@Override
publicvoiddoFilter(Requestrequest,Responseresponse,FilterChainchain){
//cessHTMLTag
Stringmsg=request.getRequest().replace("<","[").replace(">","]");
request.setRequest(msg);
chain.doFilter(request,response);
response.setResponse(response.getResponse()+"--->HTMLFilter");
}
}
//将请求消息中的"被就业"替换成"就业"
classSensitiveFilterimplementsFilter{
@Override
publicvoiddoFilter(Requestrequest,Responseresponse,FilterChainchain){
Stringmsg=request.getRequest().replace("被就业","就业");
request.setRequest(msg);
chain.doFilter(request,response);
response.setResponse(response.getResponse()+"--->SensitiveFilter");
}
}
//将请求消息中的":
)"替换成"笑脸"
classFaceFilterimplementsFilter{
publicvoiddoFilter(Requestrequest,Responseresponse,FilterChainchain){
Stringmsg=est.getRequest().replace(":
)","笑脸");
request.setRequest(msg);
chain.doFilter(request,response);
response.setResponse(response.getResponse()+"--->FaceFilter");
}
}
3、过滤链的抽象:
FilterChain
//对过滤链的抽象(横切关注点),是多个过滤器的聚集,本质上,FilterChain也可以看作是一个大的Filter
publicclassFilterChain{
Listfilters=newArrayList();
intindex=0;
//链式编程
publicFilterChainaddFilter(Filterfilter){
filters.add(filter);
returnthis;//返回自身
}
publicvoiddoFilter(Requestrequest,Responseresponse){
if(index==filters.size())return;
Filterfilter=filters.get(index);
index++;
filter.doFilter(request,response,this);
}
}
4、请求和响应的抽象:
Request和Response
//对请求消息的抽象
publicclassRequest{
//请求消息
privateStringrequest;
publicStringgetRequest(){
returnrequest;
}
publicvoidsetRequest(Stringrequest){
this.request=request;
}
}
//对响应消息的抽象
classResponse{
//响应消息
privateStringresponse;
publicStringgetResponse(){
returnresponse;
}
publicvoidsetResponse(Stringresponse){
this.response=response;
}
}
5、客户端的抽象:
Client
publicclassClient{
publicstaticvoidmain(String[]args){
//待处理消息
Stringmsg="大家好:
),