职责链模式
定义
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
多个处理器依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理完后传递给C,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责。职责链模式实现有多种。
类图
实现
实现一个敏感词过滤。
1. 定义过滤算法接口
public interface SensitiveWordFilter {
boolean doFilter(Content content);
}
2. 定义过滤算法实现类
/**
* 敏感词过滤一
*/
public class SexyWordFilter implements SensitiveWordFilter{
@Override
public boolean doFilter(Content content) {
if (content.content.contains("性")) {
return false;
}
return true;
}
}
/**
* 敏感词过滤二
*/
public class PoliticalWorldFilter implements SensitiveWordFilter{
@Override
public boolean doFilter(Content content) {
if (content.content.contains("政治")) {
return false;
}
return true;
}
}
3. 创建Filter Chain
/**
* 通过集合保存过滤算法
* 暴露filter执行敏感词过滤。这只是其中的职责链的变种。
*/
public class SenstiveWordFilterChain {
private List<SensitiveWordFilter> filterList = new ArrayList<>();
public void addWordFilter(SensitiveWordFilter filter) {
this.filterList.add(filter);
}
public boolean filter(Content content) {
for (SensitiveWordFilter sensitiveWordFilter : filterList) {
if (!sensitiveWordFilter.doFilter(content)) {
return false;
}
}
return true;
}
}
4. Main
/**
* 1. 职责链模式很好体现单一职责原则。业务逻辑判断 设计成独立的类,通过接口方便扩展。
* 2. 职责链模式也体现开闭原则。如果需要新添加过滤算法,我们只需要实现Filter接口,并通过addWordFitler添加到职责链即可。其它代码不需要修改,进而提高代码的扩展性。
*/
public class M {
public static void main(String[] args) {
SenstiveWordFilterChain senstiveWordFilterChain = new SenstiveWordFilterChain();
senstiveWordFilterChain.addWordFilter(new PoliticalWorldFilter());
senstiveWordFilterChain.addWordFilter(new SexyWordFilter());
Content content = new Content();
content.content = "2333";
boolean filter = senstiveWordFilterChain.filter(content);
if (filter) {
System.out.println("可以正常发布");
} else {
System.out.println("不允许发布。因为包含敏感信息!");
}
}
}
Servlet Filter
Servlet Filter
是Java Servlet
规范中定义的组件。它可以对HTTP请求进行过滤,比如鉴权、限流、日志记录、参数验证等。是Servlet
规范的一部分。所以,只要是支持Servlet
的Web容器(Tomcat、Jetty),都支持过滤器功能。
步骤:
- 实现Filter
- 在
Web.xml
配置文件中添加filter-mapping
实现
1. 定义Filter实现类
public class LogFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 在创建Filter时调用
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 拦截客户端发送的请求
chain.doFilter(request, response);
// 拦截发送给客户端的响应
}
@Override
public void destory() {
// 在销毁filter时调用
}
}
2. Tomcat提供FilterChain的实现类
public final class ApplicationFilterChain implements FilterChain {
// 当前执行到了哪个filter
private int pos = 0;
// filter个数
private int n;
// 使用数组存储Filter
private ApplicationFilterConfig[] filters;
private Servlet servlet;
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
} else {
// filter都处理完毕后,执行servlet
servlet.service(request, response);
}
}
public void addFilter(ApplicationFilterConfig filterConfig) {
for (ApplicationFilterConfig filter:filters)
if (filter==filterConfig)
return;
if (n == filters.length) {
//扩容
ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + IN];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
}
该doFilter
实现比较有技巧性。使用递归保留之前过滤器的快照,目的是支持双向拦截,既能拦截客户端发送的请求,也能拦截发送给客户端的响应。你可以把这个递归操作看成压栈操作。
Spring Interceptor
可以和Servlet Filter
看作一个概念,都是用来对HTTP
请求进行拦截处理。不同之处在于
Servlet Filter
是Servlet
规范的一部分,实现依赖于Web
容器。而Spring Interceptor
是Spring MVC
的一部分,由Spring
框架提供实现。- 客户端的请求,先经过
Servlet Filter
,再经过Spring Interceptor
,最后到达具体业务代码。
实现
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("拦截客户端发送来的请求.");
return true; // 继续后续的处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response) {
System.out.println("拦截发送给客户端的响应.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response) {
System.out.println("这里总是被执行.");
}
}
<!--在Spring MVC配置文件中配置interceptors-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="com.xx.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
同样,Spring Interceptor
的底层也是基于职责链模式
实现。其中,HandlerExecutionChain
是职责链中的处理器链。它的实现相校于Tomcat,逻辑更加清晰,没有递归。主要原因是它将请求和响应的拦截工作拆分到了两个函数中实现。源码如下(做了裁剪):
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
}
}
return true;
}
// 在真正执行业务逻辑前,执行此函数
void applyPostHandle(HttpServletRequest request, HttpServletResponse response) {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
// 在真正执行业务逻辑后,执行此函数
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
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);
}
}
}
}
}
总结
-
职责链很好地符合
单一职责原则(每个算法都是独立的实现)
和开闭原则(扩展算法不需要修改其他代码,只需要修改客户端代码。也可以在类中做标记,比如实现某个标记接口,然后通过Spring的Bean扫描并装载到职责链内)
-
职责链本意是依次处理同一个请求,用户可以自定义该请求是否需要经历所有处理器,不会中途停止; 还是说当某个处理器有能力处理请求就完成职责链对该请求的处理。
-
职责链具有
复用
和扩展
能力。在实际的项目开发中比较常用。常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的拦截功能。 -
Double Filter、Netty ChannelPipeline
也是职责链模式
的实际应用案例。 -
AOP、Servlet Filter、Spring Interceptor
三者区别Servlet Filter
可以获取原始HTTP
请求,却无法获取请求的控制器(Controller)以及方法信息。粒度是HttpRequest、HttpResponse
。Spring Interceptor
可以拿到请求的控制器和方法,无法获取到请求方法的参数。粒度是HttpRequest、HttpResponse
。Spring AOP
有能力获取方法的参数,却无法获取HTTP请求和响应对象。粒度是类
。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。