【设计模式】行为型设计模式之 职责链模式,探究过滤器、拦截器、Mybatis插件的底层原理

一、介绍

职责链模式在开发场景中经常被用到,例如框架的过滤器、拦截器、还有 Netty 的编解码器等都是典型的职责链模式的应用。

标准定义

GOF 定义:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求,将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链条上的某个对象能够处理这个请求为止;

更常见的变体

实际上,职责链的实际应用中往往会更多的使用另一种变体,就是职责链上的每个对象都将请求处理一遍而不是遇到能处理的就终止!

二、代码举例

2.1 第一种:使用链表保存职责链

java 复制代码
//抽象的处理对象
public abstract class Handler {
    //链条的下一个处理对象
    protected Handler successor = null;

    public abstract void handle();
}


//处理对象实现 HandlerA
public class HandlerA extends Handler{

    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        //如果自己不能够处理,则交由链条的下一个处理对象处理
        if(!handled && successor!=null){
            successor.handle();
        }
    }
}

//处理对象实现 HandlerB
public class HandlerB extends Handler{

    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        //如果自己不能够处理,则交由链条的下一个处理对象处理
        if(!handled && successor!=null){
            successor.handle();
        }
    }
}
java 复制代码
//职责链的链表实现
public class HandlerChain{
    private Handler head=null;
    private Handler tail=null;

    public void addHandler(Handler handler){
        handler.setSuccessor(null);
        if(head==null){
            head=handler;
            tail=handler;
            return;
        }
        tail.setSuccessor(handler);
        tail=handler;
    }

    
    public void handle () {
        if(head !=null){
            head.handler;
        }
    }
}
java 复制代码
public class Application {
    public static void main (String[] args){
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handler();
    }
}

**存在的问题:**handler 函数中存在对下一个处理器的调用,一旦被忘掉就链条就断了。

**改进:**使用模板方法模式改进

使用模板方法改进版本
java 复制代码
//抽象的处理对象
public abstract class Handler {
    //链条的下一个处理对象
    protected Handler successor = null;
    //模板方法
    public final void handle(){
        boolean handled = doHandle();
        if(successor!=null && !handled){
            successor.handle();
        }
    }
    //抽象方法,由子类重写
    public abstract boolean doHandle();
}


//处理对象实现 HandlerA
public class HandlerA extends Handler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}


//处理对象实现 HandlerB
public class HandlerB extends Handler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}

2.2 第二种:使用数组保存职责链

java 复制代码
public interface IHandler{
    boolean handle();
}


//处理对象实现 HandlerA
public class HandlerA implements IHandler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}


//处理对象实现 HandlerB
public class HandlerB extends IHandler{
    @Override
    public void handle (){
        //是否能够处理,默认设置false
        boolean handled = false;
        ....
        return handled;
    }
}
java 复制代码
public class HandlerCHain{

    private List<IHandler> handlers = new ArrayList<>();

    public void addHandler(Handler handler){
        this.handlers.add(handler);
    }

    public void handle(){
        for(Ihandler handler : handlers){
            boolea handled = handler.handle();
            if(handled){
                break;
            }
        }
    }
}

三、应用举例

3.1 职责链模式过滤敏感词

处理敏感词有两种方式第一种是包含敏感词直接禁止发布,可以使用职责链模式的标准模式。

第二种则是包含敏感词后直接替换为其他符号后扔给下一个处理器处理,可以使用职责链的常见变体。

3.2 职责链模式在 Servlet Filter 中的应用剖析

Servlet Filter 介绍
  1. Servlet Filter 过滤器可以实现对 Http 请求的过滤功能,可以实现鉴权、限流、记录日志、参数验证等功能。
  2. Servlet Filter 是 Servlet 规范的一部分,只要支持 Servlet 规范的 Web 容器,例如 Tomcat、Jetty 等都支持过滤器功能。
过滤器使用举例
java 复制代码
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{
        System.out.println("请求路径上的处理");
        chain.doFilter(request,response);
        System.out.pringln("响应路径上的处理");
    }
    @Override
    public void destory(){
        //销毁filter时自动调用
    }
}
  1. 过滤器的使用非常简单,实现 Filter 接口后添加过滤器配置即可,这完美符合开闭原则.
  2. 过滤器中使用了职责链模式,其中 Filter 就是职责链的 Handler,FilterChain 对应着就是 HandlerChain
过滤器 FilterChain 简化源码简单解析
java 复制代码
// org.apache.catalina.core.ApplicationFilterChain 类的简化表示
public class ApplicationFilterChain {

    // 过滤器数组,存储了配置在某个servlet前的所有过滤器
    private ApplicationFilterConfig[] filters;
    
    // 链中的下一个实体,通常是目标Servlet
    private Servlet servlet;

    // 当前请求在过滤器链中的位置
    private int pos = 0; // 注意:实际实现中可能不会直接暴露此字段作为公共状态
    
    public ApplicationFilterChain(Filter[] filters, Servlet servlet) {
        this.filters = filters;
        this.servlet = servlet;
    }

    // 处理请求的主要方法
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // 如果还有过滤器待执行
        if (pos < filters.length) {
            // 获取当前要执行的过滤器
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter currentFilter = filterConfig.getFilter();
            // 调用过滤器的doFilter方法,将责任传递给下一个过滤器或最终的Servlet
            currentFilter.doFilter(request, response, this); // 注意这里将自身作为参数传递,形成链式调用
        } else {
            // 所有过滤器都已执行完毕,调用目标Servlet处理请求
            servlet.service(request, response);
        }
    }
}
  1. 这里的 doFilter 方法其实是一个递归调用,一直调用到最后一个 Filter 后,会调用真正的 Servlet 的 Service 方法执行具体的业务逻辑
  2. servlet 的 service 方法执行之后,会开始执行 Filter.doFilter 方法中chain.doFilter(request,response);之后的部分。也就是说,Servlet 的 filter 机制使用了 doFilter 方法的递归调用实现了一个请求和响应的双向拦截,非常巧妙。

3.3 职责链模式在 SpringMVC Interceptor 中的应用剖析

SpringMVC 拦截器介绍

SpringMVC 的拦截器 Interceptor 和上述的过滤器的作用十分相似,他们都是用来实现对 Http 请求实现拦截处理的功能。

不同之处在于

  1. Servlet 的过滤器是 Servlet 规范的一部分,由 Web 容器提供代码实现
  2. Spring Interceptor 是 SpringMVC 框架的一部分,由 SpringMVC 框架提供代码实现。拦截器只能拦截被扫描注册到 SpringMVC 的 dispatch 中的 Handler。对于一些静态资源,直接由 Web 容器管理的是无法被拦截器拦截的。
Spring MVC 的 Interceptor 使用举例
java 复制代码
@Component
public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在处理请求前执行
        System.out.println("Request URL: " + request.getRequestURL() + ", Start Time: " + System.currentTimeMillis());
        return true; // 返回true表示继续执行后续的Interceptor和Handler
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在Handler方法调用后,渲染视图前执行
        // 这里可以对ModelAndView进行操作,但本例中不做任何处理
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求处理完成之后执行,可以做清理工作
        long endTime = System.currentTimeMillis();
        System.out.println("Request URL: " + request.getRequestURL() + ", End Time: " + endTime + ", Duration: " + (endTime - request.getAttribute("startTime")));
    }
}
Spring MVC Interceptor 职责链实现分析

Interceptor 也是基于职责链模式实现的,其职责链的实现中的处理器链为 HandlerExecutionChain ,其简化源代码如下

java 复制代码
package org.springframework.web.servlet;

import java.util.ArrayList;
import java.util.List;

public class HandlerExecutionChain {

    private final Object handler;
    private List<HandlerInterceptor> interceptors;

    public HandlerExecutionChain(Object handler) {
        this.handler = handler;
        this.interceptors = null;
    }

    public HandlerExecutionChain(Object handler, List<HandlerInterceptor> interceptors) {
        this.handler = handler;
        this.interceptors = (interceptors != null ? new ArrayList<>(interceptors) : null);
    }

    public void addInterceptor(HandlerInterceptor interceptor) {
        if (this.interceptors == null) {
            this.interceptors = new ArrayList<>();
        }
        this.interceptors.add(interceptor);
    }

    public Object getHandler() {
        return this.handler;
    }

    public List<HandlerInterceptor> getInterceptors() {
        return this.interceptors;
    }

    // 应用所有拦截器的preHandle方法
    public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.interceptors != null) {
            for (HandlerInterceptor interceptor : this.interceptors) {
                if (!interceptor.preHandle(request, response, this.handler)) {
                    return false; // 如果任意拦截器返回false,则短路后续处理
                }
            }
        }
        return true;
    }

    // 应用所有拦截器的postHandle方法
    public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        if (this.interceptors != null) {
            for (HandlerInterceptor interceptor : this.interceptors) {
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

    // 触发所有拦截器的afterCompletion方法
    public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
        if (this.interceptors != null) {
            for (int i = this.interceptors.size() - 1; i >= 0; i--) {
                this.interceptors.get(i).afterCompletion(request, response, this.handler, ex);
            }
        }
    }
}

简化后的 DispatchServlet 代码

java 复制代码
public class DispatcherServlet extends HttpServletBean {

    // 省略了其他属性和方法...

    // 主要的请求分发方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取HandlerExecutionChain,这包括了Handler和一系列的Interceptor
        HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);

        // 尝试应用所有拦截器的preHandle方法
        if (!handlerExecutionChain.applyPreHandle(request, response)) {
            return; // 如果有拦截器返回false,直接返回,不再继续处理
        }

        // 此处省略了根据Handler实际执行业务逻辑的部分
        // 例如,通过反射调用Controller方法,处理异常等
        
        ModelAndView modelAndView = null; // 假设这是Controller方法执行后的结果
        
        // 应用所有拦截器的postHandle方法
        if (modelAndView != null) {
            handlerExecutionChain.applyPostHandle(request, response, modelAndView);
        }

        // 渲染视图的逻辑省略...

        // 最后,触发所有拦截器的afterCompletion方法
        triggerAfterCompletion(request, response, null); // 假设没有异常发生
    }

    // 获取HandlerExecutionChain的简化方法
    private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) throws Exception {
        // 实际上这里会通过HandlerMapping查找合适的Handler并创建HandlerExecutionChain
        Object handler = // ... 省略了查找逻辑
        List<HandlerInterceptor> interceptors = // ... 省略了获取拦截器的逻辑
        return new HandlerExecutionChain(handler, interceptors);
    }

    // 触发所有拦截器的afterCompletion方法的简化实现
    private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
        HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);
        handlerExecutionChain.triggerAfterCompletion(request, response, ex);
    }
}

SpirngMVC 的 HandlerExecutionChain 没有使用递归,而是将前置处理和后置处理拆成两个方法分别执行代码更加的直观。

DispatchServlet 类的在 doDispatch 中,会在调用 Handler 处理逻辑前后,分别调用过滤器链的 preHandler 方法

3.4 职责链模式在 MyBatis plugin 中的应用剖析

Mybatis 的插件机制和上面的过滤器、拦截器机制都很相似,都是在不修改原有流程代码的情况下拦截某些方法的调用进行链式处理。

MyBatis 插件的举例
java 复制代码
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlExecutionTimeInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(SqlExecutionTimeInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            long end = System.currentTimeMillis();
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = null;
            if (invocation.getArgs().length > 1) {
                parameter = invocation.getArgs()[1];
            }
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String sql = boundSql.getSql();
            long time = (end - start);
            logger.info("SQL: {} 耗时: {} ms", sql, time);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以在这里读取自定义配置,如果有的话
    }
}

Mybatis 插件通过@Intercepts注解来指定拦截器拦截的范围,这个注解嵌套@Signature注解 该注解通过三个参数,type、method、args 来指明要拦截的类,要拦截的方法名、要拦截的方法对应的参数。通过制定这三个元素我们就能完全确定要拦截的具体是哪个方法了。

Mybatis 插件机制原理

Mybatis 使用 Executor 类执行 SQL 语句,Executor 类会创建 StatementHandler、ParameterHandler 和 ResultSetHandler 三个类的对象,Executor 执行 sql 时这三个对象按照下方顺序被调用。只要拦截这几个类的方法就可以实现非常多的功能了。

  1. 首先使用 ParameterHandler 类来解析替换 SQL 中的占位符
  2. 使用 StatementHandler 来执行 SQL 语句
  3. 最后使用 ResultSetHandler 来封装 SQL 的结果

插件的执行过程

  1. Mybatis 解析配置后,将所有的拦截器加载到 InterceptorChain 中
java 复制代码
//这里包含着一个方法的反射调用
public class Invocation {
    privatre final Object target;
    privatre final Method method;
    privatre final Object[] args;
    //省略构造函数和getter方法
    public Object proceed() throws InvocatrionTargetException,IllegalAccessException{
      return method.invocation(target,args);  
    }
}

//这是拦截器的接口代码
public interface Interceptor{
    Object intercept(Invocation invocation) throw Throwable;
    Object plugin(Object target);
    void setProperties(Properties properties);
}


//InterceptorChain Mybatis解析配置后,所有注册的Interceptor都保存到这儿
public class InterceptorChain{
    private final List<Interceptro> interceptors = new ArrayList<Interceptor>();

    public Object pluginAll(Object target){
        //这里循环对代理对象进行重复代理
        for(Interceptor interceptor : interceptors){
            //这里的plugin方法调用的Plugin.wrap方法,生成了一个代理对象
            target = interceptor.plugin(target);
        }
        return target;
    }
}
  1. Mybatis 执行 SQL 的过程中,会创建 Executor、ParameterHandler、StatementHandler、ResultSetHandler 等四个对象创建的代码在 Configuration 中。这四个对象的创建代码都会调用 InterceptorChain 的pluginAll 方法。
  2. pluginAll 方法的逻辑很简单,就是循环调用 Interceptor 的 plgin 方法,这个方法一般我们都会直接调用 Plugin 的静态 wrapper 方法
  3. Plugin 的 wrapper 方法的逻辑
    1. 检测 Interceptor 上的签名中是否包含当前 target 的类
    2. 如果不包含,则原样返回 target 也就是上面四个对象
    3. 如果包含,则生成代理对象,代理逻辑如下
      1. 判断当前方法是否是拦截器拦截的方法,如果是则调用拦截器的 Interceptor 方法。拦截器的 intercept 方法传入的 Invocation 则是使用代理对象传入的方法也就是被多层代理的对象。
      2. 如果当前方法不是拦截器拦截的方法,那么则直接调用被代理的原始方法。
    4. 最终代理对象一层层执行后,最后执行的才是那四个对象的原始方法。
相关推荐
阿伟*rui40 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj3 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck3 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei3 小时前
java的类加载机制的学习
java·学习
Yaml45 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~5 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616885 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java6 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~6 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust