透过源码学习设计模式1—Servlet Filter和责任链模式

时间:2022-06-20
本文章向大家介绍透过源码学习设计模式1—Servlet Filter和责任链模式,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Servlet filter 一瞥

相信大家都熟悉Servlet中Filter过滤器,我们可以在servlet和servlet容器之间插入Filter过滤器来包装、预处理请求,或者包装、处理响应。过滤器本身不知道自己的顺序。而是由FilterChain按照web.xml中的配置顺序执行的,确切的说,按照<filter-mapping>的顺序执行的。而调用完chain.doFilter()方法后,即当filter链中的filter都按顺序执行完毕,像堆栈一样,filter又从最后的一个开始执行。

Filter实现类的示例如下:

import javax.servlet.*;
import java.util.*;
//实现 Filter 类
@Slf4j
public class LogFilter implements Filter  {
    public void  init(FilterConfig config) throws ServletException {
        // 获取初始化参数
        String preInfo = config.getInitParameter(“preInfo");
        // 输出初始化参数
        log.info("preInfo: " + preInfo);
    }
    public void  doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException {
        // 取得根目录所对应的绝对路径:
        String currentURL = request.getRequestURI(); 
        currentURL = currentURL.replaceAll("//+","/");
        log.info("请求地址:{}",currentURL);
        // 把请求传回过滤链
        chain.doFilter(request,response);
    }
    public void destroy( ){
        /* 在 Filter 实例被 Web 容器从服务移除之前调用 */
    }
}

每个Filter过滤器实现javax.servlet.Filter接口,其中包含一个doFilter()方法,该方法接受一个request,resonse对以及filterChain作为参数输入,filterChain实现javax.servlet.FilterChain接口(由servlet容器提供),当请求到来时,它将会管理与该请求相关的一系列过滤器的执行,当过滤器执行完毕,doFilter接下来就会调用servlet的service()方法。

想象一下,如果没有filter机制,我们可能会把所有的逻辑写在一处,里面堆积着一堆if….else,这样是违反“单一职责原则”的,就算我们把这些逻辑拆分为多个类,假如后期增加或删除某些节点,或者调整节点顺序,就要修改源码,进行严格测试。而servlet filter使用了责任链模式,很好地解决了这个设计问题。

责任链模式

责任链模式包含一份请求对象和一系列执行对象,每个执行对象都定义了可以执行哪些请求对象,剩下的请求对象就传递给链中的下一个执行对象。也可以添加新的执行对象到链尾,这样责任链和if ... else if ... else if ....... else … endif 在语义上比较类似。但他的条件模块可以在运行时动态编排和配置。

责任链模式解决的问题:

1. 请求者和接受者松散耦合

在责任链模式中,请求者并不知道接受者是谁,也不知道具体如何处理。请求者只负责向责任链发出请求就可以了,该模式下可以有多个接受者处理对象,每个接受者只负责处理自己的部分,其他的就交给其他的接受者去处理。请求在链中传递,接受者处理该请求,或者传递给链中下一个接受者。请求者不再和特定接受者紧密耦合。

2. 通过改变链内成员的或者调整它们的次序,允许你动态地新增或删除责任。

细看FilterChain

我们以tomcat中的ApplicationFilterFactory和ApplicationFilterChain为例,看看他做了什么,首先,我们看看它是怎么样动态编排一条责任链的呢:

ApplicationFilterFactory 的createFilterChain方法:

public static ApplicationFilterChain createFilterChain(ServletRequest request,            Wrapper wrapper, Servlet servlet) {
        // 如果没有相关servlet,返回null        if (servlet == null)            return null;
        // 创建、初始化一个FilterChain对象        ApplicationFilterChain filterChain = null;        if (request instanceof Request) {            Request req = (Request) request;            if (Globals.IS_SECURITY_ENABLED) {                // Security: Do not recycle                filterChain = new ApplicationFilterChain();            } else {                filterChain = (ApplicationFilterChain) req.getFilterChain();                if (filterChain == null) {                    filterChain = new ApplicationFilterChain();                    req.setFilterChain(filterChain);                }            }        } else {            // Request dispatcher in use            filterChain = new ApplicationFilterChain();        }
        filterChain.setServlet(servlet);        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
        // 获取上下文中的filterMapping        StandardContext context = (StandardContext) wrapper.getParent();        FilterMap filterMaps[] = context.findFilterMaps();
        // 如果没有 filter mappings, 完成。        if ((filterMaps == null) || (filterMaps.length == 0))            return (filterChain);
        // Acquire the information we will need to match filter mappings        DispatcherType dispatcher =                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
        String requestPath = null;        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);        if (attribute != null){            requestPath = attribute.toString();        }
        String servletName = wrapper.getName();
        // 添加相关path-mapped的filter到filterChain        for (int i = 0; i < filterMaps.length; i++) {            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {                continue;            }            if (!matchFiltersURL(filterMaps[i], requestPath))                continue;            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                context.findFilterConfig(filterMaps[i].getFilterName());            if (filterConfig == null) {                // FIXME - log configuration problem                continue;            }            filterChain.addFilter(filterConfig);        }
        // Add filters that match on servlet name second        for (int i = 0; i < filterMaps.length; i++) {            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {                continue;            }            if (!matchFiltersServlet(filterMaps[i], servletName))                continue;            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                context.findFilterConfig(filterMaps[i].getFilterName());            if (filterConfig == null) {                // FIXME - log configuration problem                continue;            }            filterChain.addFilter(filterConfig);        }
        // Return the completed filter chain        return filterChain;    }
           if (!matchFiltersURL(filterMaps[i], requestPath))                continue;            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                context.findFilterConfig(filterMaps[i].getFilterName());            if (filterConfig == null) {                // FIXME - log configuration problem                continue;            }            filterChain.addFilter(filterConfig);

后面截取的这段中,它会按路径(url path)匹配规则筛选Filter,匹配成功,则返回true,将该filter添加到filterChain,否则就不添加,这样针对不同的请求URL,形成了相应的filterChain,达到动态编排的效果。而在链具体执行过程中,FilterChain发挥了巨大的作用,看ApplicationFilterChain部分源码:

ApplicationFilterChain的dofilter方法和internalDoFilter()方法:

/**     *触发下一个filter,传递request,response,如果链中没有filter,就触发service()     *     * @param request The servlet request we are processing     * @param response The servlet response we are creating     *     * @exception IOException if an input/output error occurs     * @exception ServletException if a servlet exception occurs     */    @Override    public void doFilter(ServletRequest request, ServletResponse response)        throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {            final ServletRequest req = request;            final ServletResponse res = response;            try {                java.security.AccessController.doPrivileged(                    new java.security.PrivilegedExceptionAction<Void>() {                        @Override                        public Void run()                            throws ServletException, IOException {                            internalDoFilter(req,res);                            return null;                        }                    }                );            } catch( PrivilegedActionException pe) {                Exception e = pe.getException();                if (e instanceof ServletException)                    throw (ServletException) e;                else if (e instanceof IOException)                    throw (IOException) e;                else if (e instanceof RuntimeException)                    throw (RuntimeException) e;                else                    throw new ServletException(e.getMessage(), e);            }        } else {            internalDoFilter(request,response);        }    }
    private void internalDoFilter(ServletRequest request,                                  ServletResponse response)        throws IOException, ServletException {
        // 如果有,获取下一个过滤器        if (pos < n) {            ApplicationFilterConfig filterConfig = filters[pos++];            try {                Filter filter = filterConfig.getFilter();
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(                        filterConfig.getFilterDef().getAsyncSupported())) {                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);                }                if( Globals.IS_SECURITY_ENABLED ) {                    final ServletRequest req = request;                    final ServletResponse res = response;                    Principal principal =                        ((HttpServletRequest) req).getUserPrincipal();
                    Object[] args = new Object[]{req, res, this};                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);                } else {                    filter.doFilter(request, response, this);                }            } catch (IOException | ServletException | RuntimeException e) {                throw e;            } catch (Throwable e) {                e = ExceptionUtils.unwrapInvocationTargetException(e);                ExceptionUtils.handleThrowable(e);                throw new ServletException(sm.getString("filterChain.filter"), e);            }            return;        }
        // We fell off the end of the chain -- call the servlet instance        try {            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {                lastServicedRequest.set(request);                lastServicedResponse.set(response);            }
            if (request.isAsyncSupported() && !servletSupportsAsync) {                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,                        Boolean.FALSE);            }            // Use potentially wrapped request from this point            if ((request instanceof HttpServletRequest) &&                    (response instanceof HttpServletResponse) &&                    Globals.IS_SECURITY_ENABLED ) {                final ServletRequest req = request;                final ServletResponse res = response;                Principal principal =                    ((HttpServletRequest) req).getUserPrincipal();                Object[] args = new Object[]{req, res};                SecurityUtil.doAsPrivilege("service",                                           servlet,                                           classTypeUsedInService,                                           args,                                           principal);            } else {                servlet.service(request, response);            }        } catch (IOException | ServletException | RuntimeException e) {            throw e;        } catch (Throwable e) {            e = ExceptionUtils.unwrapInvocationTargetException(e);            ExceptionUtils.handleThrowable(e);            throw new ServletException(sm.getString("filterChain.servlet"), e);        } finally {            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {                lastServicedRequest.set(null);                lastServicedResponse.set(null);            }        }    }

ApplicationFilterChain 会调用doFilter(request, response)方法,在doFilter里面会调用internalDoFilter(request,response)方法,该方法拿到每个过滤器,然后过滤器调用自身的doFilter(request, response, filterChain)方法(实现了Filter接口)。

Filter接口doFilter定义如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

过滤器链里面的filter在调用dofilter完成后,会继续调用chain.doFilter(request,response)方法,而这个chain其实就是ApplicationFilterChain,所以调用过程又回到了上面调用dofilter和调用internalDoFilter方法,以此类推直到里面的过滤器全部执行。通过FilterChain,每个过滤器相当于间接拥有了下一个Filter的引用,将请求在链中传递,接受者处理相应请求或者将请求传递给下一个接受者。