学习要点
- 过滤器
- 监听器
过滤器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>