Servlet过滤器简单探索
Posted Qcer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet过滤器简单探索相关的知识,希望对你有一定的参考价值。
过滤器的工作时机介于浏览器和Servlet请求处理之间,可以拦截浏览器对Servlet的请求,也可以改变Servlet对浏览器的响应。
其工作模式大概是这样的:
一、Filter的原理
在Servlet API中,过滤器接口Filter会依赖于FilterChain和FilterConfig两个接口,都位于package javax.servlet;包中。
在Tomcat容器中,ApplicationFilterChain和ApplicationFilterConfig两个类分别实现了FilterChain和FilterConfig两个接口。
Filter接口和FilterChain接口都有一个叫doFilter()的方法,Filter的doFilter()在web应用中编写具体的过滤器时,由继承Filter的实现类来重写;FilterChain接口的doFilter()方法由Tomcat容器实现了。
相关接口和类的结构关系如下图:
以下简单探索Filter的原理。
ApplicationFilterChain类在Tomcat源码中位于包org.apache.catalina.core中。
用ApplicationFilterConfig类型的数组filters保存了web应用中设置的若干个ApplicationFilterConfig实例,由ApplicationFilterConfig实例可以获得web中定义的Filter实例。因此在这种意义上,ApplicationFilterConfig实例也就的映射成Filter实例。
pos指针记录filters数组中当前的ApplicationFilterConfig实例索引。
n指针存储所有的ApplicationFilterConfig实例数量。
在ApplicationFilterChain中,主要实现了FilterChain的doFilter()方法。
//package org.apache.catalina.core; //继承javax.servlet.FilterChain(request,response)方法 @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);//调用内部私有的internalDoFilter()方法 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);//调用内部私有的internalDoFilter()方法 } }
内部私有的internalDoFilter()方法如下:
private void internalDoFilter(ServletRequest request,ServletResponse response) throws IOException, ServletException { if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { Filter filter = filterConfig.getFilter(); //其他处理省略 if( Globals.IS_SECURITY_ENABLED ) { //其他处理省略 //通过invoke()调用filter对象的doFilter()方法 SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal); } else { //直接调用filter对象的doFilter()方法 filter.doFilter(request, response, this); } } catch (IOException | ServletException | RuntimeException e) { //异常处理省略 } return; } try { //其他代码省略 if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) { //其他代码省略 //通过invoke()调用servlet对象的servicer()方法 SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal); } else { //直接调用servlet对象的service()方法 servlet.service(request, response); } } catch (IOException | ServletException | RuntimeException e) { //异常处理省略 } }
逻辑已经很清楚,当web应用设置了多了过滤器时(比如同时设置字符过滤器、请求参数编码过滤和静态资源过滤器三个过滤器)。会依次调用这三个Filter实例的doFilter方法,就相当于是一个任务链,完成之后,在调用servlet的service()方法正式处理请求逻辑。
总结起来,过滤器的工作流程大致如下:
请求到来,陆续调用各个Filter实例的doFilter()方法,然后在调用servlet的service()处理请求,之后在执行位于doFilter()方法后的代码处理逻辑,最后才返回给浏览器。
//service()前的处理逻辑 chain.doFilter(req, res); //service()后的处理逻辑
二、简单应用
常见的设置过滤器的场景为:
1、特殊字符过滤器(如html页面元素:<,>等);
2、请求参数编码过滤器(GET请求参数和POST请求参数编码设置);
3、登录检测过滤器(判定用户是否登录)等等。
如下针对2和3的场景实现一个过滤器:
package com.tms.web; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; public class TMSFilter implements Filter { private static Logger logger = Logger.getLogger(TMSFilter.class); public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } public void destroy() { // TODO Auto-generated method stub } public void doFilter(ServletRequest resquest, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletRequest req = (HttpServletRequest)resquest; HttpServletResponse res = (HttpServletResponse)response; logger.info(req.getRequestURL()); //用户登录检测 if (req.getSession().getAttribute("hasLoginedUsername") != null) { //请求参数编码设置 HttpServletRequest wreq = new TMSRequestWrapper(req,"UTF-8"); chain.doFilter(wreq, res); //service()后的处理逻辑 }else { res.sendRedirect("/tms/login"); } } }
通过继承HttpServletRequestWrapper类,覆盖其getParameter(String name)方法,将请求对象包装处理,使其针对GET和POST请求都能够正确的处理编码问题,这样的好处是集中在一个地方处理编码问题和登录检测,避免了在每个Servlet的doXXX()方法中去写这部分重复的代码,利于重构和维护,体现了DRY原则。
package com.tms.web; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public class TMSRequestWrapper extends HttpServletRequestWrapper { private String encoding = ""; public TMSRequestWrapper(HttpServletRequest req,String encoding){ super(req); this.encoding = encoding; } public String getParameter(String name){ HttpServletRequest req = (HttpServletRequest)getRequest(); String value = ""; try { //POST请求编码设置,直接通过setCharacterEncoding()来设置编码方式 req.setCharacterEncoding(encoding); value = req.getParameter(name); //如果为GET请求,通过获取字节数据再转化为String对象来设置编码方式 if("GET".equals(req.getMethod())){ if (value != null) { byte[] b = value.getBytes("ISO-8859-1");//ISO-8859-1 value = new String(b, encoding); } } } catch (Exception e) { // TODO: handle exception new RuntimeException(e); } return value; } }
参考资料:
1、《深入分析java web技术内幕》
2、《Servlet和JSP笔记》
3、apache-tomcat-9.0.1-src
以上是关于Servlet过滤器简单探索的主要内容,如果未能解决你的问题,请参考以下文章
python 用于数据探索的Python代码片段(例如,在数据科学项目中)