过滤器Filter
过滤器filter简介
Filter 是 Servlet 规范的三大组件之一,另外两个分别是servlet和listener。filter中文意思是过滤,可以在请求到达目标资源之前先对请求进行拦截过滤,即对请求进行一些处理; 也可以在响应到达客户端之前先对响应进行拦截过滤,即对响应进行一些处理。
WEB开发人员通过Filter技术,可以对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息、计算系统的响应时间等一些高级功能。
在 Servlet 规范中,有一个 javax.servlet.Filter 接口。实现了该接口的类称为过滤器,接口中有三个方法可以重写:
- init():初始化方法,即 Filter 被创建后,在后面用到的资源的初始化工作,可以在这里完成。
- doFilter():Filter 的核心方法,对于请求与响应的过滤,就是在这个方法中完成的。
- destroy():销毁方法。 Filter 被销毁前所调用执行的方法。对于资源的释放工作,可以在这里完成。
创建Filter过滤器的步骤
1.创建一个类实现javax.servlet.Filter接口
2.重写接口中的方法
3.在web.xml文件中配置Filter
创建一个类实现Filter接口并重写接口中的方法
1 package com.monkey1024.filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 12 public class MyFilter implements Filter{ 13 14 public MyFilter(){ 15 System.out.println("实例化"); 16 } 17 18 @Override 19 public void init(FilterConfig filterConfig) throws ServletException { 20 System.out.println("初始化"); 21 } 22 23 @Override 24 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 25 throws IOException, ServletException { 26 System.out.println("过滤请求"); 27 //通过过滤器继续访问资源 28 chain.doFilter(request, response); 29 System.out.println("过滤响应"); 30 } 31 32 @Override 33 public void destroy() { 34 System.out.println("销毁"); 35 } 36 37 38 39 }
注意:在Filter的doFilter方法内如果没有执行doFilter(request, response)方法,那么服务器中的资源是不会被访问到的。
在web.xml中配置filter,跟配置servlet类似:
1 <filter> 2 <filter-name>my</filter-name> 3 <filter-class>com.monkey1024.filter.MyFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>my</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
表示全路径匹配,Filter的全路径匹配只支持/*,不支持/
将项目部署到tomcat并启动成功之后,会在控制台中看到下面信息:
实例化
初始化
访问项目,会在控制台中看到下面信息:
过滤请求
过滤响应
正常关闭tomcat,会在控制台中看到下面信息:
销毁
通过上面的操作,可以得出Filter的生命周期如下:
- 当服务器启动,会创建Filter对象,并调用init方法,只调用一次.
- 当访问资源时,路径与Filter的拦截路径匹配,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法.
- 当服务器关闭时,会调用Filter的destroy方法来进行销毁操作.
一个Filter的生命周期跟servlet有些类似,需要经历实例化—>初始化—>doFilter—>销毁四个过程。
dispatcher 标签
在 filter-mapping 中还有一个子标签 dispatcher ,用于设置过滤器所过滤的请求类型。
其有四种取值:REQUEST、FORWARD、INCLUDE、ERROR,默认是REQUEST
- FORWARD
若请求是由一个 Servlet 通过 RequestDispatcher 的 forward()方法所转发的, 那么这个请求将被值为 FORWARD 的 Filter 拦截。即当前 Filter 只会拦截由RequestDispatcher 的 forward()方法所转发的请求。其它请求均不拦截。 - INCLUDE
当前 Filter 只会拦截由 RequestDispatcher 的 include()方法所转发的请求。其它请求均不拦截 - ERROR
在 web.xml 中可以配置错误页面 error-page ,当发生指定状态码的错误后,会跳转到指定的页面。而这个跳转同样是发出的请求。若的值设置为 EEROR,则当前过滤器只会拦截转向错误页面的请求,其它请求不会拦截。1 <error-page> 2 <error-code>404</error-code> 3 <location>/error.jsp</location> 4 </error-page>
- REQUEST
默认值。即不设置 dispatcher 标签,也相当于指定了其值为 REQUEST。只要请求不是由 RequestDispatcher 的 forward()方法或 include()方法转发的,那么该 Filter均会被拦截,即使是向错误页面的跳转请求,同样会被拦截。
Filter特征
- Filter 是单例多线程的。
- Filter 是在应用被加载时创建并初始化, 这是与 Servlet 不同的地方。 Servlet 是在该 Servlet被第一次访问时创建。 Filter 与 Servlet 的共同点是,其无参构造器与 init()方法只会执行一次。
- 用户每提交一次该 Filter 可以过滤的请求,服务器就会执行一次 doFilter()方法,即doFilter()方法是可以被多次执行的。
- 当应用被停止时执行 destroy()方法,Filter 被销毁,即 destroy()方法只会执行一次。
- 由于 Filter 是单例多线程的,所以为了保证其线程安全性,一般情况下是不为 Filter 类定义可修改的成员变量的。因为每个线程均可修改这个成员变量,会出现线程安全问题。
FilterConfig
在Filter中的init方法上有一个参数叫FilterConfig,这是Filter的配置对象,通过FilterConfig对象可以获取当前 Filter 在 web.xml中的配置信息,这与ServletConfig类似。一个 Filter 对象一个 FilterConfig 对象,多个 Filter 对象会有多个 FilterConfig 对象。它的作用:
- 获取Filter的名称
- 获取初始化参数
- 获取ServletContext对象
FilterConfig 接口中的方法与 ServletConfig 接口中的方法,方法名与意义完全相同。
1 package com.monkey1024.filter; 2 3 import java.io.IOException; 4 import java.util.Enumeration; 5 6 import javax.servlet.Filter; 7 import javax.servlet.FilterChain; 8 import javax.servlet.FilterConfig; 9 import javax.servlet.ServletContext; 10 import javax.servlet.ServletException; 11 import javax.servlet.ServletRequest; 12 import javax.servlet.ServletResponse; 13 14 /** 15 * FilterConfig 16 */ 17 public class ConfigFilter implements Filter { 18 FilterConfig fConfig; 19 20 public void init(FilterConfig fConfig) throws ServletException { 21 this.fConfig = fConfig; 22 } 23 24 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 25 //获取ServletContext 26 ServletContext sc = fConfig.getServletContext(); 27 System.out.println("ServletContext:" + sc); 28 29 //获取FilterName 30 String filterName = fConfig.getFilterName(); 31 System.out.println("filter name:" + filterName); 32 33 //获取指定的初始化参数值 34 String param = fConfig.getInitParameter("name"); 35 System.out.println(param); 36 37 //获取所有的初始化参数名称 38 Enumeration<String> names = fConfig.getInitParameterNames(); 39 while(names.hasMoreElements()){ 40 String name = names.nextElement(); 41 String value = fConfig.getInitParameter(name); 42 System.out.println(name + ":" + value); 43 } 44 45 //转向下个资源 46 chain.doFilter(request, response); 47 } 48 49 public void destroy() { 50 51 } 52 53 54 55 }
在web.xml文件中注册Filter:
1 <filter> 2 <filter-name>ConfigFilter</filter-name> 3 <filter-class>com.monkey1024.filter.ConfigFilter</filter-class> 4 <init-param> 5 <param-name>name</param-name> 6 <param-value>monkey1024</param-value> 7 </init-param> 8 <init-param> 9 <param-name>password</param-name> 10 <param-value>123456</param-value> 11 </init-param> 12 </filter> 13 <filter-mapping> 14 <filter-name>ConfigFilter</filter-name> 15 <url-pattern>/*</url-pattern> 16 </filter-mapping>
多个Filter的执行过程
若web应用中配置了多个 Filter,那么这些 Filter 的执行过程是以“链”的方式执行的。即会将这
些与请求相匹配的 Filter 串成一个可执行的“链”,然后按照这个链中的顺序依次执行。这些 Filter 在链中的顺序与它们在 web.xml 中的注册顺序相同,即 web.xml 中的注册顺序就是 Filter 的执行顺序。
一个 Filter 的执行完毕,转而执行另一个 Filter,这个转向工作是由 FilterChain 的 doFilter()方法完成的。当然,若当前 Filter 是最后一个 Filter,则 FilterChain 的 doFilter()会自动转向最终的请求资源。
当请求到达 Filter 后,Filter 可以拦截到请求对象,并对请求进行修改。修改过后,再将
该修改过的请求转向下一个资源。
当最终的资源执行完毕,并形成响应对象后,会按照请求访问 Filter 的倒序,再次访问Filter。此时 Filter 可以拦截到响应对象,并对响应进行修改。最终,客户端可以收到已被修改过的响应。
使用过滤器Filter解决乱码问题
在之前编写servlet时,需要在每个servlet中声明编码,这种方式比较繁琐,下面使用Filter解决该问题。
定义一个Filter
1 package com.monkey1024.filter; 2 3 import java.io.IOException; 4 import javax.servlet.Filter; 5 import javax.servlet.FilterChain; 6 import javax.servlet.FilterConfig; 7 import javax.servlet.ServletException; 8 import javax.servlet.ServletRequest; 9 import javax.servlet.ServletResponse; 10 11 /** 12 * 字符编码Filter 13 */ 14 public class CharacterEncodingFilter implements Filter { 15 16 public void init(FilterConfig fConfig) throws ServletException { 17 18 } 19 public void destroy() { 20 21 } 22 23 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 24 //设置post请求的编码 25 request.setCharacterEncoding("utf-8"); 26 //设置响应编码 27 response.setContentType("text/html;charset=utf-8"); 28 29 chain.doFilter(request, response); 30 } 31 32 33 34 }
在web.xml文件中注册Filter
1 <filter> 2 <filter-name>CharacterEncodingFilter</filter-name> 3 <filter-class>com.monkey1024.filter.CharacterEncodingFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>CharacterEncodingFilter</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
定义一个LoginServlet来测试是否真正解决乱码问题并在web.xml文件中注册
1 package com.monkey1024.servlet; 2 3 import java.io.IOException; 4 import javax.servlet.ServletException; 5 import javax.servlet.http.HttpServlet; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 9 /** 10 * 登录 11 */ 12 public class LoginServlet extends HttpServlet { 13 private static final long serialVersionUID = 1L; 14 15 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 16 String username = request.getParameter("username"); 17 System.out.println(username); 18 19 response.getWriter().print("用户名为:" + username); 20 } 21 22 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 23 doGet(request, response); 24 } 25 26 }
编写JSP:
1 <form action="${pageContext.request.contextPath }/login" method="post"> 2 用户名:<input type="text" name="username"> 3 <input type="submit" value="登录"> 4 </form>
同时解决get和post乱码问题
上面的代码中只能解决post方式的乱码问题,然而对于get方式依然不起作用。
下面我们再来分析一下乱码产生的原因:当浏览器将包含有中文(例如 UTF-8 编码)参数的请求(无论是 GET 还是 POST 请求),以字节序列的形式发送到服务器后,服务器会按照其默认的字符编码ISO8859-1 进行解码,并将解码后的字符存放到 ParameterMap 中。此时的 ParameterMap 中存放的字符其实已经是乱码了,因为将 UTF-8 的字符序列解码为ISO8859-1 的字符,当然会出现乱码。要从根本上解决这个乱码问题,我们可以将ParameterMap中的参数重新按照UTF-8进行编码,这样Servlet 再从 ParameterMap 中读取参数,就不会出现乱码了。
不过向 ParameterMap 中存放数据是由服务器自动完成的,“向 ParameterMap中存放数据”这个时间点程序员无法捕获。我们的解决方案思路是,自定义一个HttpServletRequest 类型,该类型是HttpServletRequest 的一个装饰者。让这个装饰者重写HttpServletRequest 中请求参数相关方法。例如,重写 getParameterMap()方法,在该方法中定义一个 Map,并将原始 ParameterMap 中存放的乱码问题解决后,将数据存放到这个新的Map 中。然后,再重写其它参数相关方法,让这些方法获取请求参数,直接从这个新的 Map中获取。这样乱码问题就得以解决。也就是说,将来整个应用中所有的请求对象将使用我们自定义的这个请求的装饰者。这个替换工作可以通过过滤器完成,即所有请求到达应用后,首先经过这个过滤器,将HttpServletRequest 请求替换为装饰者。
修改上面代码中的Filter中添加代码如下:
1 // 自定义request对象 2 class MyRequest extends HttpServletRequestWrapper { 3 4 private HttpServletRequest request; 5 6 private boolean hasEncode; 7 8 public MyRequest(HttpServletRequest request) { 9 super(request);// super必须写 10 this.request = request; 11 } 12 13 // 对需要增强方法 进行覆盖 14 @Override 15 public Map getParameterMap() { 16 // 先获得请求方式 17 String method = request.getMethod(); 18 if (method.equalsIgnoreCase("post")) { 19 // post请求 20 try { 21 // 处理post乱码 22 request.setCharacterEncoding("utf-8"); 23 return request.getParameterMap(); 24 } catch (UnsupportedEncodingException e) { 25 e.printStackTrace(); 26 } 27 } else if (method.equalsIgnoreCase("get")) { 28 // get请求 29 Map<String, String[]> parameterMap = request.getParameterMap(); 30 if (!hasEncode) { // 确保get手动编码逻辑只运行一次 31 for (String parameterName : parameterMap.keySet()) { 32 String[] values = parameterMap.get(parameterName); 33 if (values != null) { 34 for (int i = 0; i < values.length; i++) { 35 try { 36 // 处理get乱码 37 values[i] = new String(values[i] 38 .getBytes("ISO-8859-1"), "utf-8"); 39 } catch (UnsupportedEncodingException e) { 40 e.printStackTrace(); 41 } 42 } 43 } 44 } 45 hasEncode = true; 46 } 47 return parameterMap; 48 } 49 50 return super.getParameterMap(); 51 } 52 53 @Override 54 public String getParameter(String name) { 55 Map<String, String[]> parameterMap = getParameterMap(); 56 String[] values = parameterMap.get(name); 57 if (values == null) { 58 return null; 59 } 60 return values[0]; // 取回参数的第一个值 61 } 62 63 @Override 64 public String[] getParameterValues(String name) { 65 Map<String, String[]> parameterMap = getParameterMap(); 66 String[] values = parameterMap.get(name); 67 return values; 68 } 69 70 }
使用Filter控制权限
当用户访问某网站时,有些页面或 Servlet 在不登录的情况下是可以访问的,例如首页、登录页面等。除此之外大部分资源是必须登录后才能访问的。此时,可以定义一个权限过滤器,对每一个访问该应用的请求进行过滤:若具有访问权限,则直接跳转到相应资源即可;若不具有访问权限,则跳转到登录页面。
修改一下之前写过的登录功能,当用户没有登录时,不能访问login_success.jsp页面。
首先在WebConten目录下创建一个success文件夹,之后将login_success.jsp页面移至该文件夹下。
之后创建一个Filter,根据session中是否含有user属性来判断用户是否登录:
1 package com.monkey1024.filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 import javax.servlet.http.HttpSession; 14 15 import com.monkey1024.javabean.User; 16 17 /** 18 * 权限过滤器 19 */ 20 public class PermissionFilter implements Filter { 21 22 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 23 HttpServletRequest req = (HttpServletRequest)request; 24 HttpServletResponse res = (HttpServletResponse)response; 25 26 //获取请求路径 27 String path = req.getServletPath(); 28 29 //判断请求路径是是否包含/success 30 if(path.startsWith("/success")){ 31 HttpSession session = req.getSession(); 32 //如果为null则说明session还未创建,跳转到登录页面 33 if(session == null){ 34 res.sendRedirect(req.getContextPath() + "/login.jsp"); 35 }else{ 36 User user = (User)session.getAttribute("user"); 37 //如果为null则说明还没有登录成功,跳转到登录页面 38 if(user == null){ 39 res.sendRedirect(req.getContextPath() + "/login.jsp"); 40 } 41 } 42 43 } 44 45 chain.doFilter(request, response); 46 } 47 48 public void destroy() { 49 } 50 51 public void init(FilterConfig fConfig) throws ServletException { 52 } 53 54 }
web.xml文件如下:
1 <filter> 2 <filter-name>PermissionFilter</filter-name> 3 <filter-class>com.monkey1024.filter.PermissionFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>PermissionFilter</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
将应用部署到tomcat并启动成功之后,在浏览器中访问/success/login_success.jsp,此时系统会跳转到login.jsp页面,从而实现了在用户没有登录的情况下不能访问/success/login_success.jsp页面。
使用Filter过滤非法IP
有些web系统中会遭受一些不友好的访问,例如在论坛中发送不健康的帖子,博客留言中骂人,这时可以将该用户的ip地址封掉,即该ip地址发出request请求后不进行处理,这时就可以使用Filter来实现。
首先创建一个IPUtil类,里面初始化一些要禁止的IP(实际开发中会从数据库中读取),有时也可将某些ip段屏蔽,例如:192.168.1.*,此时在该ip段内的用户是无法访问系统的,缺点是可能会屏蔽正常访问系统的用户。
1 package com.monkey1024.util; 2 3 4 import java.util.ArrayList; 5 import java.util.List; 6 7 public class IPUtil { 8 9 private static List<String> ipList = new ArrayList<>(); 10 11 12 static { 13 ipList.add("192.168.1.102"); 14 ipList.add("0:0:0:0:0:0:0:1");//本机的ip 15 } 16 17 public static List<String> getIpList() { 18 return ipList; 19 } 20 }
然后创建Filter过滤器来过滤ip,如果请求的ip在禁用ip段内,就会直接return,即该请求不会到达目标资源就会被response:
1 package com.monkey1024.filter; 2 3 import com.monkey1024.util.IPUtil; 4 5 import javax.servlet.*; 6 import javax.servlet.annotation.WebFilter; 7 import java.io.IOException; 8 import java.util.List; 9 10 @WebFilter(filterName = "Filter",urlPatterns = "/*") 11 public class Filter implements javax.servlet.Filter { 12 public void destroy() { 13 } 14 15 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { 16 String ip = req.getRemoteAddr(); 17 List<String> ipList = IPUtil.getIpList(); 18 System.out.println(ip); 19 if (ipList.contains(ip)) { 20 System.out.println("非法ip"); 21 resp.getWriter().write("非法ip"); 22 return; 23 } 24 chain.doFilter(req, resp); 25 } 26 27 public void init(FilterConfig config) throws ServletException { 28 29 } 30 31 }
在项目的根目录下创建一个index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title></title> </head> <body> hello </body> </html>
启动tomcat,访问系统,被禁用的ip用户是无法访问index.jsp的
使用Filter计算请求耗时
有时为了更详细的检测web系统性能,需要计算每次请求到响应所耗费的时间,然后看看哪些请求耗时较多,从而有针对性的进行优化操作,此时可以使用Filter过滤器自己实现一个请求响应耗时计算的功能。每个请求在到达目标资源之前都会被Filter过滤器拦截,我们可以在请求到达之前获取系统当前的时间,每个响应也会先被Filter过滤器拦截,我们在此时也获取一下系统的当前时间,之后将两次时间作比较就能计算出这次请求响应所耗费的时间了。
创建一个Filter过滤器
1 package com.monkey1024.filter; 2 3 import javax.servlet.*; 4 import javax.servlet.annotation.WebFilter; 5 import javax.servlet.http.HttpServletRequest; 6 import java.io.IOException; 7 import java.time.Duration; 8 import java.time.LocalTime; 9 10 @WebFilter(filterName = "TimeFilter",urlPatterns = "/*") 11 public class TimeFilter implements Filter { 12 public void destroy() { 13 } 14 15 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { 16 HttpServletRequest request = (HttpServletRequest) req; 17 18 //请求时的系统时间 19 LocalTime time1 = LocalTime.now(); 20 21 22 chain.doFilter(req, resp); 23 24 //响应时的系统时间 25 LocalTime time2 = LocalTime.now(); 26 //计算请求响应耗时 27 Duration total = Duration.between(time1, time2); 28 System.out.println(request.getRequestURI() + "耗时:" + total.toMillis()); 29 } 30 31 public void init(FilterConfig config) throws ServletException { 32 33 } 34 35 }
通过上面的Filter过滤器就可以计算每次请求响应的耗时了,并且还可以查看到请求的目标资源,从而有针对性的做优化处理。
创建一个用于测试的Servlet:
package com.monkey1024.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet(name = "TestServlet",urlPatterns = {"/test"}) public class TestServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //为了查看效果,所以睡眠3秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }