59、职责链模式(下)

职责链模式常用来开发:框架的过滤器和拦截器,通过 Servlet Filter、Spring Interceptor 这两个 Java 开发中常用的组件,来具体讲讲它在框架开发中的应用

1、Servlet Filter

Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等
因为它是 Servlet 规范的一部分,所以只要是支持 Servlet 的 Web 容器(Tomcat、Jetty 等)都支持过滤器功能
image

1.1、示例

在实际项目中,我们该如何使用 Servlet Filter 呢,我写了一个简单的示例代码,如下所示
添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中
Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理

/**
 * Servlet Filter
 * ApplicationFilterChain 分析, 支持双向拦截
 */
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse rsp, FilterChain chain) throws IOException, ServletException {
        System.out.println("拦截客户端发送来的请求.");
        chain.doFilter(req, rsp);
        System.out.println("拦截发送给客户端的响应.");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
<!-- 在 web.xml 配置文件中如下配置 -->
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>com.zzw.LogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

从刚刚的示例代码中我们发现,添加过滤器非常方便,不需要修改任何代码,定义一个实现 javax.servlet.Filter 的类,再改改配置就搞定了,完全符合开闭原则
那 Servlet Filter 是如何做到如此好的扩展性的呢,我想你应该已经猜到了,它利用的就是职责链模式,我们剖析它的源码,详细地看看它底层是如何实现的

1.2、原理

在上一节课中我们讲到,职责链模式的实现包含处理器接口(IHandler)或抽象类(Handler),以及处理器链(HandlerChain)
对应到 Servlet Filter,javax.servlet.Filter 就是处理器接口,FilterChain 就是处理器链,接下来我们重点来看 FilterChain 是如何实现的

不过 Servlet 只是一个规范,并不包含具体的实现,所以 Servlet 中的 FilterChain 只是一个接口定义
具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如 ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类,源码如下所示

为了让代码更易读懂,我对代码进行了简化,只保留了跟设计思路相关的代码片段,完整的代码你可以自行去 Tomcat 中查看

public final class ApplicationFilterChain implements FilterChain {

    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 + INCREMENT]; // increment = 10
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;
    }
}

ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,实际上是一个递归调用
这样实现主要是为了在一个 doFilter() 方法中支持双向拦截,既能拦截客户端发送来的请求,也能拦截发送给客户端的响应
你可以用每个 Filter(比如 LogFilter)的 doFilter() 的代码实现,直接替换 ApplicationFilterChain 的第 12 行代码,一眼就能看出是递归调用了

// 我替换了一下,如下所示
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        Filter filter = filterConfig.getFilter();
        // filter.doFilter(request, response, this);

        // 把 filter.doFilter 的代码实现展开替换到这里
        System.out.println("拦截客户端发送来的请求.");
        chain.doFilter(request, response); // chain 就是 this
        System.out.println("拦截发送给客户端的响应.");
    } else {
        // filter 都处理完毕后, 执行 servlet
        servlet.service(request, response);
    }
}

2、Spring Interceptor

刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器
尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理,它们的不同之处在于

  • Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器
  • Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现

客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中
image

2.1、示例

在项目中,我们该如何使用 Spring Interceptor 呢,我写了一个简单的示例代码,如下所示
LogInterceptor 实现的功能跟刚才的 LogFilter 完全相同,只是实现方式上稍有区别

  • LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的
  • LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现
/**
 * <p>Spring MVC Interceptor
 * <p>HandlerExecutionChain 分析
 */
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截客户端发送来的请求.");
        return true; // 继续后续的处理
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截发送给客户端的响应.");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("这里总是被执行.");
    }
}
<!-- 在 Spring MVC 配置文件中配置 interceptors -->
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/*"/>
       <bean class="com.zzw.LogInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

2.2、原理

Spring Interceptor 底层也是基于职责链模式实现的,HandlerExecutionChain 类是职责链模式中的处理器链
它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现

public class HandlerExecutionChain {

    private final Object handler;
    private HandlerInterceptor[] interceptors;

    public void addInterceptor(HandlerInterceptor interceptor) {
        initInterceptorList().add(interceptor);
    }

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        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, ModelAndView mv) throws Exception {
        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, Exception ex) 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 框架中,DispatcherServlet 的 doDispatch() 方法来分发请求
它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 函数,用来实现拦截的功能
具体的代码实现很简单,你自己应该能脑补出来,这里就不罗列了,感兴趣的话,你可以自行去查看

posted @ 2023-07-09 12:02  lidongdongdong~  阅读(5)  评论(0编辑  收藏  举报