介绍
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
何时使用:在处理消息的时候以过滤很多道。
如何解决:拦截的类都实现统一接口。
关键代码:Handler 里面定义好处理的逻辑,在 HandlerRequest 里判断是否继续进行链条传递,如果没达到条件则向下传递,达到条件则链条终止传递。
应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。
注意事项:在 JAVA WEB 中遇到很多应用。
实例演示
不管是在社交媒体还是在论坛中,我们都会进行发帖,帖子发布以后会进行关键词汇的审核,例如说暴力、色情、敏感词汇等,我们发布的内容经过一层层过滤之后才会呈现出来,我们先来看一下例子的类图:
首先定义一个帖子的消息类:
class Msg{
String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;`在这里插入代码片`
}
@Override
public String toString() {
return "Msg{" +
"msg='" + msg + '\'' +
'}';
}
}
定义过滤器的接口,在这里的doFilter方法设置称为布尔类型,如果返回true则继续执行,返回false则中断(用于后面的链条当中进行判断):
interface Filter{
/**
* 返回true则继续执行
* @param msg 处理的信息
* @return 是否继续执行
*/
boolean doFilter(Msg msg);
}
定义四个不同的过滤器:
class SymbolFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace(":)", "~~"));
return true;
}
}
class ViolentFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace("打架", "争吵"));
return true;
}
}
class SensitiveFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace("残疾", "受伤"));
return true;
}
}
class URLFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace("123.com", "***.com"));
return true;
}
}
在Main方法中进行调用:
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("很解气:),今天喝完酒找我们打架的小伙子被我们弄残疾了,来我空间123.com看照片吧");
System.out.println(msg.getMsg());
new SymbolFilter().doFilter(msg);
new ViolentFilter().doFilter(msg);
new SensitiveFilter().doFilter(msg);
new URLFilter().doFilter(msg);
System.out.println(msg.getMsg());
}
}
可以看到结果成功实现了过滤的功能:
很解气:),今天喝完酒找我们打架的小伙子被我们弄残疾了,来我空间123.com看照片吧
很解气~~,今天喝完酒找我们争吵的小伙子被我们弄受伤了,来我空间***.com看照片吧
以上我们就实现了一个帖子内容的过滤功能,假如我要再增加过滤器,那么在Main方法中就要调用无数次的doFilter
方法。有没有什么方法可以在Main方法中调用一次doFilter
方法就能实现一连串的过滤功能呢?
目前可以想到利用一个List来存放我们的过滤器(链条的概念),循环遍历就可以实现了,那么在Main方法中定义List的话add各种Filter不就好了嘛。但是这样还是不符合我们面向对象的思想,不管Filter用什么存,我在Main里面只需要实例化一个类,这个类帮我干这些事情就行了,在Main方法中只需要调用这个类的doFilter
方法才是我们想要的。
运用此方法的另一个好处就是如果我的帖子执行到链条中的某一个过滤器时再不需要往下执行了,那么该方法就可解决这个问题。
那么好,我们来一起定义一个FilterChain
类,并且我在执行到SensitiveFilter
时退出链条不执行URLFilter
。我们来看一下改进后的类图:
刚才我们说到,将doFilter方法定义为布尔类型是为了判断当前过滤器是否继续执行,既然我们想要在执行到SensitiveFilter
时退出链条不执行URLFilter
,那么我们就要在SensitiveFilter
类中的doFilter
返回一个false,代表该过滤器执行完毕链条不再继续执行,来看一下修改返回值后的四个过滤器:
class SymbolFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace(":)", "~~"));
return true;
}
}
class ViolentFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace("打架", "争吵"));
return true;
}
}
class SensitiveFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace("残疾", "受伤"));
//当判断完敏感词汇后链条不再继续执行
return false;
}
}
class URLFilter implements Filter {
@Override
public boolean doFilter(Msg msg) {
msg.setMsg(msg.getMsg().replace("123.com", "***.com"));
return true;
}
}
接下来定义责任链FilterChain:
class FilterChain implements Filter {
private List<Filter> filters = new ArrayList<>();
FilterChain add(Filter f){
filters.add(f);
return this;
}
@Override
public boolean doFilter(Msg msg) {
for(Filter f:filters){
//判断链条是否继续执行
if(!f.doFilter(msg)){
return false;
}
}
return true;
}
}
最后在Main方法中进行调用:
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("很解气:),今天喝完酒找我们打架的小伙子被我们弄残疾了,来我空间123.com看照片吧");
System.out.println(msg.getMsg());
FilterChain filter = new FilterChain();
filter.add(new SymbolFilter()).add(new ViolentFilter()).add(new SensitiveFilter()).add(new URLFilter());
filter.doFilter(msg);
System.out.println(msg.getMsg());
}
}
很解气:),今天喝完酒找我们打架的小伙子被我们弄残疾了,来我空间123.com看照片吧
很解气~~,今天喝完酒找我们争吵的小伙子被我们弄受伤了,来我空间123.com看照片吧
根据输出结果可以看到最后一个URLFilter过滤器的确没有执行,并且在Main方法中将我们的过滤器添加进链条以后直接调用一次doFilter
即可完成我们的目的。
如果我们定义了另外一个FilterChain
,那么链条之间是可以相互进行add方法的。
Servlet中的Filter细节辨析
当客户端向服务器发送一条请求时,该请求会通过一个链条进行doFilter方法过滤,服务器接收到请求以后再给客户端返回一条信息,该返回信息还会经过doFilter方法过滤,这么听的话好像跟上面的例子没什么区别,但是细节之处不同的就是Servlet中的过滤操作是如下图的顺序进行的:
可以看到Request和Response经过的链条的顺序刚好是相反的,那么该方法是如何实现的呢?
调用Api可发现在servlet中的Filter接口下的doFilter方法是这样的:
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
FilterChain下的doFilter方法是这样的:
doFilter(ServletRequest request, ServletResponse response)
可以看到跟我们上面例子不同的是,在实现Filter接口的时候 doFilter方法新增了第三个参数,是FilterChain 的,这个地方乍一看比较疑惑,现在我们就来写一个小例子来解释一下吧。
首先我们定义一个Request和Response类:
class Request{
String str;
}
class Response{
String str;
}
接下来我只保留两个过滤器,分别为SymbolFilter以及ViolentFilter,并且进行修改:
interface Filter{
boolean doFilter(Request request, Response response, FilterChain filterChain);
}
class SymbolFilter implements Filter{
@Override
public boolean doFilter(Request request, Response response, FilterChain filterChain) {
request.str = request.str.replace(":)", "~~");
filterChain.doFilter(request, response);
response.str += "执行SymbolFilter ";
return true;
}
}
class ViolentFilter implements Filter{
@Override
public boolean doFilter(Request request, Response response, FilterChain filterChain) {
request.str = request.str.replace("打架", "争吵");
filterChain.doFilter(request, response);
response.str += "执行ViolentFilter ";
return true;
}
}
我们在这两个Filter都加入了递归操作,咱们接着来看FilterChain是怎么实现的:
class FilterChain{
private List<Filter> filters = new ArrayList<>();
int index = 0;
FilterChain add(Filter f){
filters.add(f);
return this;
}
public boolean doFilter(Request request, Response response) {
if(index == filters.size()){
return false;
}
Filter f = this.filters.get(index);
index++;
return f.doFilter(request, response, this);
}
}
可以看到,我们新增了一个成员变量index,并且在doFilter方法中进行index的判断,如果链条执行到头则终止执行,最后在Main方法中进行测试,看看是否会满足上图所示的那样:
public class ServletMain {
public static void main(String[] args) {
Request request = new Request();
request.str = "很解气:),今天喝完酒找我们打架的小伙子被我们弄残疾了,来我空间123.com看照片吧";
Response response = new Response();
response.str = "";
System.out.println(request.str);
FilterChain filterChain = new FilterChain();
filterChain.add(new SymbolFilter()).add(new ViolentFilter());
filterChain.doFilter(request, response);
System.out.println(request.str);
System.out.println("response.str:" + response.str);
}
}
很解气:),今天喝完酒找我们打架的小伙子被我们弄残疾了,来我空间123.com看照片吧
很解气~~,今天喝完酒找我们争吵的小伙子被我们弄残疾了,来我空间123.com看照片吧
response.str:执行ViolentFilter 执行SymbolFilter
我们看到Request成功走完链条的两个过滤器,并且Response经过过滤器的顺序与Request是相反的,我们来一起分析一下吧。
一、程序刚开始进入doFilter方法,此时index为0,并且取出第一个过滤器进行过滤,index自增变为1。
二、进入第一个过滤器以后对Request进行了处理并且再次调用FilterChain的doFilter方法,此时取出第二个过滤器进行过滤并且index自增变为2。
三、进入第二个过滤器以后对Request进行过滤,并且再次调用FilterChain的doFilter方法,此时Request已走完链条的全部过滤器。
四、此时index等于链条的长度,返回false,表明链条不再继续执行,返回上一步第二个过滤器中往下接着往下执行,开始对Response进行过滤,返回true。
五、此时返回第一个过滤器中,接着往下执行,便是第一个过滤器对Response进行处理,最后返回True,所有流程执行完毕。
经过以上步骤,刚好实现了上图所示的步骤。
总结
以上就是对责任链模式进行的一点学习小结~