问题描述
在服务器编程中,通常需要处理多种不同的请求,在正式处理请求之前,需要对请求做一些预处理,如:
- 纪录每个client的每次访问信息。
- 对client进行认证和授权检查(authentication and authorization)。
- 检查当前session是否合法。
- 检查client的ip地址是否可信赖或不可信赖(ip地址白名单、黑名单)。
- 请求数据是否先要解压或解码。
- 是否支持client请求的类型、browser版本等。
- 添加性能监控信息。
- 添加调试信息。
- 保证所有异常都被正确捕获到,对未预料到的异常做通用处理,防止给client看到内部堆栈信息。
在响应返回给客户端之前,有时候也需要做一些预处理再返回:
- 对响应消息编码或压缩。
- 为所有响应添加公共头、尾等消息。
- 进一步enrich响应消息,如添加公共字段、session信息、cookie信息,甚至完全改变响应消息等。
如何实现这样的需求,同时保持可扩展性、可重用性、可配置、移植性?
问题解决
要实现这种需求,最直观的方法就是在每个请求处理过程中添加所有这些逻辑,为了减少代码重复,可以将所有这些检查提取成方法,这样在每个处理方法中调用即可:
public response service1(request request) {
validate(request);
request = transform(request);
response response = process1(request);
return transform(response);
}
此时,如果出现service2方法,依然需要拷贝service1中的实现,然后将process1换成process2即可。这个时候我们发现很多重复代码,继续对它重构,比如提取公共逻辑到基类成模版方法,这种使用继承的方式会引起子类对父类的耦合,如果要让某些模块变的可配置需要有太多的判断逻辑,代码变的臃肿;因而可以更进一步,将所有处理逻辑抽象出一个processor接口,然后使用decorate模式(即引用优于继承):
public interface processor {
response process(request request);
}
public class coreprocessor implements processor {
public response process(request request) {
// do process/calculation
}
}
public class decoratedprocessor implements processor {
private final processor innerprocessor;
public decoratedprocessor(processor processor) {
this.innerprocessor = processor;
}
public response process(request request) {
request = preprocess(request);
response response = innerprocessor.process(request);
response = postprocess(response);
return response;
}
protected request preprocess(request request) {
return request;
}
protected response postprocess(response response) {
return response;
}
}
public void transformer extends decoratedprocessor {
public transformer(processor processor) {
super(processor);
}
protected request preprocess(request request) {
return transformrequest(request);
}
protected response postprocess(response response) {
return transformresponse(response);
}
}
此时,如果需要在真正的处理逻辑之前加入其他的预处理逻辑,只需要继承decoratedprocessor,实现preprocess或postprocess方法,分别在请求处理之前和请求处理之后横向切入一些逻辑,也就是所谓的aop编程:面向切面的编程,然后只需要根据需求构建这个链条:
processor processor = new missingexceptioncatcher(new debugger(new transformer(new coreprocessor());
response response = processor.process(request);
......
这已经是相对比较好的设计了,每个processor只需要关注自己的实现逻辑即可,代码变的简洁;并且每个processor各自独立,可重用性好,测试方便;整条链上能实现的功能只是取决于链的构造,因而只需要有一种方法配置链的构造即可,可配置性也变得灵活;然而很多时候引用是一种静态的依赖,而无法满足动态的需求。要构造这条链,每个前置processor需要知道其后的processor,这在某些情况下并不是在起初就知道的。此时,我们需要引入intercepting filter模式来实现动态的改变条链。
intercepting filter模式
在前文已经构建了一条由引用而成的processor链,然而这是一条静态链,并且需要一开始就能构造出这条链,为了解决这个限制,我们可以引入一个processorchain来维护这条链,并且这条链可以动态的构建。
有多种方式可以实现并控制这个链:
- 在存储上,可以使用数组来存储所有的processor,processor在数组中的位置表示这个processor在链条中的位置;也可以用链表来存储所有的processor,此时processor在这个链表中的位置即是在链中的位置。
- 在抽象上,可以所有的逻辑都封装在processor中,也可以将核心逻辑使用processor抽象,而外围逻辑使用filter抽象。
- 在流程控制上,一般通过在processor实现方法中直接使用processorchain实例(通过参数掺入)来控制流程,利用方法调用的进栈出栈的特性实现preprocess()和postprocess()处理。
在实际中使用这个模式的有:servlet的filter机制、netty的channelpipeline中、structs2中的interceptor中都实现了这个模式。
intercepting filter模式在servlet的filter中的实现(jetty版本)
其中servlet的filter在jetty的实现中使用数组存储filter,filter末尾可以使用servlet实例处理真正的业务逻辑,在流程控制上,使用filterchain的dofilter方法来实现。如filterchain在jetty中的实现:
public void dofilter(servletrequest request, servletresponse response) throws ioexception, servletexception
// pass to next filter
if (_filter < lazylist.size(_chain)) {
filterholder holder= (filterholder)lazylist.get(_chain, _filter);
filter filter= holder.getfilter();
filter.dofilter(request, response, this);
return;
}
// call servlet
httpservletrequest srequest = (httpservletrequest)request;
if (_servletholder != null) {
_servletholder.handle(_baserequest,request, response);
}
}
这里,_chain实际上是一个filter的arraylist,由filterchain调用dofilter()启动调用第一个filter的dofilter()方法,在实际的filter实现中,需要手动的调用filterchain.dofilter()方法来启动下一个filter的调用,利用方法调用的进栈出栈的特性实现request的pre-process和response的post-process处理。如果不调用filterchain.dofilter()方法,则表示不需要调用之后的filter,流程从当前filter返回,在它之前的filter的filterchain.dofilter()调用之后的逻辑反向处理直到第一个filter处理完成而返回。
public class myfilter implements filter {
public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception {
// pre-process servletrequest
chain.dofilter(request, response);
// post-process servlet response
}
}
整个filter链的处理流程如下:
intercepting filter模式在netty3中的实现
netty3在defaultchannelpipeline中实现了intercepting filter模式,其中channelhandler是它的filter。在netty3的defaultchannelpipeline中,使用一个以channelhandlercontext为节点的双向链表来存储channelhandler,所有的横切面逻辑和实际业务逻辑都用channelhandler表达,在控制流程上使用channelhandlercontext的senddownstream()和sendupstream()方法来控制流程。不同于servlet的filter,channelhandler有两个子接口:channelupstreamhandler和channeldownstreamhandler分别用来请求进入时的处理流程和响应出去时的处理流程。对于client的请求,从defaultchannelpipeline的sendupstream()方法入口:
public void senddownstream(channelevent e) {
defaultchannelhandlercontext tail = getactualdownstreamcontext(this.tail);
if (tail == null) {
try {
getsink().eventsunk(this, e);
return;
} catch (throwable t) {
notifyhandlerexception(e, t);
return;
}
}
senddownstream(tail, e);
}
void senddownstream(defaultchannelhandlercontext ctx, channelevent e) {
if (e instanceof upstreammessageevent) {
throw new illegalargumentexception("cannot send an upstream event to downstream");
}
try {
((channeldownstreamhandler) ctx.gethandler()).handledownstream(ctx, e)
} catch (throwable t) {
e.getfuture().setfailure(t);
notifyhandlerexception(e, t);
}
}
如果有响应消息,该消息从defaultchannelpipeline的senddownstream()方法为入口:
public void sendupstream(channelevent e) {
defaultchannelhandlercontext head = getactualupstreamcontext(this.head);
if (head == null) {
return;
}
sendupstream(head, e);
}
void sendupstream(defaultchannelhandlercontext ctx, channelevent e) {
try {
((channelupstreamhandler) ctx.gethandler()).handleupstream(ctx, e);
} catch (throwable t) {
notifyhandlerexception(e, t);
}
}
在实际实现channelupstreamhandler或channeldownstreamhandler时,调用channelhandlercontext中的sendupstream或senddownstream方法将控制流程交给下一个channelupstreamhandler或下一个channeldownstreamhandler,或调用channel中的write方法发送响应消息。
public class mychannelupstreamhandler implements channelupstreamhandler {
public void handleupstream(channelhandlercontext ctx, channelevent e) throws exception {
// handle current logic, use channel to write response if needed.
// ctx.getchannel().write(message);
ctx.sendupstream(e);
}
}
public class mychanneldownstreamhandler implements channeldownstreamhandler {
public void handledownstream(
channelhandlercontext ctx, channelevent e) throws exception {
// handle current logic
ctx.senddownstream(e);
}
}
当channelhandler向channelpipelinecontext发送事件时,其内部从当前channelpipelinecontext
节点出发找到下一个channelupstreamhandler或channeldownstreamhandler实例,并向其发送
channelevent,对于downstream链,如果到达链尾,则将channelevent发送给channelsink:
public void senddownstream(channelevent e) {
defaultchannelhandlercontext prev = getactualdownstreamcontext(this.prev);
if (prev == null) {
try {
getsink().eventsunk(defaultchannelpipeline.this, e);
} catch (throwable t) {
notifyhandlerexception(e, t);
}
} else {
defaultchannelpipeline.this.senddownstream(prev, e);
}
}
public void sendupstream(channelevent e) {
defaultchannelhandlercontext next = getactualupstreamcontext(this.next);
if (next != null) {
defaultchannelpipeline.this.sendupstream(next, e);
}
}
正是因为这个实现,如果在一个末尾的channelupstreamhandler中先移除自己,在向末尾添加一个新的channelupstreamhandler,它是无效的,因为它的next已经在调用前就固定设置为null了。
在defaultchannelpipeline的channelhandler链条的处理流程为:
在这个实现中,不像servlet的filter实现利用方法调用栈的进出栈来完成pre-process和post-process,而是在进去的链和出来的链各自调用handleupstream()和handledownstream()方法,这样会引起调用栈其实是两条链的总和,因而需要注意这条链的总长度。这样做的好处是这条channelhandler的链不依赖于方法调用栈,而是在defaultchannelpipeline内部本身的链,因而在handleupstream()或handledownstream()可以随时将执行流程转发给其他线程或线程池,只需要保留channelpipelinecontext引用,在处理完成后用这个channelpipelinecontext重新向这条链的后一个节点发送channelevent,然而由于servlet的filter依赖于方法的调用栈,因而方法返回意味着所有执行完成,这种限制在异步编程中会引起问题,因而servlet在3.0后引入了async的支持。
intercepting filter模式的缺点
简单提一下这个模式的缺点:
1. 相对传统的编程模型,这个模式有一定的学习曲线,需要很好的理解该模式后才能灵活的应用它来编程。
2. 需要划分不同的逻辑到不同的filter中,这有些时候并不是那么容易。
3. 各个filter之间共享数据将变得困难。在netty3中可以自定义自己的channelevent来实现自定义消息的传输,或者使用channelpipelinecontext的attachment字段来实现消息传输,而servlet中的filter则没有提供类似的机制,如果不是可以配置的数据在config中传递,其他时候的数据共享需要其他机制配合完成。
参考
posted on 2015-09-03 22:14
dlevin 阅读(5467)
评论(0) 编辑 收藏 所属分类:
architecture