Spring 编码过滤 -- 解析Filter实现原理
一、使用Spring进行编码过滤
Spring中的字符集过滤器可以很方便的为我们解决项目中出现的中文乱码问题,而且使用方法也很简单,只需要在web.xml文件中配置一下该过滤器,设置两个重要的参数(encoding和forceEncoding)即可.
<filter> <filter-name>EncodingFilter</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>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
二、过滤器(Filter)的概念
-
过滤器位于客户端和web应用程序之间,用于检查和修改两者之间流过的请求和响应。
-
在请求到达Servlet/JSP之前,过滤器截获请求。
-
在响应送给客户端之前,过滤器截获响应。
-
多个过滤器形成一个过滤器链,过滤器链中不同过滤器的先后顺序由部署文件web.xml中过滤器映射的顺序决定。
-
最先截获客户端请求的过滤器将最后截获Servlet/JSP的响应信息。
三、实现过滤器
可以通过实现javax.servlet.Filter类来实现自定义过滤器。
public class MyFilter implements Filter { public void init(FilterConfig fc) { //过滤器初始化代码 } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { //在这里可以对客户端请求进行检查 //沿过滤器链将请求传递到下一个过滤器。 chain.doFilter(request, response); //在这里可以对响应进行处理 } catch (IOException e) { e.printStackTrace(); } catch (ServletException e) { e.printStackTrace(); } } public void destroy( ) { //过滤器被销毁时执行的代码 } }
四、过滤器的链式结构
可以为一个Web应用组件部署多个过滤器,这些过滤器组成一个过滤器链,每个过滤器只执行某个特定的操作或者检查。这样请求在到达被访问的目标之前,需要经过这个过滤器链。
五、如何实现链式结构
其实Filter的链式结构是使用了Java中的责任链模式。下面我用简单的例子来实现责任链模式,然后再来讲解Spring容器中的实现。下面是我整个的包结构:
然后我再来详细说明每个类的说明: 1、Filter.java。用来模拟Servlet中的Filter.java.
public interface Filter { void doFilter(Request request, Response response, FilterChain filterChain); }
2、Request.java。用来模拟Servlet中HttpServletRequest.java。
@Data public class Request { private String requestStr; }
3、Response.java。用来模拟Servlet中的HttpServletResponse.java。
@Data public class Response { private String responseStr = "responseStr"; }
4、HtmlFilter.java。用来把”:)”转换成”-”,并且标记执行顺序
public class HtmlFilter implements Filter { @Override public void doFilter(Request request, Response response, FilterChain filterChain) { String newRequestStr = request.getRequestStr().replace(":)", "^-^") + "----HtmlFilter"; request.setRequestStr(newRequestStr); filterChain.doFilter(request, response, filterChain); String newResponseStr = response.getResponseStr() + "----HtmlFilter"; response.setResponseStr(newResponseStr); } }
5、TextFilter.java。用来做字符串转换
public class TextFilter implements Filter { @Override public void doFilter(Request request, Response response, FilterChain filterChain) { String newRequestStr = request.getRequestStr().replace("中国10.1", "中国") + "----TextFilter"; request.setRequestStr(newRequestStr); filterChain.doFilter(request, response, filterChain); String newResponseStr = response.getResponseStr() + "----TextFilter"; response.setResponseStr(newResponseStr); } }
6、FilterChain.java。用来模拟Servlet中的FilterChain,只不过这个类是一个接口。这个类同时也是实现了Filter这个接口,这样设计更加精巧。等下我们对比一下Spring中的对ServletChain的实现就可以发现了。
public class FilterChain implements Filter { private List<Filter> filters = Lists.newArrayList(); private int currentPosition = 0; public FilterChain addFilter(Filter filter){ this.filters.add(filter); return this; } @Override public void doFilter(Request request, Response response, FilterChain filterChain) { if (this.currentPosition == this.filters.size()) { return; } this.currentPosition++; Filter nextFilter = this.filters.get(this.currentPosition - 1); nextFilter.doFilter(request, response, this); } }
7、Client.java。用来测试结果。
public class Client { public static void main(String[] args) { String requestStr = "1111111,2222222:),中国10.1国庆节...."; Request request = new Request(); request.setRequestStr(requestStr); Response response = new Response(); FilterChain filterChain = new FilterChain(); filterChain.addFilter(new HtmlFilter()).addFilter(new TextFilter()); filterChain.doFilter(request, response, filterChain); System.out.println(request.getRequestStr()); System.out.println(response.getResponseStr()); } }
我们先来看看运行结果,再来分析实现原理。运行结果如下:
对比图中标注,我们不难发现。requst进去的时候先是HtmlFilter,然后才是TextFilter.但是response返回的时候反过来了,先是TextFilter,再是HtmlFilter。这样是不是就实现了过滤器链式结构。完成这个功能我们首先要看看并记住HtmlFilter中代码结构,其实TextFilter的代码结构也一样。
我们不难发现,这是不是与实现Servlet中Filter(过滤器)的逻辑一样呢? 然后我们再来看一下FilterChain中的业务逻辑。
由上面的图片我们不难发现,就是这个FilterChain会遍历的实现各个Filter的处理,那么它是怎么实现了request是顺序执行,而response是倒序执行呢? 下面我们来由代码来分析一下,我们再回过头去看看HtmlFilter中的代码结构,TextFilter代码结构也是一样的。
- 先处理request逻辑
- 调用Filter的doFilter()
-
处理response逻辑 然后我们再来分析一下FilterChain的代码逻辑。
1. 首先我们有2个Filter,我们暂且把它命名为A与B吧。
2. 当Client首次调用doFilter时,标记位是0,不等于filters的size.标记位++,标记位值为1,然后获取到size为0也就是第一个A这个filter执行doFilter方法。
3. 根据我们刚才对HtmlFilter的分析,先执行request处理逻辑,然后调用FilterChain的doFilter方法(请记住response处理逻辑还未执行)。
4. 然后又调用到FilterChain中的doFilter方法。由于刚才的标记位++了这个时候是1,然后又不等于filters的size。标记位++为2,然后就获取到size等于1的Filter也就是B了。B再执行doFilter方法。
5. 然后就跳到第3步了,执行B的doFilter方法。先执行request的处理逻辑,然后调用FilterChain的doFilter方法(请记住response处理逻辑还未执行)。
6. 然后又调用到FilterChain中的doFilter方法。这个时候的标记位的值是2.等于filters的size,然后就return. 7. 对于return,我们可以使用递归的逻辑来理解,是不是层级一级一级的返回下调用,直到调用到最下层,处理完了最下层的逻辑,返回它就反向返回。Filter也可以这么来理解,一个Filter顺着request一级一级的往外执行,直到执行到最下层。然后response再反向返回。
8. 当然这个例子的filter只是2个,如果是多个也是同样的逻辑。
六、Spring中的FilterChain实现
其实Filter中的FilterChain实现是通过CompositeFilter的私有内部类VirtualFilterChain来实现的。我们来看一看源码:
public class CompositeFilter implements Filter { private List<? extends Filter> filters = new ArrayList<Filter>(); public void setFilters(List<? extends Filter> filters) { this.filters = new ArrayList<Filter>(filters); } /** * Initialize all the filters, calling each one's init method in turn in the order supplied. * @see Filter#init(FilterConfig) */ @Override public void init(FilterConfig config) throws ServletException { for (Filter filter : this.filters) { filter.init(config); } } /** * Forms a temporary chain from the list of delegate filters supplied ({@link #setFilters}) * and executes them in order. Each filter delegates to the next one in the list, achieving * the normal behavior of a {@link FilterChain}, despite the fact that this is a {@link Filter}. * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain) */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { new VirtualFilterChain(chain, this.filters).doFilter(request, response); } /** * Clean up all the filters supplied, calling each one's destroy method in turn, but in reverse order. * @see Filter#init(FilterConfig) */ @Override public void destroy() { for (int i = this.filters.size(); i-- > 0;) { Filter filter = this.filters.get(i); filter.destroy(); } } private static class VirtualFilterChain implements FilterChain { private final FilterChain originalChain; private final List<? extends Filter> additionalFilters; private int currentPosition = 0; public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) { this.originalChain = chain; this.additionalFilters = additionalFilters; } @Override public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, ServletException { if (this.currentPosition == this.additionalFilters.size()) { this.originalChain.doFilter(request, response); } else { this.currentPosition++; Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1); nextFilter.doFilter(request, response, this); } } } }
更多学习: Spring全家桶:面试涨薪题解[45讲]+技术底层原理源码[99讲],Spring→Boot→MVC→Cloud→SpringSecurity,死磕就对了!
长期分享Java面试、面经、学习、架构笔记