• SpringMVC源码阅读:过滤器


    1.前言

    SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧

    本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC过滤器是如何执行的,并自定义过滤器,分清楚过滤器和拦截器的区别

    2.源码分析

    web.xml配置

        <filter>
            <!--过滤器名称-->
            <filter-name>Set Character Encoding</filter-name>
            <!--过滤器处理类-->
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <!--初始化参数-->
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <init-param>
                <param-name>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>Set Character Encoding</filter-name>
            <!--拦截路径-->
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    打开CharacterEncoding类,该类为request设置字符编码,打开类继承图

    CharacterEncodingFilter重写父类OncePerRequestFilter的doFilterInterval方法

    forceRequestEncoding为True

    188行获取编码

    191行为request设置编码

    194行为response设置编码

    197行调用FilterChain

    打开CharcterEncodingFilter的父类OncePerRequestFilter,OncePerRequestFilter保证请求分发执行一次Filter

    CharcterEncodingFilter实现Filter接口的doFilter方法,打开之

    96行获取属性,该属性表明这个方法是否被过滤

    98行hasAlreadyFilteredAttribute表明这个方法是否被过滤;skipDispatch方法判断是否跳过分发;shouldNotFilter方法默认返回False,被子类重写,然而,CharacterEncodingFilter类并未重写,ForwardedHeaderFilter重写该方法,暂时不看

    101行直接调用FilterChain的doFilter方法,FilterChain部分

    98行的上述条件都不满足,则进入105行,为request设置“已过滤”标识

    107行调用子类CharacterEncodingFilter的doFilterInterval方法

    继续深入,打开OncePerRequestFilter类的父类GenericFilterBean类

    重点看init方法,该方法启动服务才会进入,主要负责获取web.xml里配置的参数

     

    180行获取FilterConfig

    filterClass和filterName是我们在web.xml配置的属性

    184行获取<init-param>里属性

     

    185行声明BeanWrapper,一个通用的JavaBean接口,封装了setget方法,在SpringMVC源码阅读:属性编辑器、数据绑定我已经讲过

    186行声明资源加载器,加载classpath和文件系统

    189行给属性设置True

    199行initFilterBean方法被子类重写,用于补充初始化方法,OncePerRequestFilter未重写,我们暂时不看

    3.自定义过滤器

    现在Web项目基本都是前后端分离的,不可避免地要解决跨域问题。看Filter接口继承图,发现CorsFilter可以处理跨域,但是并不方便

    什么是跨域?

    前端地址和后台地址的

    IP一致,端口不一致

    IP不一致,端口不一致

    IP不一致,端口一致

    都属于跨域范畴

    前端代码如下

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title></title>
        <!-- <link rel="stylesheet" href=""> -->
        <style>
        </style>
    </head>
    
    <body>
        <script></script>
        <script type="text/javascript" src="jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            $.ajax({
                url: "http://localhost:8080/springmvcdemo/employee/detail/8",
                data: {},
                success: function (data) 
                {
                    console.log(html)
                },
                dataType: "json"
            });
        </script>
    
    </body>
    
    </html>

    将前端代码地址设置为http://localhost:1234,VSCode可以做,不赘述。后台代码地址http://localhost:8080,因为端口不一致,形成了跨域,不做跨域处理,直接访问有如下效果

    现在我们来自定义过滤器解决跨域问题

    3.1 自定义过滤器继承OncePerRequestFilter

    web.xml

        <filter>
            <!--过滤器名称-->
            <filter-name>CorsFilter</filter-name>
            <!--过滤器处理类-->
            <filter-class>org.format.demo.custom.ExtendedCorsFilter</filter-class>
        </filter>
    
        <filter-mapping>
            <filter-name>CorsFilter</filter-name>
            <!--拦截路径-->
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    ExtendedCorsFilter.java,重写doFilterInterval方法

    public class ExtendedCorsFilter extends OncePerRequestFilter{
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
            response.addHeader("Access-Control-Allow-Headers", "Content-Type");
            filterChain.doFilter(request,response);
        }
    
    }

    此时再运行前端代码,已经成功请求到结果

    Headers设置成功

    3.2 自定义过滤器实现Filter接口

    web.xml配置如下

     <filter>
            <!--过滤器名称-->
            <filter-name>CorsFilter</filter-name>
            <!--过滤器处理类-->
            <filter-class>org.format.demo.custom.ImplementedCorsFilter</filter-class>
            <!--初始化参数-->
            <init-param>
                <param-name>allowedOrigins</param-name>
                <param-value>*</param-value>
            </init-param>
            <init-param>
                <param-name>allowedMethods</param-name>
                <param-value>*</param-value>
            </init-param>
            <init-param>
                <param-name>allowedHeaders</param-name>
                <param-value>*</param-value>
            </init-param>
        </filter>

    ImplementedCorsFilter.java

    public class ImplementedCorsFilter implements Filter{
        //允许的请求域
        private String allowedOrigins;
        //允许的请求方法
        private String allowedMethods;
        //允许的请求头
        private String allowedHeaders;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            this.allowedOrigins = filterConfig.getInitParameter("allowedOrigins");
            this.allowedMethods = filterConfig.getInitParameter("allowedMethods");
            this.allowedHeaders = filterConfig.getInitParameter("allowedHeaders");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse rsp = (HttpServletResponse) response;
            rsp.setHeader("Access-Control-Allow-Origin", allowedOrigins);
            rsp.setHeader("Access-Control-Allow-Methods", allowedMethods);
            rsp.setHeader("Access-Control-Allow-Headers", allowedHeaders);
    
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
    
        }
    }

    相比继承OncePerRequestFilter方法,实现Filter接口可以通过重写init方法获取web.xml属性值,效果一致

    4.过滤器(Filter)和拦截器(Interceptor)的区别

    我在SpringMVC源码阅读:拦截器详细讲解了SpringMVC拦截器的工作原理

    过滤器先于拦截器执行,后于拦截器执行结束

    4.1 过滤器:

      依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据.

      比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等

    4.2 拦截器:

      依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于Web框架的调用。

      因此可以使用spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。

    总结:业务中尽量使用基于方法的拦截器,在进行一些需要统一处理的业务可以使用基于Servlet的过滤器

    5.总结:

    Filter接口用来执行过滤任务

    CompositeFilter实现filter,用到了组合设计模式

    抽象类GenericFilterBean实现Filter接口,负责解析web.xml的Filter的init-param中参数,是所有过滤器的父类。init方法解析web.xml的参数

    抽象类OncePerRequestFilter继承GenericFilterBean,doFilter方法根据hasAlreadyFilteredAttribute判断是否执行过滤

    CharacterEncodingFilter重写父类OncePerRequestFilter的doFilterInterval方法,调用FilterChain的doDilter方法执行过滤逻辑

    其他过滤器园友可以自行查看,不再赘述,demo源码

    6.参考

    https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

    https://docs.spring.io/spring/docs/current/javadoc-api/

    https://github.com/spring-projects/spring-framework

    文中难免有不足,欢迎指正

  • 相关阅读:
    JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)
    Java内部类详解 2
    内部类详解(很详细)
    《JAVA与模式》之简单工厂模式
    《JAVA与模式》之适配器模式
    多态
    java Final关键字
    java实例初始化块
    tween.js
    three.js 之旅 (三)
  • 原文地址:https://www.cnblogs.com/Java-Starter/p/10444617.html
Copyright © 2020-2023  润新知