JavaEE-07 过滤器和监听器
学习要点
- 过滤器
- 监听器
过滤器Filter
过滤器的概念
- 过滤器位于客户端和web应用程序之间,用于检查和修改两者之间流过的请求和响应。
- 在请求到达Servlet/JSP之前,过滤器截获请求。
- 在响应送给客户端之前,过滤器截获响应。
- 多个过滤器形成一个过滤器链,过滤器链中不同过滤器的先后顺序由部署文件web.xml中过滤器映射<filter-mapping>的顺序决定。
- 最先截获客户端请求的过滤器将最后截获Servlet/JSP的响应信息。
过滤器的链式结构
可以为一个Web应用组件部署多个过滤器,这些过滤器组成一个过滤器链,每个过滤器只执行某个特定的操作或者检查。这样请求在到达被访问的目标之前,需要经过这个过滤器链。
实现过滤器
在Web应用中使用过滤器需要实现javax.servlet.Filter接口,实现Filter接口中所定义的方法,并在web.xml中部署过滤器。
public class MyFilter implements Filter { public void init(FilterConfig fc) { //过滤器初始化代码 } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { //在这里可以对客户端请求进行检查 //沿过滤器链将请求传递到下一个过滤器。 chain.doFilter(request, response); //在这里可以对响应进行处理 } public void destroy( ) { //过滤器被销毁时执行的代码 } }
Filter接口常用方法
方法名称 |
功能描述 |
public void init(FilterConfig config) |
容器在实例化过滤器调用。FilterConfig对象包含Filter相关的配置信息。 |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
每当请求和响应经过过滤器链时,容器都调用一次该方法。过滤器的一个实例可以同时服务于多个请求,需要注意线程同步问题,尽量不用或少用实例变量。 在过滤器的doFilter()方法实现中,任何出现在FilterChain的doFilter方法之前地方,request是可用的;在doFilter()方法之后response是可用的。 |
public void destroy() |
容器调用destroy()方法指出将从服务中删除该过滤器。如果过滤器使用了其他资源,需要在这个方法中释放这些资源。 |
部署过滤器
在Web应用的WEB-INF目录下,找到web.xml文件,在其中添加如下代码来声明Filter。
<filter> <filter-name>MyFilter</filter-name> <filter-class> com.etc.web.MyFilter </filter-class> <init-param> <param-name>codeFilter</param-name> <param-value>UTF-8</param-value> </init-param> </filter> //针对一个Servlet做过滤 <filter-mapping> <filter-name>MyFilter</filter-name> <servlet-name>MyServlet</servlet-name> </filter-mapping> //针对URL Pattern做过滤 <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<filter-mapping>标记是有先后顺序的,它的声明顺序说明容器是如何形成过滤器链的。过滤器应当设计为在部署时很容易配置的形式。通过使用初始化参数,可以得到复用性很高的过滤器。
过滤器逻辑与Servlet逻辑不同,它不依赖于任何用户状态信息,因为一个过滤器实例可能同时处理多个完全不同的请求。
新闻发布系统中,添加Post乱码处理过滤器
在web.xml中配置过滤器信息
<web-app> <filter> <filter-name>EncodeFilter</filter-name> <filter-class>com.etc.news.web.EncodeFilter</filter-class> <init-param> <param-name>encode</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>EncodeFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
注解方式部署过滤器
<!-- @WebFilter(urlPatterns = {"/*"},filterName="EncodeFilter" ,initParams = {@WebInitParam(name = "encode", value = "utf-8")}) -->
编写自定义类EncodeFilter
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; public class EncodeFilter implements Filter { private String encode = null; public void destroy() { encode = null; } public void init(FilterConfig filterConfig) throws ServletException { String encode = filterConfig.getInitParameter("encode"); if (this.encode == null) { this.encode = encode; } } // 对所有页面设置字符集 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (null == request.getCharacterEncoding()) { request.setCharacterEncoding(encode); } chain.doFilter(request, response); response.setContentType("text/html;charset="+encode); response.setCharacterEncoding(encode); } }
支持post和get方式的编码过滤器
HttpServletRequestWrapper和HttpServletResponseWrapper类
- Servlet2.1规范中的filter引入了一个功能强大的拦截模式。Filter能在request到达servlet的服务方法之前拦截HttpServletRequest对象,而在服务方法转移控制后又能拦截HttpServletResponse对象。
- 但是HttpServletRequest中的参数是无法改变的,若是手动执行修改request中的参数,则会抛出异常。且无法获取到HttpServletResponse中的输出流中的数据,因为HttpServletResponse中输出流的数据会写入到默认的输出端,你手动无法获取到数据。
- 我们可以利用HttpServletRequestWrapper包装HttpServletRequest,用HttpServletResponseWrapper包装HttpServletResponse,在Wrapper中实现参数的修改或者是response输出流的读取,然后用HttpServletRequestWrapper替换HttpServletRequest,HttpServletResponseWrapper替换HttpServletResponse。这样就实现了参数的修改设置和输出流的读取。
- HttpServletRequestWrapper是HttpServletRequest的一个实现类,所以可以用HttpServletRequestWrapper替换HttpServletRequest。ServletRequestWrapper采取了装饰器器模式,实际上内部操作的就是构造方法中传递的ServletRequest。
- HttpServletResponseWrapper是HttpServletResponse的实现类,所以HttpServletResponseWrapper可以替换HttpServletResponse。同ServletResponseWrapper一样,ServletResponseWrapper也是采去了装饰器模式,内部操作的也是构造方法中传递的ServletResponse。
- 装饰器模式:在java输入输出流中常见,处理流经常需要节点流作为参数来构建。例如BufferedInputStream,BufferedOutputSteam、BuffereReader、BuferedWriter等等。
请求装饰类示例代码
import java.io.UnsupportedEncodingException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * 对Get方式传递的请求参数进行编码 */ public class CharacterEncodingRequest extends HttpServletRequestWrapper { private HttpServletRequest request = null; public CharacterEncodingRequest(HttpServletRequest request) { super(request); this.request = request; } /** * 对参数重新编码 */ @Override public String getParameter(String name) { String value = super.getParameter(name); if (value == null) return null; String method = request.getMethod(); if ("get".equalsIgnoreCase(method)) { try { value = new String(value.getBytes("ISO8859-1"), request.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return value; } }
过滤器代码
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; public class CharacterEncodingFilter implements Filter { private String encode = "UTF-8";// 默认UTF-8编码 public void init(FilterConfig filterConfig) throws ServletException { String encoding = filterConfig.getInitParameter("encode"); if (encoding != null) { this.encode = encoding; } } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 设置request编码 request.setCharacterEncoding(encode); chain.doFilter(new CharacterEncodingRequest(request), response); // 设置响应信息编码 response.setContentType("text/html;charset=" + encode); response.setCharacterEncoding(encode); } public void destroy() { } }
HttpServletRequestWrapper用于实现敏感字过滤
项目结构
定义HTTP包装类
package com.etc.filter; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** http 请求包装器 */ public class HttpRequestWrapper extends HttpServletRequestWrapper { private Map<String, String> map = null;// 需要替换内容的集合key敏感字 value替换内容 public HttpRequestWrapper(HttpServletRequest request) { super(request); } /** * 敏感字替换 * * @param rs请求字符串 * @return 替换敏感字后的字符串 */ public String replace(String rs) { StringBuffer rssb = new StringBuffer(rs);// StringBuffer修改字符串不产生副本,非线程安全,速度快 Set<String> keys = this.getMap().keySet();// map的key为敏感字集合 Iterator<String> it = keys.iterator();// 敏感字迭代器 //String ss = null;// 存储key变量 while (it.hasNext()) { String key = it.next(); int index = rssb.indexOf(key);// 查找字符串中是否存在需要替换的内容 if (index != -1 && key != null) {// 找到敏感字并且敏感字集合该敏感字不为空,执行替换 //ss = key; rssb.replace(index, index + key.length(), this.getMap().get(key));// 替换敏感字 } } // if (ss != null) { // if (rssb.toString().indexOf(ss) == -1) {// 确保已经替换完毕 // return rssb.toString(); // } else {// 再次进行替换 // return replace(rssb.toString()); // } // } return rssb.toString(); } //Servlet中实际调用的getParameter方法: 重写的getParameter()方法 public String getParameter(String str) { String content = super.getParameter(str); return replace(content);// 返回被替换文本 // if (str.equals("pager.offset")) {// JSP视图使用pager-taglib分页框架的分页信息。忽略处理 // return super.getParameter(str); // } else {// 敏感字替换 // String content = super.getParameter(str); // return replace(content);// 返回被替换文本 // } } public Map<String, String> getMap() { return map; } public void setMap(Map<String, String> map) { this.map = map; } }
定义内容过滤器
package com.etc.filter; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; 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 = "ContentFilter", urlPatterns = "*.action", initParams = { @WebInitParam(name = "filePath", value = "/WEB-INF/words") }) public class ContentFilter implements Filter { private Map<String, String> map = new HashMap<String, String>(); // 过滤器的初始化:读取敏感字文件 public void init(FilterConfig config) throws ServletException { String filePath = config.getInitParameter("filePath");// 从配置文件中取得文件的相对路径/WEB-INF/words ServletContext context = config.getServletContext();// 读取上下文环境 String realPath = context.getRealPath(filePath);// 绝对路径D:\Tomcat7\webapps\guestbook\WEB-INF\words FileReader fr = null;//节点流 BufferedReader br = null;//处理流 // 根据相对路径取得绝对路径 try { fr = new FileReader(realPath);// 根据绝对路径,通过文件流来读取文件 br = new BufferedReader(fr); String line = null; while ((line = br.readLine()) != null) { String[] str = line.split("==");// 按照==拆分字符串 map.put(str[0], str[1]); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (br != null) { br.close(); } if (fr != null) { fr.close(); } } catch (IOException e) { e.printStackTrace(); } } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 处理请求中的数据:使用HttpRequestWrapper包装类替换HttpRequest HttpRequestWrapper hrw = new HttpRequestWrapper((HttpServletRequest) request); hrw.setMap(map); chain.doFilter(hrw, response);// 过滤链 } @Override public void destroy() { this.map = null; } }
监听器
Servlet事件
Web容器管理Servlet/JSP相关的生命周期。
Servlet事件是指HttpServletRequest对象、HttpSession对象、ServletContext对象生成、销毁或相关属性进行了设置等等事件。
Servlet监听器
监听器由web容器管理,它的作用是监听Servlet有效事件,并根据需求做出适当响应。下表为Servlet和JSP中的8个Listener和6个Event类。
Listener接口 |
监听Event类 |
ServletContextListener |
ServletContextEvent |
ServletContextAttributeListener |
ServletContextAttributevent |
HttpSessionListener |
HttpSessionEvent |
HttpSessionActivationListener |
|
HttpSessionAttributeListener |
HttpSessionBindingEvent |
HttpSessionBindingListener |
|
ServletRequestListener |
ServletRequestEvent |
ServletRequestAttributeListener |
ServletRequestAttributeEvent |
Servlet监听器的功能和Java的GUI的监听器类似,可以监听Servlet容器由于Web应用程序中状态改变而产生的相应事件,然后接受和处理这些事件。
监听Servlet上下文
用于监听ServletContext对象的创建、删除和添加属性,以及删除和修改操作,主要用到以下接口:
1.ServletContextListener接口
该接口主要用来监听SerrvletContext的创建和删除,他提供了以下两个方法,也称为“web应用程序的生命周期方法”:
-
- contextInitialized(ServletContextEvent event)方法:通知正在收听的对象应用程序已经被加载及初始化。
- contextInitialized(ServletContextEvent event)方法:通知正在收听的对象应用程序已经被载出、即将关闭。
2.ServletContextAttributeListener接口
用来监听ServletContext属性的增加、删除及修改,它提供了一下三个方法:
-
- attributeAdded(ServletContextAttributeEvent event)方法:若有对象加入application范围,通知正在收听的对象。
- attributeReplaced(ServletContextAttributeEvent event)方法:若在application范围内的对象取代另一个对象,通知正在收听的对象。
- attributeRemoved(ServletContextAttributeEvent event)方法:若有对象从application范围被移除,则通知正在收听的对象。
监听HTTP会话
提供了4个接口监听HTTP会话(HTTPSEession)信息。
1.HttpSessionListener接口
该接口监听HTTP会话的创建及撤销,它提供了两个方法:
-
- sessionCreated(HttpSessionEvent event)方法:通知正在收听的对象,session已经被加载及初始化。
- sessionDestroyed(HttpSessionEvent event)方法:通知正在收听的对象,session已经被载出(HttpSessionEvent类的主要方法是getSession,可以使用该方法回传一个session对象)。
2.HttpSessionActivationListener接口
该接口实现监听HTTP会话active和passivate情况,它提供了如下2个方法。
- sessionDidActivate(HttpSessionEvent event)方法:通知正在收听的对象,其session已经变为有效状态。
- sessionWillPassivate(HttpSessionEvent event)方法:通知正在收听的对象,其session已经变为无效的状态。
3.HttpSessionAttributeListener接口
该接口实现监听HTTP会话中属性的设置请求,它提供了3个方法:
- attributeAdded(HttpSessionBindingEvent event)方法:若有对象加入session的范围,通知正在收听的对象。
- attributeRemoved(HttpSessionBindingEvent event)方法:若有对象从session的范围移除,通知正在收听的对象(HttpSessionBindingEvent类主要有三个方法:getName()、getSession()、getValues())。
- attributeReplaced(HttpSessionBindingEvent event)方法:若在session范围内一个对象取代另一个对象,则通知在收听的对象。
4.HttpSessionBindingListener接口
该接口实现监听HTTP会话中对象的绑定信息,它是唯一不需要在web.xml中设置Listener的,它提供了两个方法:
- valueBound(HttpSessionBindingEvent event)方法:当有对象加入session范围时,自动调用。
- valueUnbound(HttpSessionBindingEvent event)方法:当有对象从session范围内移除时,会被自动调用。
监听Servlet请求
用来监听客户端的请求,一旦在监听程序中获取了客户端的请求,就可以统一处理请求,它提供了两个接口。
1.ServletRequestListener接口
-
- requestInitialized(ServletRequestEvent event)方法:通知正在收听的对象,ServletRequest已经被加载及初始化。
- requestDestroyed(ServletRequestEvent event)方法:通知正在收听的对象,ServletRequest已经被载出,即将关闭。
2.ServletRequestAttributeListener接口
-
- attributeAdded(ServletRequestAttributeEvent event)方法:若有对象加入request的范围,通知正在收听的对象。
- attributeRemoved(ServletRequestAttributeEvent event)方法:若有对象从request范围移除,通知正在收听的对象。
- attributeReplaced(ServletRequestAttributeEvent event)方法:若有对象在request范围被取代,通知正在收听的对象。
监听器实例:使用监听器查看在线用户
1.UserContainer类
package com.etc.listener; import java.util.LinkedList; import java.util.List; /** 在线用户操作类 ————单例模式 */ public class UserContainer { private static UserContainer userContainer = new UserContainer(); private List<String> list = null;//保存用户账号 private UserContainer() { this.list = new LinkedList<String>(); } /** 在线用户操作类单例模式 :所有的用户都保存在一个UserInfoList对象中 */ public static UserContainer getInstance() { return userContainer; } /** 添加用户 */ public boolean addUser(String user) { if (user != null) { this.list.add(user); return true; } else { return false; } } /** 删除用户 */ public void removeUser(String user) { if (user != null) { for (int i = 0; i < list.size(); i++) { if (user.equals(list.get(i))) { list.remove(i); } } } } /** 获取用户列表 */ public List<String> getList() { return this.list; } }
2.监听在线用户类
package com.etc.listener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; /** 监听在线用户类 */ @WebListener public class UserListener implements HttpSessionBindingListener { private String user;// 用户字符串 private UserContainer userContainer = UserContainer.getInstance();// 用户信息处理实例 public UserListener() { this.user = null; } /** 设置在线监听人员 */ public void setUser(String user) { this.user = user; } /** 获取在线人员 */ public String getUser() { return this.user; } /** 当有对象加入session范围时,自动调用 */ @Override public void valueBound(HttpSessionBindingEvent arg0) { System.out.println(this.user + "上线 "); this.userContainer.addUser(this.user); } /** 当有对象从session范围内移除时,会被自动调用 */ @Override public void valueUnbound(HttpSessionBindingEvent arg0) { System.out.println(this.user + "下线 "); this.userContainer.removeUser(this.user); } }
3.Servlet处理类
package com.etc.action; import java.io.IOException; 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 javax.servlet.http.HttpSession; import com.etc.listener.UserContainer; import com.etc.listener.UserListener; @WebServlet("/ServletLogin.action") public class ServletLogin extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); UserContainer userContainer = UserContainer.getInstance(); UserListener userListener = new UserListener(); String user = request.getParameter("name").trim();// 获取用户名 if (user.length() == 0) { user = "默认用户"; } userListener.setUser(user);// 设定监听用户 session.setAttribute("userListener", userListener); userContainer.addUser(userListener.getUser());// 用户信息添加到用户信息处理类 session.setMaxInactiveInterval(30); System.out.println(userContainer.getList().size()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
4.登陆处理代码
<% UserInfoList list = UserInfoList.getInstance(); UserInfoTrace trace = new UserInfoTrace(); request.setCharacterEncoding("utf-8"); String user = request.getParameter("name").trim();//获取用户名 if (user.length() == 0) { user = "默认用户"; } trace.setUser(user);//设定监听用户 session.setAttribute("trace", trace); list.addUser(trace.getUser());//用户信息添加到用户信息处理类 session.setMaxInactiveInterval(30); %> <h3>当前登录用户</h3> <ul> <% Vector<String> vector = list.getList(); for (int i = 0; i < vector.size(); i++) { out.print("<li>" + vector.elementAt(i) + "</li>"); } %> </ul>
5.登陆视图代码
<%@ 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>登陆视图</title> </head> <body> <form action="ServletLogin.action" method="post"> <p> 用户名:<input type="text" name="name"> </p> <p> <input type="submit" value="登陆"> </p> </form> </body> </html>
本博客文章未经许可,禁止转载和商业用途!
如有疑问,请联系: 2083967667@qq.com