各位java技术爱好者,我们又见面了! 之前我在面试的时候被问到责任链模式的问题,当时答不上来。这件事就一直在我心里耿耿于怀。相信很多人面试完都有这种体验,哈哈~ 不过今日不同往日了,现在我已经搞懂了,其实并不是很难,给大家分享一下。
要学习一种技术,当然要搞清楚能解决什么问题,这是最关键的,否则就像买了一个开瓶器,还是用嘴咬开啤酒瓶盖一样(比喻很巧妙)。
首先我们用Request
对象,表示一个请求。
public class Request {
//请求数据
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
假设data
中有很多数据,我们要过滤掉一些关键字,比如水果
、蔬菜
。
如果不用设计模式,直接用if
解决,就是这样写:
public static void main(String[] args) throws Exception {
//创建请求体
String data = "肉蛋葱鸡水果蔬菜千层饼雅俗共赏第五层的马老师";
Request request = new Request();
request.setData(data);
String requestData = request.getData();
//如果包含水果关键字
if (requestData.contains("水果")) {
//过滤水果关键字
requestData = filterByWord(requestData, "水果");
}
//如果包含蔬菜关键字
if (requestData.contains("蔬菜")) {
//过滤蔬菜关键字
requestData = filterByWord(requestData, "蔬菜");
}
request.setData(requestData);
System.out.println(requestData);//肉蛋葱鸡千层饼雅俗共赏第五层的马老师
}
//过滤关键字的方法
private static String filterByWord(String data, String word) {
StringBuilder sb = new StringBuilder(data);
while (true) {
int index = sb.indexOf(word);
//如果不等于-1,搜索到关键字
if (index != -1) {
for (int i = 0; i < word.length(); i++) {
//关键字有多长就删多少次,比如水果,在index位置上删两次
sb.deleteCharAt(index);
}
} else {
//等于-1,没有关键字,跳出循环
break;
}
}
return sb.toString();
}
}
这样的代码在项目中不要太多,遇事不决来个if
,不行就再来一个。一般这种程序员就只看到了第一层。
问题:
1.如果这时候要增加多一些关键字呢,怎么处理?在原来的代码里继续加if
的话,那这个方法就越写越长。而且破坏了开闭原则
。
2.如果要调换顺序呢。难道要剪切复制代码?
所以一个好的程序员,实现功能只是基本要求,重要是代码要有好的维护性和扩展性。
我们可以这样想,把每个过滤关键字的方法抽成一个类,然后定义一个过滤关键字的方法,因为有很多个类似职责的类,所以定义一个接口,公共的方法可以定义在接口。 第一步:定义过滤器接口
public interface Filter {
//接口方法
String doFilter(String data, FilterChain filterChain);
//过滤关键字的方法
default String filterByWord(String data, String word) {
StringBuilder sb = new StringBuilder(data);
while (true) {
int index = sb.indexOf(word);
if (index != -1) {
for (int i = 0; i < word.length(); i++) {
//关键字有几个字,就删几次。比如水果就在index删两次即可
sb.deleteCharAt(index);
}
} else {
//如果找不到关键字,就跳出循环
break;
}
}
return sb.toString();
}
}
第二步:定义水果关键字过滤器
public class FruitsFilter implements Filter {
@Override
public String doFilter(String data, FilterChain filterChain) {
//过滤水果关键字
data = filterByWord(data, "水果");
//拿到控制器,继续调用下一个过滤器
return filterChain.preHandleRequest(data);
}
}
第三部:定义蔬菜关键字过滤器
public class VegetablesFilter implements Filter {
@Override
public String doFilter(String data, FilterChain filterChain) {
//过滤蔬菜关键字
data = filterByWord(data, "蔬菜");
//拿到控制器,继续调用下一个过滤器
return filterChain.preHandleRequest(data);
}
}
第四步:定义一个过滤器的控制器
public class FilterChain {
//使用List封装过滤器,List是有序的
private List<Filter> filters = new ArrayList<>();
//过滤器的下标,从0开始,每次调用preHandleRequest方法就+1
private int index = 0;
//添加过滤器
public void addFilter(Filter filter) {
filters.add(filter);
}
//调用过滤器的doFilter()方法,并把指针+1指向下一个过滤器
public String preHandleRequest(String data) {
//如果指针大于集合的size,则return,不再往下调用,相当于递归的终结条件
if (index == filters.size()) {
return data;
}
Filter filter = filters.get(index);
//下标指针+1
index++;
//精髓在这个this,把自己再当做参数传进去,实现了递归
return filter.doFilter(data, this);
}
}
这样就大功告成了,最后我们创建一个Main方法试试吧~
public class Main {
public static void main(String[] args) throws Exception {
//创建请求体
String data = "肉蛋葱鸡水果蔬菜千层饼雅俗共赏第五层的马老师";
Request request = new Request();
request.setData(data);
//创建FilterChain执行链
FilterChain filterChain = new FilterChain();
//添加过滤器
filterChain.addFilter(new FruitsFilter());
filterChain.addFilter(new VegetablesFilter());
//调用调用链的preHandleRequest方法
String s = filterChain.preHandleRequest(request.getData());
request.setData(s);
//打印结果,验证
System.out.println(request.getData());//下面是打印结果,结果正确
//肉蛋葱鸡千层饼雅俗共赏第五层的马老师
}
}
这就是责任链模式!下面用一张图来看看调用链的执行顺序。其实没有想得那么难吧。你学会了吗?
我们回过头去看,为什么这么绕呢,直接if
不香吗?
还真不香,在用if
的时候,提到的两个问题,破坏开闭原则
,还有调用顺序的问题,用责任链模式都得到解决了。
1.如果要加一些过滤的关键字,只需要加一个过滤的类,然后再添加到过滤器的集合中,不需要对原来的代码进行侵入式的开发。符合开闭原则
。2.执行顺序要变更的话,也不需要侵入式的改代码,只需要改一下添加过滤器的顺序。在Servlet
中,过滤器的顺序就是按xml
文件定义的顺序。在SpringMVC
则是按注册的顺序。这就非常易于扩展。
要多为下一个程序员着想,我为人人,人人为我,世界才会变得更美好!哈哈~~
以上就是一个简单的demo
,重要的是学习这种思想。那么我们看实战中是怎么应用的,所谓知其然知其所以然,学习忌讳浅尝辄止,趁热打铁,我们看看框架中怎么应用责任链模式。
很简单,实现HandlerInterceptor
接口,接口有三个方法需要重写。
DispatcherServlet
完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);我们定义一个关键字过滤器WordInterceptor
:
//定义一个关键字拦截器
public class WordInterceptor implements HandlerInterceptor {
//在执行controller定义的请求方法前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行WordInterceptor的preHandle()");
return true;
}
//controller定义的请求方法执行后,但还没渲染页面前,执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("执行WordInterceptor的postHandle()");
}
//在渲染页面完毕后执行,或者preHandle()返回fasle时执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("执行WordInterceptor的afterCompletion()");
}
}
如法炮制,再定义一个登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
//在执行controller定义的请求方法前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行LoginInterceptor的preHandle()");
return true;
}
//controller定义的请求方法执行后,但还没渲染页面前,执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("执行LoginInterceptor的postHandle()");
}
//在渲染页面完毕后执行,或者preHandle()返回fasle时执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("执行LoginInterceptor的afterCompletion()");
}
}
然后再注册到拦截器的集合中。
@Component
public class WebInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//先注册关键字拦截器,拦截所有请求
registry.addInterceptor(new WordInterceptor()).addPathPatterns("/**");
//再注册登录拦截器,拦截所有请求
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
}
}
然后启动项目,发起一个请求,我们就可以在控制台看到调用的顺序了。注意观察:
//按注册顺序执行preHandle()方法
执行WordInterceptor的preHandle()
执行LoginInterceptor的preHandle()
//按注册顺序,逆序执行postHandle()方法
执行LoginInterceptor的postHandle()
执行WordInterceptor的postHandle()
//从最后一个preHandle()执行的类,逆序执行afterCompletion()
执行LoginInterceptor的afterCompletion()
执行WordInterceptor的afterCompletion()
执行的流程图就是这样:
原理是什么呢?我们不妨走进源码去分析。
还是要看DispatcherServlet
的doDispatch()
方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//请求对象
HttpServletRequest processedRequest = request;
//定义一个调用链
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//省略
// 获取调用链
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//获取对应的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//调用拦截器的preHandle()前处理方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
//如果上面返回fasle,取反就是true,进来这里就return,结束了
return;
}
//调用Controller的RequestMapping对应的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//调用拦截器的postHandle()后处理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
//省略
}catch (Exception ex) {
//如果出现异常还是会执行triggerAfterCompletion()方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//如果出现异常还是会执行triggerAfterCompletion()方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
//省略
}
}
从上面源码中明显可以知道,拦截器接口的执行顺序:
applyPreHandle()
→handle()
→applyPostHandle()
→triggerAfterCompletion()
或者
applyPreHandle()
→triggerAfterCompletion()
其实上面这些方法除了handle()
定义在适配器中,其他都是这个接口的。handle()
方法我已经在上一篇《适配器模式与SpringMV》讲过了。下面我们看HandlerExecutionChain
接口。
public class HandlerExecutionChain {
//省略
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable//拦截器集合
private List<HandlerInterceptor> interceptorList;
//指针,用来记录applyPreHandle()方法执行到哪一个拦截器
private int interceptorIndex = -1;
}
这不就跟我们的demo
类似吗?定义了一个集合封装拦截器,定义一个指针遍历集合。
那么前置方法applyPreHandle()
是怎么样执行的呢?
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取拦截器
HandlerInterceptor[] interceptors = getInterceptors();
//判断不为空
if (!ObjectUtils.isEmpty(interceptors)) {
//按顺序遍历,所以拦截器接口定义的preHandle()是按顺序执行的
for (int i = 0; i < interceptors.length; i++) {
//按顺序获取注册的拦截器
HandlerInterceptor interceptor = interceptors[i];
//执行拦截器的preHandle()方法
if (!interceptor.preHandle(request, response, this.handler)) {
//如果preHandle()返回false,那就调用triggerAfterCompletion()方法
triggerAfterCompletion(request, response, null);
//返回false,结束调用
return false;
}
//如果preHandle()返回true,继续执行
//把下标索引记录到成员变量的指针中,用于后面执行triggerAfterCompletion()方法
this.interceptorIndex = i;
}
}
return true;
}
然后下一步执行的applyPostHandle()
方法,又是怎么执行的呢?
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//倒序遍历,所以postHandle()方法是从最后一个拦截器开始执行的
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
//postHandle()能获取到ModelAndView对象,拦截器可以对mv对象进行后处理
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//从成员变量interceptorIndex记录的指针值,开始倒序遍历
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
我们都看到上面三个方法都有一个公用的方法getInterceptors()
。
@Nullable
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[0]);
}
return this.interceptors;
}
关键是这个interceptorList
是在哪里把拦截器add()
进去的呢?
其实很简单,经过一路顺藤摸瓜,我们看到AbstractHandlerMapping
的getHandlerExecutionChain()
方法:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
//判断是否继承拦截器父类
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
//添加拦截器
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
//添加拦截器
chain.addInterceptor(interceptor);
}
}
//返回调用链
return chain;
}
那么上面这个方法又在什么时候被调用呢?我们一直往上找调用方。
首先是AbstractHandlerMapping
的getHandler()
方法
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取调用链
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//省略
return executionChain;
}
然后到了DispatcherServlet
的getHandler()
方法
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
//省略
//获取调用链
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
//不为null则返回
return handler;
}
}
}
return null;
}
最后回到了DispatcherServlet
的doDispatch()
方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 获取调用链方法!!!
mappedHandler = getHandler(processedRequest);
//省略
}
}
}
}
哈哈~~真相大白了!是在doDispatch()
方法里,获取调用链getHandler()
方法中组装好interceptorList
拦截器集合的!
用张图总结一下,就是这样,我用不同的颜色分步骤从浅到深标记了(求点赞):
除了SpringMVC
外,其实还有很多框架都使用了责任链模式,比如Servlet
的Filter
,还有Struts2
的Interceptor
等等。有兴趣的同学可以去看看源码,其实都大同小异,思想懂了之后,源码看起来就没那么费劲了。