一、简介
(一)概述
1、Filter,过滤器,用于在servlet之外对request 和response 进行修改。Filter 有一个 FilterChain 的概念,一个FilterChain 包括多个 Filter。客户端请求 request在抵达servlet 之前会经过 FilterChain 里面所有的 Filter,服务器响应 response 从servlet 抵达客户端浏览器之前也会经过 FilterChain 里面所有的 Filter 。过程如图所示:
(二) Filter 的实现
1、实现自定义的 Filter 需要满足一下条件:
1)实现 javax.servlet.Filter 接口,实现其 init、doFilter、destroy 三个方法。
2)实现在web.xml中的配置。
2、javax.servlet.Filter 接口
1) Filter 接口有三个方法:这三个方法反应了 Filter 的生命周期。
①、init:只会在 web 程序加载的时候调用,即启动如tomcat等服务器时调用。一般负责加载配置的参数。
②、destroy :web程序卸载的时候调用。一般负责关闭某些容器等。
③、doFilter:每次客户端请求都会调用一次。Filter 的所有工作基本都集中在该方法中进行。
package servlet.filter; 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 org.apache.log4j.Logger; /** * * MyFilter.java * * @title 过滤器 * @description * @author SAM-SHO * @Date 2014-9-25 */ public class MyFilter implements Filter { private Logger logger = Logger.getLogger(this.getClass()); public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String contextPath = request.getContextPath();//上下文路径 String servletPath = request.getServletPath();//得到访问的servlet或者jsp的路径 logger.debug("上下文路径:"+contextPath); logger.debug("访问的servlet或者jsp的路径 : "+servletPath); chain.doFilter(req, resp); } public void init(FilterConfig filterConfig) throws ServletException { String name = filterConfig.getInitParameter("name"); logger.debug("获取过滤器的初始化参数: " + name); } }
3、 配置 Filter:每个过滤器需要配置在web.xml中才能生效,一个Filter需要配置<filter> 和 <filter-mapping>标签。
1)<filter> :配置 Filter 名称,实现类以及初始化参数。可以同时配置多个初始化参数。
2)<filter-mapping> :配置什么规则下使用这个Filter 。
①、<url-pattern> :配置url的规则,可以配置多个,也可以使用通配符(*)。例如 /jsp/* 适用于本ContextPath下以“/jsp/ ”开头的所有servlet路径, *.do 适用于所有以“ .do”结尾的servlet路径。
②、<dispatcher> :配置到达servlet的方式,可以同时配置多个。有四种取值:REQUEST、FORWARD、ERROR、INCLUDE。如果没有配置,则默认为REQUEST。它们的区别是:
# REQUEST :表示仅当直接请求servlet时才生效。
# FORWARD :表示仅当某servlet通过forward转发到该servlet时才生效。
# INCLUDE :Jsp中可以通过<jsp:include/>请求某servlet, 只有这种情况才有效。
# ERROR :Jsp中可以通过<%@page errorPage="error.jsp" %>指定错误处理页面,仅在这种情况下才生效。
③、<url-pattern>和<dispatcher> 是且的关系,只有满足<url-pattern>的条件,且满足<dispatcher>的条件,该Filter 才能生效。
<!-- 过滤器配置 --> <filter> <filter-name>MyFilter</filter-name> <filter-class>servlet.filter.MyFilter</filter-class> <init-param> <param-name>name</param-name> <param-value>Sam-Sho</param-value> </init-param> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/jsp/*</url-pattern> <url-pattern>*.do</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping>
个Web程序可以配置多个Filter ,访问有先后顺序,<filter-mapping> 配置在前面的Filter 执行要早于配置在后面的Filter 。
二、常用 Filter
(一)字符编码的 Filter
1、字符编码的 Filter 几乎每个项目都会用到。代码如下:
package servlet.filter; 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; /** * * CharacterEncodingFilter.java * * @title 编码过滤器 * @description * @author SAM-SHO * @Date 2014-10-12 */ public class CharacterEncodingFilter implements Filter { private String characterEncoding; private boolean enabled;//是否启用 public void init(FilterConfig config) throws ServletException { // 获取配置好的参数, characterEncoding = config.getInitParameter("characterEncoding");//配置好的字符编码 enabled = "true".equalsIgnoreCase(config.getInitParameter("enabled"));//是否启用 } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //设置字符编码 if (enabled && characterEncoding != null) { request.setCharacterEncoding(characterEncoding); response.setCharacterEncoding(characterEncoding); } chain.doFilter(request, response);//调用下一个过滤器 } public void destroy() { characterEncoding = null;//注销的时候,设为空 } }
<!-- 编码过滤器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>servlet.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>characterEncoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>enabled</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
(二)防盗链 Filter
1、防盗链需要使用到请求头 Referer ,该 Filter 的配置仅对 /images/ 和 /upload/images/ 下面的所有资源有效。代码如下:
package servlet.filter; 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; /** * * RefererFilter.java * * @title 责任链过滤器 * @description * @author SAM-SHO * @Date 2014-12-9 */ public class RefererFilter implements Filter { public void init(FilterConfig config) throws ServletException { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // 必须的 HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 禁止缓存 response.setHeader("Cache-Control", "no-store"); response.setHeader("Pragrma", "no-cache"); response.setDateHeader("Expires", 0); // 链接来源地址,通过获取请求头 referer 得到 String referer = request.getHeader("referer"); System.out.println("获取的来源--->: " + referer); if (referer == null || !referer.contains(request.getServerName())) {//本站点访问,则有效 /** * 如果 链接地址来自其他网站,则返回错误图片 */ request.getRequestDispatcher("/error.gif").forward(request, response); } else { /** * 图片正常显示 */ chain.doFilter(request, response); } } public void destroy() { } }
2、配置如下:
<!--责任链过滤器 --> <filter> <filter-name>RefererFilter</filter-name> <filter-class>servlet.filter.RefererFilter</filter-class> </filter> <filter-mapping> <filter-name>RefererFilter</filter-name> <url-pattern>/images/*</url-pattern> <url-pattern>/upload/images/*</url-pattern> </filter-mapping>
(三)权限校验 Filter
1、为了方便,权限配置在文件中:
package servlet.filter; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; 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; public class PrivilegeFilter implements Filter { private Properties pp = new Properties();//读取配置文件 public void init(FilterConfig config) throws ServletException { // 从 初始化参数 中获取权 限配置文件 的位置 String file = config.getInitParameter("file"); String realPath = config.getServletContext().getRealPath(file); try { pp.load(new FileInputStream(realPath)); } catch (Exception e) { config.getServletContext().log("读取权限控制文件失败。", e); } } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; // 获取访问的路径,例如:admin.jsp String requestURI = request.getRequestURI().replace( request.getContextPath() + "/", ""); // 获取 action 参数,例如:add String action = req.getParameter("action"); action = action == null ? "" : action; // 拼接成 URI。例如:log.do?action=list String uri = requestURI + "?action=" + action; // 从 session 中获取用户权限角色。 String role = (String) request.getSession(true).getAttribute("role"); role = role == null ? "guest" : role; boolean authentificated = false; // 开始检查该用户角色是否有权限访问 uri for (Object obj : pp.keySet()) { String key = ((String) obj); // 使用正则表达式验证 需要将 ? . 替换一下,并将通配符 * 处理一下 if (uri.matches(key.replace("?", "\?").replace(".", "\.") .replace("*", ".*"))) { // 如果 role 匹配 if (role.equals(pp.get(key))) { authentificated = true; break; } } } if (!authentificated) { System.out.println("您无权访问该页面。请以合适的身份登陆后查看。"); } // 继续运行 chain.doFilter(req, res); } public void destroy() { pp = null; } }
<!-- 权限过滤器 --> <filter> <filter-name>privilegeFilter</filter-name> <filter-class> servlet.filter.PrivilegeFilter </filter-class> <init-param> <param-name>file</param-name> <param-value>/WEB-INF/classes/privilege.properties</param-value> </init-param> </filter> <filter-mapping> <filter-name>privilegeFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
3、权限配置如下:
# Privilege Settings admin.do?action=* = administrator log.do?action=* = administrator list.do?action=add = member list.do?action=delete = member list.do?action=save = member list.do?action=view = guest list.do?action=list = guest
(四)GZIP 压缩 Filter
1、使用servlet 的对响应内容进行压缩:
private void GZipTest(HttpServletResponse response) throws IOException { //实现压缩 String tDate = "准备被压缩的数据"; System.out.println("压缩前的数据大小: "+tDate.getBytes().length); ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gout = new GZIPOutputStream(bout); gout.write(tDate.getBytes()); gout.flush(); gout.finish(); gout.close();//写到字节数组流中 byte[] gzip = bout.toByteArray();//得到压缩后的数据 System.out.println("压缩后的数据大小: "+gzip.length); // 通知浏览器数据采用压缩格式 response.setHeader("Content-Encoding", "gzip");//压缩格式 response.setHeader("Content-Length",gzip.length+"" );//压缩数据的长度 response.getOutputStream().write(gzip); }
2、使用过滤器代码:
1)GZIP 压缩的核心是 JDK 自带的压缩数据的类,GZIPOutputStream 。
2)响应头:Content-Encoding 和 Content-Length 。
3)GZipResponseWrapper 类为自定义的 Response 类,内部对输出的内容进行 GZIP 的压缩。
3、代码如下:
package servlet.filter.gzip; 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; /** * * GZipFilter.java * * @title 压缩过滤器 * @description * @author SAM-SHO * @Date 2014-12-9 */ public class GZipFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //获取浏览器支持的压缩格式 String acceptEncoding = request.getHeader("Accept-Encoding"); System.out.println("Accept-Encoding: " + acceptEncoding); if (acceptEncoding != null && acceptEncoding.toLowerCase().indexOf("gzip") != -1) { // 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据 GZipResponseWrapper gzipResponse = new GZipResponseWrapper(response); chain.doFilter(request, gzipResponse); // 输出压缩数据 gzipResponse.getOutputStream(); gzipResponse.finishResponse(); } else { // 否则, 不压缩 chain.doFilter(request, response); } } public void init(FilterConfig arg0) throws ServletException { } }
package servlet.filter.gzip; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; /** * * GZipResponseWrapper.java * * @title 封装的Response ,不会真正输出到客户端 * 继承 HttpServletResponseWrapper,其实现了 HttpServletResponse 接口 * @description * @author SAM-SHO * @Date 2014-12-9 */ public class GZipResponseWrapper extends HttpServletResponseWrapper { // 默认的 response private HttpServletResponse response; // 自定义的 outputStream, 执行close()的时候对数据压缩,并输出 private GZipOutputStream gzipOutputStream; // 自定义 printWriter,将内容输出到 GZipOutputStream 中 private PrintWriter writer; public GZipResponseWrapper(HttpServletResponse response) throws IOException { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { if (gzipOutputStream == null) gzipOutputStream = new GZipOutputStream(response); return gzipOutputStream; } @Override public PrintWriter getWriter() throws IOException { if (writer == null) writer = new PrintWriter(new OutputStreamWriter( new GZipOutputStream(response), "UTF-8")); return writer; } // 压缩后数据长度会发生变化 因此将该方法内容置空 @Override public void setContentLength(int contentLength) { } @Override public void flushBuffer() throws IOException { gzipOutputStream.flush(); } public void finishResponse() throws IOException { if (gzipOutputStream != null) gzipOutputStream.close(); if (writer != null) writer.close(); } }
package servlet.filter.gzip; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; /** * * GZipOutputStream.java * * @title 自定义的压缩流,内部调用JDK自带的压缩流 * @description * @author SAM-SHO * @Date 2014-12-9 */ public class GZipOutputStream extends ServletOutputStream { private HttpServletResponse response; // JDK 自带的压缩数据的类 private GZIPOutputStream gzipOutputStream; // 将压缩后的数据存放到 ByteArrayOutputStream 对象中 private ByteArrayOutputStream byteArrayOutputStream; public GZipOutputStream(HttpServletResponse response) throws IOException { this.response = response; byteArrayOutputStream = new ByteArrayOutputStream(); gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream); } @Override public void write(int b) throws IOException { gzipOutputStream.write(b); } @Override public void close() throws IOException { // 压缩完毕 一定要调用该方法 gzipOutputStream.finish(); // 将压缩后的数据输出到客户端 byte[] content = byteArrayOutputStream.toByteArray(); // 设定压缩方式为 GZIP, 客户端浏览器会自动将数据解压 response.addHeader("Content-Encoding", "gzip"); response.addHeader("Content-Length", Integer.toString(content.length)); // 输出 ServletOutputStream out = response.getOutputStream(); out.write(content); out.close(); } @Override public void flush() throws IOException { gzipOutputStream.flush(); } @Override public void write(byte[] b, int off, int len) throws IOException { gzipOutputStream.write(b, off, len); } @Override public void write(byte[] b) throws IOException { gzipOutputStream.write(b); } }
<!-- 压缩过滤器 --> <filter> <filter-name>gzipFilter</filter-name> <filter-class>servlet.filter.gzip.GZipFilter</filter-class> </filter> <filter-mapping> <filter-name>gzipFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
(四)文件上传 Filter
1、上传文件,修改<form> 标签的 enctype 设置为 “multipart/form-data” 。这样就可以通过获取请求头 Content-type 判断是否为文件上传。
2、使用 commons-fileupload-1.2.1.jar 实现上传。
package servlet.filter.upload; 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; /** * * UploadFilter.java * * @title 文件上传 Filter * @description * @author SAM-SHO * @Date 2014-12-9 */ public class UploadFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { UploadRequestWrapper uploadRequest = new UploadRequestWrapper((HttpServletRequest) request); chain.doFilter(uploadRequest, response); } public void init(FilterConfig filterConfig) throws ServletException { } }
package servlet.filter.upload; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.fileupload.DiskFileUpload; import org.apache.commons.fileupload.FileItem; /** * * UploadRequestWrapper.java * * @title 文件上传自定义Request * @description * @author SAM-SHO * @Date 2014-12-9 */ public class UploadRequestWrapper extends HttpServletRequestWrapper { private static final String MULTIPART_HEADER = "Content-type"; // 是否是上传文件 private boolean multipart; // map,保存所有的域 private Map<String, Object> params = new HashMap<String, Object>(); @SuppressWarnings("all") public UploadRequestWrapper(HttpServletRequest request) { super(request); // 判断是否为上传文件 multipart = request.getHeader(MULTIPART_HEADER) != null && request.getHeader(MULTIPART_HEADER).startsWith("multipart/form-data"); //是文件上传 if (multipart) { try { // 使用apache的工具解析 DiskFileUpload upload = new DiskFileUpload();//代替 DiskFileUpload upload.setHeaderEncoding("utf8"); // 解析,获得所有的文本域与文件域 List<FileItem> fileItems = upload.parseRequest(request); for (Iterator<FileItem> it = fileItems.iterator(); it.hasNext();) { // 遍历 FileItem item = it.next(); if (item.isFormField()) { // 如果是文本域,直接放到map里 params.put(item.getFieldName(), item.getString("utf8")); } else { // 否则,为文件,先获取文件名称 String filename = item.getName().replace("\", "/"); filename = filename.substring(filename.lastIndexOf("/") + 1); // 保存到系统临时文件夹中 File file = new File(System.getProperty("java.io.tmpdir"), filename); // 保存文件内容 OutputStream ous = new FileOutputStream(file); ous.write(item.get()); ous.close(); // 放到map中 params.put(item.getFieldName(), file); } } } catch (Exception e) { e.printStackTrace(); } } } @Override public Object getAttribute(String name) { // 如果为上传文件,则从map中取值 if (multipart && params.containsKey(name)) { return params.get(name); } return super.getAttribute(name); } @Override public String getParameter(String name) { // 如果为上传文件,则从map中取值 if (multipart && params.containsKey(name)) { return params.get(name).toString(); } return super.getParameter(name); } public static void main(String[] args) { System.out.println(System.getProperties().toString().replace(", ", " ")); } }
<!-- 文件上传 Filter --> <filter> <filter-name>uploadFilter</filter-name> <filter-class>servlet.filter.upload.UploadFilter</filter-class> </filter> <filter-mapping> <filter-name>uploadFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>