zoukankan      html  css  js  c++  java
  • 惊呆了,Servlet Filter和Spring MVC Interceptor的实现居然这么简单

    前言

    创建型:单例模式,工厂模式,建造者模式,原型模式
    结构型:桥接模式,代理模式,装饰器模式,适配器模式,门面模式,组合模式,享元模式
    行为型:观察者模式,模板模式,策略模式,责任链模式,状态模式,迭代器模式,访问者模式

    介绍

    在工作中,我们经常要和Servlet Filter,Spring MVC Interceptor打交道,虽然我配置写的很6,但是出了问题还得到处google,于是看了一下源码,用Demo的方式来分析一下这两者是怎么工作的。

    Servlet Filter

    Filter的使用

    可能很多小伙伴没怎么用过Filter,我就简单演示一下

    1.在web.xml中配置2个Filter

    <filter-mapping>
        <filter-name>logFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>imageFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    2.实现如下,略去了init方法和destroy方法

    @WebFilter(filterName = "logFilter")
    public class LogFilter implements Filter {
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            System.out.println("LogFilter execute");
            chain.doFilter(request, response);
        }
    }
    
    @WebFilter(filterName = "imageFilter")
    public class ImageFilter implements Filter {
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            System.out.println("ImageFilter execute");
            chain.doFilter(request, response);
        }
    }
    

    3.然后你访问任意一个servlet方法,LogFilter和ImageFilter的doFilter方法都会执行

    如果你在一个Filter方法后不加chain.doFilter(request, response)
    则后续的Filter和Servlet都不会执行,这是为什么呢?看完我手写的Demo你一下就明白了

    可以看到Filter可以在请求到达Servlet之前做处理,如

    1. 请求编码
    2. 敏感词过滤等

    有兴趣的小伙伴可以看看相关的源码

    手写Filter的实现

    Servlet接口,任何一个web请求都会调用service方法

    public interface Servlet {
        public void service();
    }
    
    public class MyServlet implements Servlet {
        @Override
        public void service() {
            System.out.println("MyServlet的service方法执行了");
        }
    }
    

    拦截器接口

    public interface Filter {
        public void doFilter(FilterChain chain);
    }
    
    public class LogFilter implements Filter {
        @Override
        public void doFilter(FilterChain chain) {
            System.out.println("LogFilter执行了");
            chain.doFilter();
        }
    }
    
    public class ImageFilter implements Filter {
        @Override
        public void doFilter(FilterChain chain) {
            System.out.println("ImageFilter执行了");
            chain.doFilter();
        }
    }
    

    拦截器链对象

    public interface FilterChain {
        public void doFilter();
    }
    
    public class ApplicationFilterChain implements FilterChain {
    
        private Filter[] filters = new Filter[10];
        private Servlet servlet = null;
    
        // 总共的Filter数目
        private int n;
    
        // 当前执行完的filter数目
        private int pos;
    
        @Override
        public void doFilter() {
            if (pos < n) {
                Filter curFilter = filters[pos++];
                curFilter.doFilter(this);
                return;
            }
            servlet.service();
        }
    
        public void addFilter(Filter filter) {
            // 这里源码有动态扩容的过程,和ArrayList差不多
            // 我就不演示了,直接赋数组大小为10了
            filters[n++] = filter;
        }
    
        public void setServlet(Servlet servlet) {
            this.servlet = servlet;
        }
    }
    

    测试例子

    public class Main {
    
        public static void main(String[] args) {
            // 在tomcat源码中,会将一个请求封装为一个ApplicationFilterChain对象
            // 然后执行ApplicationFilterChain的doFilter方法
            ApplicationFilterChain applicationFilterChain = new ApplicationFilterChain();
            applicationFilterChain.addFilter(new LogFilter());
            applicationFilterChain.addFilter(new ImageFilter());
            applicationFilterChain.setServlet(new MyServlet());
    
            // LogFilter执行了
            // ImageFilter执行了
            // MyServlet的service方法执行了
            applicationFilterChain.doFilter();
        }
    }
    

    如果任意一个Filter方法的最后不加上chain.doFilter(),则后面的拦截器及Servlet都不会执行了。相信你看完ApplicationFilterChain类的doFilter方法一下就明白了,就是一个简单的递归调用

    Spring MVC Interceptor

    Interceptor的使用

    以前写过一篇拦截器应用的文章,有想了解使用方式的小伙伴可以看一下

    用Spring MVC拦截器做好web应用的安保措施

    今天就来分析一下拦截器是怎么实现的?可以通过以下方式实现拦截器

    1. 实现HandlerInterceptor接口
    2. 继承HandlerInterceptorAdapter抽象类,按需重写部分实现即可,(HandlerInterceptorAdapter也实现了HandlerInterceptor接口)

    总而言之拦截器必须必须实现了HandlerInterceptor接口

    HandlerInterceptor有如下3个方法

    boolean preHandler():在controller执行之前调用
    void postHandler():controller执行之后,且页面渲染之前调用
    void afterCompletion():页面渲染之后调用,一般用于资源清理操作

    在这里插入图片描述
    这个图应该很好的显示了一个请求可以被拦截的地方

    1. Servlet Filter是对一个请求到达Servlet的过程进行拦截
    2. 而HandlerInterceptor是当请求到达DispatcherServlet后,在Controller的方法执行前后进行拦截

    手写Interceptor的实现

    我来手写一个Demo,你一下就能明白了

    拦截接口,为了方便我这里就只定义了一个方法

    public interface HandlerInterceptor {
        boolean preHandle();
    }
    

    定义如下2个拦截器

    public class CostInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle() {
            // 这里可以针对传入的参数做一系列事情,我这里就简单返回true了;
            System.out.println("CostInterceptor 执行了");
            return true;
        }
    }
    
    public class LoginInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle() {
            System.out.println("LoginInterceptor 执行了");
            return true;
        }
    }
    

    存放拦截器的容器

    public class HandlerExecutionChain {
    
        private List<HandlerInterceptor> interceptorList = new ArrayList<>();
    
        public void addInterceptor(HandlerInterceptor interceptor) {
            interceptorList.add(interceptor);
        }
    
        public boolean applyPreHandle() {
            for (int i = 0; i < interceptorList.size(); i++) {
                HandlerInterceptor interceptor = interceptorList.get(i);
                if (!interceptor.preHandle()) {
                    return false;
                }
            }
            return true;
        }
    }
    

    演示DispatcherServlet的调用过程

    public class Main {
    
        public static void main(String[] args) {
            // Spring MVC会根据请求返回一个HandlerExecutionChain对象
            // 然后执行HandlerExecutionChain的applyPreHandle方法,controller中的方法
            HandlerExecutionChain chain = new HandlerExecutionChain();
            chain.addInterceptor(new CostInterceptor());
            chain.addInterceptor(new LoginInterceptor());
    
            // 只有拦截器都返回true,才会调用controller的方法
            // CostInterceptor 执行了
            // LoginInterceptor 执行了
            if (!chain.applyPreHandle()) {
                return;
            }
            result();
        }
    
        public static void result() {
            System.out.println("这是controller的方法");
        }
    }
    

    如果任意一个Interceptor返回false,则后续的Interceptor和Controller中的方法都不会执行原因在Demo中显而易见

    当想对请求增加新的过滤逻辑时,只需要定义一个拦截器即可,完全符合开闭原则。

    不知道你意识到没有Servlet Filter和Spring MVC Interceptor都是用责任链模式实现的

    来看看DispatcherServlet是怎么做的?和我们上面写的demo一模一样

    我们用servlet写web应用时,一个请求地址写一个Servlet类。
    而用了spring mvc后,整个应用程序只有一个Servlet即DispatcherServlet,所有的请求都发送到DispatcherServlet,然后通过方法调用的方式执行controller的方法

    DispatcherServlet的doDispatch方法源码如下,省略了一部分逻辑(所有的请求都会执行这个方法)

    protected void doDispatch() {
    
    	// 执行所有HandlerInterceptor的preHandle方法
    	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    		return;
    	}
    
    	// 执行controller中的方法
    	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    	// 执行所有HandlerInterceptor的postHandle方法
    	mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    

    Interceptor可以有如下用处

    1. 记录接口响应时间
    2. 判断用户是否登陆
    3. 权限校验等

    可以看到Servlet Filter和Spring MVC Interceptor都能对请求进行拦截,只不过时机不同。并且Servlet Filter是Servlet的规范,而Spring MVC Interceptor只能在Spring MVC中使用

    欢迎关注

    在这里插入图片描述

    参考博客

    [0]https://mp.weixin.qq.com/s/8AIRvz5HOgjw12PbsjZhCQ
    [1]https://www.cnblogs.com/xrq730/p/10633761.html
    filter源码分析
    [2]https://cloud.tencent.com/developer/article/1129724
    [3]https://www.jianshu.com/p/be47c9d89175

  • 相关阅读:
    vSphere存储
    NFS服务器搭建
    windows下部署 ISCSI存储
    使用命令修改ip地址
    磁盘操作
    OpenFiler安装与基本配置
    OSPF系列
    NAT
    VLAN系列
    Linux下DNS服务器的基本搭建
  • 原文地址:https://www.cnblogs.com/erlie/p/12741097.html
Copyright © 2011-2022 走看看