1、解决全站乱码过滤器(含开发入门&Filter链&生命周期&FilterConfig)servlet3.0规范
@WebFilter( urlPatterns = { "/ServletDemo1" }, initParams = { @WebInitParam(name = "charset", value = "UTF-8", description = "编码") }) public class FilterDemo1 implements Filter { FilterConfig fConfig=null; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { System.out.println("FilterDemo1 doFilter 前!"); String charset = fConfig.getInitParameter("charset"); HttpServletRequest req = (HttpServletRequest)request; req.setCharacterEncoding(charset); //这两句用来设置当servlet打印页面时的编码设置,实际开发中都是转发给jsp,由jsp来通过指令和meta标签进行设置 /*HttpServletResponse resp = (HttpServletResponse)response; resp.setCharacterEncoding(charset); resp.setContentType(charset);*/ chain.doFilter(request, response); System.out.println("FilterDemo1 doFilter 后!"); } public void init(FilterConfig fConfig) { System.out.println("FilterDemo1 init!"); this.fConfig = fConfig; } public void destroy() { System.out.println("FilterDemo1 destroy!"); } }
@WebFilter("/ServletDemo1") public class FilterDemo2 implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { System.out.println("FilterDemo2 doFilter 前!"); chain.doFilter(request, response); System.out.println("FilterDemo2 doFilter 后!"); } public void init(FilterConfig fConfig) throws ServletException { System.out.println("FilterDemo2 init!"); } public void destroy() { System.out.println("FilterDemo2 destroy!"); } }
@WebServlet("/ServletDemo1") public class ServletDemo1 extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) { String name = request.getParameter("username"); System.out.println("ServletDemo1 doget! name="+name); } protected void doPost(HttpServletRequest request, HttpServletResponse response) { this.doGet(request, response); } public void init(ServletConfig config) throws ServletException { System.out.println("ServletDemo1 init!"); } public void destroy() { System.out.println("ServletDemo1 destroy!"); } }
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="${pageContext.request.contextPath}/ServletDemo1 " method="post"> <input name="username" /> <input type="submit" value="提交"/> </form> </body> </html>
运行结果:
1、当服务器启动时:
FilterDemo2 init!
FilterDemo1 init!
首先初始化FilterDemo2,与调用doFilter的顺序刚好相反(doFilter的调用顺序与类的名称相关).
2、当在demo1.jsp中输入中文点击提交时:
ServletDemo1 init! FilterDemo1 doFilter 前! FilterDemo2 doFilter 前! ServletDemo1 doget! name=王维 FilterDemo2 doFilter 后! FilterDemo1 doFilter 后!
3、当服务器关闭或者项目重新发布时:
ServletDemo1 destroy! FilterDemo2 destroy! FilterDemo1 destroy!
如上所示的servlet过滤器可以实现当访问说有的servlet时,在它之前进行编码设置.
2、控制浏览器缓存页面中的静态资源,禁止浏览器缓存动态页面
<!--测试jsp页面,此页面首次访问时共会向服务器发送3次请求 -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="/Filter/css/1.css">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>noCache</title>
</head>
<body>
hello
<img id="img1" alt="ss" src="/Filter/image/1.png">
</body>
</html>
//控制jsp不缓存的Filter,我们的jsp都是由servlet forward来的 @WebFilter(dispatcherTypes = {DispatcherType.FORWARD }, urlPatterns = { "*.jsp" })//ps备注 public class NoCacheFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ HttpServletResponse resp = (HttpServletResponse)response; //注意一个是setDateHeader另两个是setHeader resp.setDateHeader("Expires",-1); resp.setHeader("Cache-Control","no-cache"); resp.setHeader("Pragma","no-cache"); chain.doFilter(request, response); } public void destroy() { } public void init(FilterConfig arg0) throws ServletException { } }
//控制css文件和图片缓存的Filter @WebFilter( urlPatterns={"*.css","*.png"},//一个Filter可对应多个url映射,类似Servlet initParams = { @WebInitParam(name = "cssExpires", value = "6000", description = "css文件缓存时间"), @WebInitParam(name = "imageExpires", value = "6000", description = "图片文件缓存时间") }) public class CacheFilter implements Filter { FilterConfig fConfig=null; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ HttpServletResponse resp = (HttpServletResponse)response; HttpServletRequest req = (HttpServletRequest)request; String cssExpires = fConfig.getInitParameter("cssExpires"); String imageExpires = fConfig.getInitParameter("imageExpires"); String uri = req.getRequestURI(); if(uri.endsWith("css")){ //这里注意是setDateheader不是setHeader resp.setDateHeader("Expires",System.currentTimeMillis()+Long.parseLong(cssExpires)); }else{ resp.setDateHeader("Expires",System.currentTimeMillis()+Long.parseLong(imageExpires)); } chain.doFilter(request, response); } public void init(FilterConfig fConfig) throws ServletException { this.fConfig=fConfig; } public void destroy() { } }
ps:1、Ctrl+F5强制刷新的话,不管浏览器有没有缓存都会向服务器重新请求.
2、<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST.用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截.
<dispatcher> 子元素可以设置的值及其意义:
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器.如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用.
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用.除此之外,该过滤器不会被调用.
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用.
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用.除此之外,过滤器不会被调用.
用法实例:
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/test.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
3、实现用户自动登陆的过滤器 *代码非常经典
<!--登陆界面 --> <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'login.jsp' starting page</title> </head> <body> <form action="${pageContext.request.contextPath }/servlet/LoginServlet" method="post"> 用户名:<input type="text" name="username"><br/> 密码:<input type="password" name="password"><br/> 有效期: 1分钟<input type="radio" name="time" value="${1*60 }"> 5分钟<input type="radio" name="time" value="${5*60 }"> 10分钟<input type="radio" name="time" value="${10*60 }"> <br/> <input type="submit" value="登陆"> </form> </body> </html> //登陆Servlet public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response){ String username = request.getParameter("username"); String password = request.getParameter("password"); BusinessService service = new BusinessService(); User user = service.login(username, password); if (user == null) { request.setAttribute("message", "用户名或密码错误!!"); request.getRequestDispatcher("/message.jsp").forward(request, response); return; } request.getSession().setAttribute("user", user); // 给客户机发送自动登陆的 cookie //把username回写给cookie,Filter需要通过username拿到用户信息,校验cookie带的md5和服务器算出的MD5是否一致,判断cookie有没有被修改 int expirestime = Integer.parseInt(request.getParameter("time")); // autologin=username:expirestime:md5(password:expirestime:username) Cookie cookie = makeCookie(user, expirestime); response.addCookie(cookie); response.sendRedirect("/day19/index.jsp"); } public Cookie makeCookie(User user, int expirestime) { long currenttime = System.currentTimeMillis(); String cookieValue = user.getUsername() + ":" + (currenttime + expirestime * 1000) + ":" + md5(user.getUsername(), user.getPassword(), (currenttime + expirestime * 1000)); Cookie cookie = new Cookie("autologin", cookieValue); cookie.setMaxAge(expirestime); cookie.setPath("/day19"); return cookie; } private String md5(String username, String password, long expirestime) { try { String value = password + ":" + expirestime + ":" + username; MessageDigest md = MessageDigest.getInstance("md5"); byte md5[] = md.digest(value.getBytes()); BASE64Encoder encode = new BASE64Encoder(); return encode.encode(md5); } catch (Exception e) { throw new RuntimeException(e); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws Exception { doGet(request, response); } } //自动登录拦截器 @WebFilter("*.*") // 访问所有资源都进行拦截 public class AutoLoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 1.先检查用户是否已登陆,没登陆才自动登陆 User user = (User) request.getSession().getAttribute("user"); if (user != null) { chain.doFilter(request, response); return; } // 2.没登陆,再执行自动登陆逻辑 // 看用户有没有带自动登陆的cookie Cookie autoLoginCookie = null; Cookie cookies[] = request.getCookies(); for (int i = 0; cookies != null && i < cookies.length; i++) { if (cookies[i].getName().equals("autologin")) { autoLoginCookie = cookies[i]; } } if (autoLoginCookie == null) { chain.doFilter(request, response); return; } // 用户带了自动登陆的cookie,则先检查cookie的有效期 String values[] = autoLoginCookie.getValue().split("\\:"); if (values.length != 3) { chain.doFilter(request, response); return; } long expirestime = Long.parseLong(values[1]); if (System.currentTimeMillis() > expirestime) { chain.doFilter(request, response); return; } // 代表cookie时间有效,再检查cookie的有效性 String username = values[0]; String client_md5 = values[2]; BusinessService service = new BusinessService(); user = service.findUser(username); if (user == null) { chain.doFilter(request, response); return; } //// autologin=username:expirestime:md5(password:expirestime:username) String server_md5 = md5(user.getUsername(), user.getPassword(), expirestime); if (!server_md5.equals(client_md5)) { chain.doFilter(request, response); return; } // 执行登陆 request.getSession().setAttribute("user", user); chain.doFilter(request, response); } private String md5(String username, String password, long expirestime) { try { String value = password + ":" + expirestime + ":" + username; MessageDigest md = MessageDigest.getInstance("md5"); byte md5[] = md.digest(value.getBytes()); BASE64Encoder encode = new BASE64Encoder(); return encode.encode(md5); } catch (Exception e) { throw new RuntimeException(e); } } public void destroy() { } public void init(FilterConfig filterConfig) throws ServletException { } }
由于开发人员在filter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用Decorator(装饰器)模式对request、response对象进行包装,再把包装对象传给目标资源,从而实现一些特殊需求.
public class CharacterEncodingFilter2 implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; request.setCharacterEncoding("UTF-8"); //只能解决post乱码问题 chain.doFilter(new MyRequest(request), response); } class MyRequest extends HttpServletRequestWrapper{ private HttpServletRequest request; public MyRequest(HttpServletRequest request) { super(request); this.request = request; } @Override public String getParameter(String name) { String value = this.request.getParameter(name); if(!request.getMethod().equalsIgnoreCase("get")){ return value; } if(value==null){ return null; } try { return value = new String(value.getBytes("iso8859-1"),request.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } } public void destroy() { } public void init(FilterConfig filterConfig) throws ServletException { } }
例2、使用Decorator模式包装request对象,实现html标签转义功能(Tomcat服务器中提供了转义html标签的工具类),对客户端提交的文本进行转义.
public class HtmlFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; chain.doFilter(new MyRequest(request), response); } class MyRequest extends HttpServletRequestWrapper{ private HttpServletRequest request; public MyRequest(HttpServletRequest request) { super(request); this.request = request; } @Override public String getParameter(String name) { String value = this.request.getParameter(name); if(value==null){ return null; } return filter(value); } public String filter(String message) { if (message == null) return (null); char content[] = new char[message.length()]; message.getChars(0, message.length(), content, 0); StringBuffer result = new StringBuffer(content.length + 50); for (int i = 0; i < content.length; i++) { switch (content[i]) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '&': result.append("&"); break; case '"': result.append("""); break; default: result.append(content[i]); } } return (result.toString()); } } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } }
例3、词汇过滤器
其实和html标签转义功能完全类似,使用装饰器模式,拦截getParameter方法,当在servlet中调用getParameter方法时,实际调用的是我修改过的getParameter方法,该方法对词汇进行了过滤.
response对象的增强最经典案例-压缩响应如下:
2、应用HttpServletResponseWrapper对象,压缩响应正文内容.思路:
例1、没有实现全站式的压缩
//压缩输出 public class ServletDemo2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "aaaaaaaa"; ByteArrayOutputStream bout = new ByteArrayOutputStream();//缓存字节流 GZIPOutputStream gout = new GZIPOutputStream(bout); gout.write(data.getBytes()); gout.close();//确保写入缓存流成功 byte gzip[] = bout.toByteArray(); response.setHeader("content-encoding", "gzip"); response.setHeader("content-length", gzip.length + ""); response.getOutputStream().write(gzip); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
例2、全站式压缩实现
//giz压缩过滤器 public class GzipFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; MyResponse myresponse = new MyResponse(response); //response.getwriter response.getOutputStream chain.doFilter(request, myresponse); //取出缓冲的数据压缩后输出 byte out[] = myresponse.getBuffer(); //得到目标资源的输出 System.out.println("压之前:" + out.length); byte gzipout[] = gzip(out); System.out.println("压之后:" + gzipout.length); response.setHeader("content-encoding", "gzip"); response.setHeader("content-length", gzipout.length + ""); response.getOutputStream().write(gzipout); } public byte[] gzip(byte b[]) throws IOException{ ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gout = new GZIPOutputStream(bout); gout.write(b); gout.close(); return bout.toByteArray(); } class MyResponse extends HttpServletResponseWrapper{ private ByteArrayOutputStream bout = new ByteArrayOutputStream(); private PrintWriter pw; private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); //myresponse.getOutputStream().write("hahah"); } @Override public PrintWriter getWriter() throws IOException { pw = new PrintWriter(new OutputStreamWriter(bout,response.getCharacterEncoding())); return pw; //MyResponse.getWriter().write("中国"); } public byte[] getBuffer(){ if(pw!=null){ pw.close(); } return bout.toByteArray(); } } class MyServletOutputStream extends ServletOutputStream{ private ByteArrayOutputStream bout; public MyServletOutputStream(ByteArrayOutputStream bout){ this.bout = bout; } @Override public void write(int b) throws IOException { bout.write(b); } } public void destroy() { } public void init(FilterConfig filterConfig) throws ServletException { } }
public class GzipServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = "中国"; response.getOutputStream().write(data.getBytes("UTF-8")); request.getRequestDispatcher("/index.jsp").forward(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
web.xml配置
<filter> <filter-name>GzipFilter</filter-name> <filter-class>cn.itcast.web.filter.example.GzipFilter</filter-class> </filter> <filter-mapping> <filter-name>GzipFilter</filter-name> <url-pattern>*.jsp</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> </filter-mapping> <filter-mapping> <filter-name>GzipFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping> <filter-mapping> <filter-name>GzipFilter</filter-name> <url-pattern>*.js</url-pattern> </filter-mapping>
ps:gzip一般压缩文本类型的数据,对图片视频等压缩率较低.
***有一点要注意,当servlet中以forward方式转发给另一个servlet时,实际是通知tomcat去调用另一个servlet,这其中如果有拦截器的话会执行.
//可以配置给几个指定的servlet
public class WebCacheFilter implements Filter { private Map<String,byte[]> map = new HashMap(); public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; //1.得到用户想访问的资源(uri) String uri = request.getRequestURI(); //2.看map集合中是否保存了该资源的数据 byte b[] = map.get(uri); //3.如果保存了,则直接取数据打给浏览器 if(b!=null){ response.getOutputStream().write(b); return;//servlet就不执行了 } //4.如果没有保存数据,则放行让目标资源执行,这时还需写一个response的包装类,捕获目标资源的输出 MyResponse my = new MyResponse(response); chain.doFilter(request, my); byte data[] = my.getBuffer(); //5.以资源uri为关键字,打资源的数据保存map集合中,以备于下次访问 map.put(uri, data); //6.输出数据给浏览器 response.getOutputStream().write(data); } class MyResponse extends HttpServletResponseWrapper{ private ByteArrayOutputStream bout = new ByteArrayOutputStream(); private PrintWriter pw; private HttpServletResponse response; public MyResponse(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { return new MyServletOutputStream(bout); //myresponse.getOutputStream().write("hahah"); } @Override public PrintWriter getWriter() throws IOException { pw = new PrintWriter(new OutputStreamWriter(bout,response.getCharacterEncoding())); return pw; //MyResponse.getWriter().write("中国"); } public byte[] getBuffer(){ if(pw!=null){ pw.close(); } return bout.toByteArray(); } } class MyServletOutputStream extends ServletOutputStream{ private ByteArrayOutputStream bout; public MyServletOutputStream(ByteArrayOutputStream bout){ this.bout = bout; } @Override public void write(int b) throws IOException { bout.write(b); } } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } }
ps:过滤器经典案例,权限系统,略。