Filter 过滤器
Filter 是拦截Request请求的对象:在用户的请求访问资源前处理 ServletRequest 以及 ServletResponse。
Filter 可用于日志记录、加解密、Session检查、图像文件保护等。通过 Filter 可以拦截处理某个资源或者某些资源。
Filter 配置可以通过 Annotation 或者部署描述符(web.xml)来完成,但是,当一个资源或者某些资源需要被多个 Filter 所使用且他们的触发顺序很重要时,只能通过部署描述符(web.xml)来配置。
Filter API
Filter 相关的接口都在 javax.servlet 包中,包含:Filter、FilterConfig、FilterChain
Filter 接口 public interface Filter
Filter 的实现必须实现 javax.servlet.Filter 接口,该接口包含三个方法
default void init(FilterConfig filterConfig) throws ServletException // 一般在应用开始时,容器会调用该方法初始化Filer,只调用一次。FilterConfig由容器传入init方法中;default表名该方法有默认实现 void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException
// 当Servlet容器每次处理Filter相关的资源时,会调用该Filter实例的doFilter方法。 default void destroy() // 一般在应用停止的时候,容器会调用该方法来销毁Filter;default表名该方法有默认实现
在 Filter 的 doFilter的实现中,最后一行需要调用 FilterChain 中的 doChain 方法。注意 Filter 的 doFilter 方法里的第3个参数,就是 filterChain 的实例:
filterChain.doFilter(request, response)
只用在 Filter 链条中最后一个 Filter 里调用的 FilterChain.doFilter(),才会触发处理资源的方法。如果在结尾处没有调用 FilterChain.doFilter()的方法,则该Request请求中止,后面的处理就会中断。
FilterChain 接口 public interface FilterChain
该接口只有一个方法 doFilter(request, response) ,该方法和Filter接口中的doFiler(request, response, filterChain)方法定义不一样。
void doFilter(ServletRequest request, ServletResponse response) throws java.io.IOException, ServletException //
一般 filter 都是一个链,部署描述符(web.xml)中配置了几个就是几个,一个一个的连在一起: request --> filter 1 --> filter 2 --> filter 3 --> ... --> request resource
chain.doFilter(request, reponse) 将请求转发给过滤器链的下一个filter,如果没有下一个filter,那就是请求的资源。
如果在filter中忘记最后的chain.doFilter(request, response),会导致无法访问到请求资源。
FilterConfig 接口 public interface FilterConfig
该接口有四个方法
java.lang.String getFilterName() // ServletContext getServletContext() // java.lang.String getInitParameter(java.lang.String name) // 返回指定参数 java.util.Enumeration<java.lang.String> getInitParameterNames() // 返回参数名称的Enumeration对象,如果没有给这个Filter配置任何参数,则返回空的Enumeration
Filter 配置
当完成 Filter 的实现之后,就可以开始配置 Filter 了,步骤如下
(1)确认哪些资源需要使用这个 Filter 拦截处理
(2)配置 Filter 的初始化参数值,这些参数可以在 Filter 的 init 方法中读取到
(3)给 Filter 取一个名字。
注册 Filter
可以通过@WebFilter进行注册,也可以通过部署描述符(web.xml)进行注册。
但是当多个 Filter 应用到同一个资源,Filter的触发顺序将变得非常重要,才是只能使用部署描述符来管理 Filter,即指定哪个 Filter 先触发。
使用@WebFilter需要熟悉下面的参数,这些参数都是在 javax.servlet.annotation.WebFilter 中定义的,所有参数都是可选的
asyncSupported // boolean description // java.lang.String dispatcherTypes // DispatcherType[] displayName // java.lang.Sring filterName // java.lang.String initParams // WebInitParam[] largeIcon // java.lang.String servletNames // java.lang.String[] smallIcon // java.lang.String urlPatterns // java.lang.String[] value // java.lang.String[]
使用@WebFilter的例子
@WebFilter (filterName = "Security Filter", urlPatterns = { "/*" },
initParams = {
@WebInitParam(name = "frequency", value = "1909"),
@WebInitParam(name = "resolution", value = "1024")
}
)
使用部署描述符(web.xml)的例子
<filter> <filter-name>Security Filter</filter-name> <filter-class>filterClass的全限定名</filter-class> <init-param> <param-name>frequency</param-name> <param-value>1909</param-value> </init-param> <init-param> <param-name>resolution</param-name> <param-value>1024</param-value> </init-param> </filter> <filter-mapping> <filter-name>Security Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
利用部署描述符配置 Filter 的触发顺序,例如 Filter 1 需要在 Filter 2 之前被触发,那么在部署描述符中,Filter 1 需要配置在 Filter 2 之前,如下
<filter> <filter-name>Filter 1</filter-name> <filter-class>实现Filter接口的类的全限定名</filter-class> </filter> <filter> <filter-name>Filter 2</filter-name> <filter-class>实现Filter接口的类的全限定名</filter-class> </filter> <fitler-mapping> ... <filter-mapping>
实例一 日志 Filter
package app09a.filter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; 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.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import javax.servlet.http.HttpServletRequest; @WebFilter(filterName = "LoggingFilter", urlPatterns = { "/*" }, initParams = { @WebInitParam(name = "logFileName", value = "log.txt"), @WebInitParam(name = "prefix", value = "URI: ") } ) public class LoggingFilter implements Filter { private PrintWriter logger; private String prefix; @Override public void init(FilterConfig filterConfig) throws ServletException { prefix = filterConfig.getInitParameter("prefix"); // 获取初始化参数 String logFileName = filterConfig.getInitParameter("logFileName"); // 获取初始化参数 String appPath = filterConfig.getServletContext().getRealPath("/"); // 获取真实路径值,此处为“C:\Program Files\Java\apache-tomcat-9.0.12\webapps\app09a\” try { logger = new PrintWriter(new File(appPath, logFileName)); // 新建文件和PrintWriter对象 } catch (FileNotFoundException e) { e.printStackTrace(); throw new ServletException(e.getMessage()); } } @Override public void destroy() { System.out.println("destroying filter"); if (logger != null) { logger.close(); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { System.out.println("LoggingFilter.doFilter"); HttpServletRequest httpServletRequest = (HttpServletRequest) request; logger.println(new Date()+ " " + prefix + httpServletRequest.getRequestURI()); logger.flush(); filterChain.doFilter(request, response); // } }
<!-- test.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>test</title> </head> <body> This is a test for filter. </body> </html>
实例二 图像文件保护 Filter
package app09a.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; @WebFilter(filterName = "ImageProtectorFilter", urlPatterns = { "*.png", "*.jpg", "*.gif" }) public class ImageProtectorFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String referer = httpServletRequest.getHeader("referer"); // HTTP referer 属性是 header 的一部分,当浏览器向web服务器发送请求的时候,一般会带上 referer,告诉服务器我是从哪个页面链接过来的。 System.out.println("referer: " + referer); // 直接访问图片时,referer 的值为 null if ( referer != null) { // 通过JSP页面访问图片时,referer 的值为 http://localhost:8080/app09a/image.jsp filterChain.doFilter(request, response); } else { throw new ServletException("Image not available!"); } } }
<!-- image.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>image</title> </head> <body> <img src='image/logo.jpg' /> <!-- 当浏览器通过该链接获取图片资源时,他也将该页面的 URL 作为 Header 的 referer 值传到服务器中 --> </body> </html>
实例三 下载基数 Filter
package app09a.filter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; 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.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; @WebFilter(filterName = "DownloadCounterFilter", urlPatterns = { "/*" }) public class DownloadCounterFilter implements Filter { ExecutorService executorService = Executors.newSingleThreadExecutor(); // 单线程池 Properties downloadLog; // Properties类对象主要用于读取Java的配置文件 File logFile; @Override public void destroy() { executorService.shutdown(); // 关闭线程池 } @Override public void init(FilterConfig filterConfig) throws ServletException { String appPath = filterConfig.getServletContext().getRealPath("/"); // 获取该应用的绝对路径 logFile = new File(appPath, "downloadLog.txt"); if (!logFile.exists()) { try { logFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } downloadLog = new Properties(); try { downloadLog.load(new FileReader(logFile)); // 从文件中载入 } catch (IOException e) { e.printStackTrace(); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; final String uri = httpServletRequest.getRequestURI(); executorService.execute(new Runnable() { @Override public void run() { String property = downloadLog.getProperty(uri); if (property == null) { downloadLog.setProperty(uri, "1"); } else { int count = 0; try { count = Integer.parseInt(property); } catch (NumberFormatException e) { e.printStackTrace(); } count ++; downloadLog.setProperty(uri, Integer.toString(count)); } try { downloadLog.store(new FileWriter(logFile), ""); } catch (IOException e) { e.printStackTrace(); } } }); filterChain.doFilter(request, response); } }