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

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

SpringMVC源码阅读:过滤器的更多相关文章

  1. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  2. SpringMVC源码阅读:属性编辑器、数据绑定

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  3. SpringMVC源码阅读:拦截器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  4. SpringMVC源码阅读:核心分发器DispatcherServlet

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

  5. SpringMVC源码阅读:定位Controller

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码分析,弄清楚Spr ...

  6. SpringMVC源码阅读:Controller中参数解析

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  7. SpringMVC源码阅读:Json,Xml自动转换

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  8. SpringMVC源码阅读:视图解析器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  9. SpringMVC源码阅读:异常解析器

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

随机推荐

  1. 设计模式之桥接模式 c++11

    参考http://blog.csdn.net/calmreason/article/details/50909321 桥接模式 实现与抽象之间由指针关联 调用sample类 实际是调用sample类保 ...

  2. Linux 第十二天

    文件系统 1.分区类型 主分区:总共最多只能分四个 扩展分区:只能有一个,也算作主分区的一种,也就是说主分区加扩展分区最多有四个.但是扩展分区不能存储数据和格式化,必须再划分成逻辑分区才能使用. 逻辑 ...

  3. drf7 分页组件

    DRF的分页 数据库有几千万条数据,这些数据需要展示,不可能直接从数据库把数据全部读取出来, 这样会给内存造成特别大的压力,有可能还会内存溢出,所以希望一点一点的取,那展示的时候也是一样的,总是要进行 ...

  4. 理解React组件的生命周期

    本文作者写作的时间较早,所以里面会出现很多的旧版ES5的时代的方法.不过,虽然如此并不影响读者理解组件的生命周期.反而是作者分为几种不同的触发机制来解释生命周期的各个方法,让读者更加容易理解涉及到的概 ...

  5. Alpha冲刺 - (4/10)

    Alpha冲刺 - (4/10)   Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 ...

  6. uniGUI日志的控制

    uniGUI日志的控制 (2015-10-12 08:30:29) 转载▼ 标签: unigui 分类: uniGUI uniGUI本身提供了日志功能,利用uniServerModule.Server ...

  7. NET Core微服务之路:SkyWalking+SkyApm-dotnet分布式链路追踪系统的分享

    对于普通系统或者服务来说,一般通过打日志来进行埋点,然后再通过elk或splunk进行定位及分析问题,更有甚者直接远程服务器,直接操作查看日志,那么,随着业务越来越复杂,企业应用也进入了分布式服务化的 ...

  8. iOS 不能加载电子签名

    问题: 1:使用WKWebView在iOS12.0以上的系统中,可以显示PDF中的电子图章,签名.在iOS12.0以下的系统中不能显示电子签名,图章. 2:  解决方案,使用PDF.js加载. pdf ...

  9. XCode 设置自定义环境变量

    XCode 设置自定义环境变量 Product -> Scheme -> Edit Scheme -> 之后设置环境变量.

  10. vue-cli脚手架项目按需引入elementUI

    https://www.jianshu.com/p/40da0ba35ac1 Couldn't find preset "es2015" relative to directory ...