第七章:Servlet高级
OVERVIEW
Filter与Listener是Servlet规范中的两个高级特性,不同于Servlet其不用处理客户端请求。
Filter用于对request、response对象进行修改,Listener用于对context、session、request事件进行监听,善用两个对象。
一、Filter过滤器
1.Filter概念
Filter被称为过滤器,其基本功能是对Servelet容器调用Servlet的过程进行拦截,从而在Servlet进行响应处理前后实现一些特殊功能。
Filter过滤器实现了javax.servlet.Filter接口的类,在javax.servlet.Filter接口中定义了3个方法,如下表:
方法声明 | 说明 |
---|---|
init(FilterConfig filterConfig) | 初始化过滤器 |
doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | 参数chain代表当前Filter链的对象 |
destroy() | 该方法在web服务器卸载Filter对象之前被调用,用于释放Filter对象打开的资源 |
注意:如果要用到FilterConfig对象,那么初始化代码就只能在Filter的
init()
方法中编写,而不能在构造方法中
以上3个方法都是Filter的生命周期方法,init()
在web应用程序加载的时候被调用、destroy()
在web应用程序卸载时调用:
doFilter()
方法只要有客户端请求时就会被调用,并且Filter所有的工作集中在该方法之中。
注意:init()与destroy()方法都只会被调用一次
2.Filter映射
Filter拦截的资源需要在web.xml文件中进行配置,这些配置信息就是Filter映射。
过滤器的配置信息中包含多个元素,这些元素分别具有不同的作用具体如下:
配置信息中元素 | 说明 |
---|---|
<filter> | 根元素,用于注册一个Filter |
<filter-name> | 子元素用于设置Filter名称 |
<filter-class> | 子元素用于设置Filter类的完整名称 |
<filter-mapping> | 根元素用于设置一个过滤器所拦截的资源 |
<url-pattern> | 子元素用于匹配用户请求的URL |
🚩具体使用如下:
package c8p1;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("Hello MyServlet");
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package c8p1;
import java.io.IOException;
import java.io.PrintWriter;
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 MyFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
//1.过滤器对象在初始化时调用,可以配置一些初始化参数
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//2.用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法就会被调用
PrintWriter out = response.getWriter();
out.write("Hello MyFilter");
}
public void destroy() {
//3.过滤器对象在销毁时自动调用,释放资源
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>c8p1.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/MyServlet</url-pattern>
</servlet-mapping>
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>c8p1.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/MyFilter</url-pattern>
</filter-mapping>
</web-app>
注意:上述请求中设置了设置了对
/MyServlet
请求资源进行拦截,将在请求到达MyServlet程序前执行MyFilter程序。
分析:通过url地址直接访问MyServlet资源没有显示Hello MyServlet,而显示为Hello MyFilter(由于Filter拦截)
(1)拦截全部请求:通配符*
Filter的<filter-mapping>元素用于配置过滤器拦截的资源信息,使用通配符*
来拦截所有的请求访问:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>c8p1.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(2)拦截不同方式的访问请求
Filter的<filter-mapping>元素用于配置过滤器拦截的资源信息,
其子元素<dispatcher>用于指定过滤器所拦截的资源被Servlet容器调用的方式,其值共有4个:
case1:REQUEST
当用户直接访问页面时,web容器将会调用过滤器。
若目标资源是通过RequestDispatcher的include()
或forward()
方法访问的,那么过滤器将不会被调用。
case2:INCLUDE
如果目标资源是通过RequestDispatcher的include()
方法访问的,那么过滤器将被调用。
case3:FORWARD
如果目标资源是通过RequestDispatcher的forward()
方法访问的,那么过滤器将被调用。
case4:ERROR
如果目标资源是通过声明式异常处理机制调用的,那么过滤器将被调用。
🚩具体使用如下:
package c8p2;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// ForwardServlet用于将请求转发给first.jsp页面
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/first.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package c8p2;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
// 该过滤器用于对first.jsp页面的访问进行拦截
public class ForwardFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 1.过滤器对象在初始化时调用,可以配置一些初始化参数
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 2.用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
PrintWriter out = response.getWriter();
out.write("Hello FilterTest_FORWARD");
}
@Override
public void destroy() {
// 3.过滤器对象在销毁时自动调用
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>ForwardServlet</servlet-name>
<servlet-class>c8p2.ForwardServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ForwardServlet</servlet-name>
<url-pattern>/ForwardServlet</url-pattern>
</servlet-mapping>
<filter>
<filter-name>ForwardFilter</filter-name>
<filter-class>c8p2.ForwardFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ForwardFilter</filter-name>
<url-pattern>/first.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
</web-app>
分析:通过在web.xml文件中设置了
<dispatcher>FORWARD</dispatcher>
后:当目标资源通过RequestDispatcher的forward()方法访问时,Filter过滤器将被调用。(first.jsp的页面内容并没有显示,显示内容为ForwardFilter类中的内容)
3.Filter链
在一个Web应用程序中可以注册多个Filter程序,每个Filter程序都可以针对某个URL进行拦截。
如果多个Filter程序对一个URL进行拦截,那么这些Filter就会组成一个Filter链(过滤器链)。
Filter链用FilterChain对象表示,FilterChain对象有一个doFilter()
方法,让Filter链上当前过滤器放行(使请求进入下一个Filter)。
🚩具体使用如下:
package c8p3;
import java.io.IOException;
import java.io.PrintWriter;
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 MyFilter01 implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
//1.过滤器对象在初始化时调用,可以配置一些初始化参数
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//2.用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法就会被调用
PrintWriter out = response.getWriter();
out.write("Hello MyFilter01<br />");
chain.doFilter(request, response);
}
public void destroy() {
//3.过滤器对象在销毁时自动调用,释放资源
}
}
package c8p3;
import java.io.IOException;
import java.io.PrintWriter;
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 MyFilter02 implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
//1.过滤器对象在初始化时调用,可以配置一些初始化参数
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//2.用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法就会被调用
PrintWriter out = response.getWriter();
out.write("Hello MyFilter02 Before<br />");
chain.doFilter(request, response);
out.write("<br />Hello MyFilter02 After<br />");
}
public void destroy() {
//3.过滤器对象在销毁时自动调用,释放资源
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>c8p1.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/MyServlet</url-pattern>
</servlet-mapping>
<filter>
<filter-name>MyFilter01</filter-name>
<filter-class>c8p3.MyFilter01</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter01</filter-name>
<url-pattern>/MyServlet</url-pattern>
</filter-mapping>
<filter>
<filter-name>MyFilter02</filter-name>
<filter-class>c8p2.MyFilter02</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter02</filter-name>
<url-pattern>/MyServlet</url-pattern>
</filter-mapping>
</web-app>
注意:Filter链中各个Filter的拦截顺序与它们在web.xml文件中
<filter-mapping>
元素的映射顺序是一致的
分析:在对MyServlet资源进行访问时,首先被MyFilter01拦截(输出其中内容),后被MyFilter02拦截,被放行之后才输出MyServlet中的内容。
✔ 存在的疑问
⚠️问题1:上述程序中发现输出的<br />标签使用chrome浏览器时不被解析,而使用IE浏览器时才能被正常解析
⚠️问题2:为什么MyFilter02 After会被输出显示?
在MyFilter02中,当调用到doFilter()方法时,请求将直接被发送给目标资源。语句out.write("<br /> Hello MyFilter02 After <br />");
将不被执行?
4.FilterConfig接口
为了获取Filter程序在web.xml文件中的配置信息,Servlet API提供了一个FIiterConfig接口,
该接口封装了Filter程序在web.xml中的所有配置信息,并提供了一系列获取这些配置信息的方法:
方法名 | 说明 |
---|---|
String getFilterName() | 返回在web.xml文件中为Filter设置的名称<filter-name> |
String getInitParameter(String name) | 返回在web.xml文件中为Filter设置的某个名称的初始化参数值 |
Enumeration getInitParameterNames() | 返回在web.xml文件中为Filter设置的所有初始化参数的名称 |
ServletContext getServletContext() | 返回FilterConfig对象中所包装的ServletContext对象的引用 |
🚩具体使用如下:
package c8p1;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("Hello MyServlet");
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package c8p4;
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 MyFilter03 implements Filter {
private String characterEncoding;
FilterConfig fc;
public void init(FilterConfig filterConfig) throws ServletException {
// 1.获取FilterConfig对象
this.fc = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 2.输出参数信息
characterEncoding = fc.getInitParameter("encoding");
System.out.println("encoding初始化参数的值为:" + characterEncoding);
chain.doFilter(request, response);
}
public void destroy() {
//3.过滤器对象在销毁时自动调用,释放资源
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!-- c8p4 -->
<filter>
<filter-name>MyFilter03</filter-name>
<filter-class>c8p4.MyFilter03</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>MyFilter03</filter-name>
<url-pattern>/MyServlet</url-pattern>
</filter-mapping>
</web-app>
分析:浏览器窗口中输出了Hello MyServlet,而在控制台窗口输出了encoding的参数信息
5.应用1:Filter实现用户自动登录(存在问题)
(1)Cookie自动登录:
Cookie可以实现用户自动登录功能:
当用户第一次访问服务器时,服务器会发送一个包含用户信息的Cookie,
当客户端再次访问服务器时,会向服务器回送Cookie,服务器从Cookie中获取用户信息,从而实现用户的自动登录功能。
注意:当客户端访问服务器的Servlet时,所有Servlet都需要对用户的Cookie信息进行校验(造成代码重复)
可以在Filter程序中实现Cookie的检验,简化代码:
由于Filter可以对服务器的所有请求进行拦截,因此一旦通过Filter程序就相当于用户信息通过了校验(Servlet程序根据获取到的用户信息就可以实现自动登录)
(2)Filter简化Cooie自动登录:
step1:User类
package c8p5;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
step2:jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Insert title here</title>
</head>
<body style="text-align: center;">
<h3>用户登录</h3>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
<table border="1" width="800px" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
<tr>
<td height="30">用户名:</td>
<td> <input type="text" name="username" />${errerMsg}</td>
</tr>
<tr>
<td height="30">密 码:</td>
<td> <input type="password" name="password" /></td>
</tr>
<tr>
<td height="35">自动登录时间</td>
<td>
<input type="radio" name="autologin" value="&{60*60*24*31}" />一个月
<input type="radio" name="autologin" value="&{60*60*24*31*3}" />三个月
<input type="radio" name="autologin" value="&{60*60*24*31*6}" />半年
<input type="radio" name="autologin" value="&{60*60*24*31*12}" />一年
</td>
</tr>
<tr>
<td height="30" colspan="2">
<input type="submit" value="登录" />
<input type="reset" value="重置" />
</td>
</tr>
</table>
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8" import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>显示登录的用户信息</title>
</head>
<body>
<c:choose>
<c:when test="${sessionScope.user == null}">
<!-- 1.没有登录过将显示用户登录超链接 -->
<br />
<center><h3>Please login first</h3></center>
<a href="${pageContext.request.contextPath}/login.jsp">点击登录</a>
</c:when>
<c:otherwise>
<!-- 2.已经登录过将显示登录的用户名以及注销按钮 -->
<br />
<center><h3>Welcome to the site!</h3></center>
欢迎你,${sessionScope.user.username}!
<a href="${pageContext.request.contextPath}/LogoutServlet">点击注销</a>
</c:otherwise>
</c:choose>
<hr />
</body>
</html>
step3:Servlet(Login&Logout)
package c8p5;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2.检查用户名和密码
if ("lch".equals(username) && "123456".equals(password)) {
// 登录成功
// (1)将用户状态user对象存入session域中
User user = new User();
user.setUsername(username);
user.setPassword(password);
request.getSession().setAttribute("user", user);
// (2)获取用户是否登录的标识符autologin,if判断是否发送自动登录的Cookie(autoLogin)
String autologin = request.getParameter("autologin");
if (autologin != null) {
// 用户点击了自动登录->将用户信息保存到cookie中(将在AutoLoginFilter中被使用)
Cookie cookie = new Cookie("autoLogin", username + "-" + password);
int time = Integer.parseInt(autologin);
cookie.setMaxAge(time);
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
}
// (3)重定向index主页面,回显用户信息
response.sendRedirect(request.getContextPath() + "/index.jsp");
} else {
// 登录失败
// (1)保存登录失败信息
request.setAttribute("errerMsg", "用户名或密码错误");
// (2)重定向login登录页面,回显错误信息
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package c8p5;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogoutServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.清除session中保存的用户信息
request.getSession().removeAttribute("user");
// 2.清除cookie中保存的用户信息(cookie覆盖)
Cookie cookie = new Cookie("autoLogin", "");
cookie.setMaxAge(0);
cookie.setPath(request.getContextPath());
response.addCookie(cookie);
// 3.重定向到index主页面
response.sendRedirect(request.getContextPath() + "/index.jsp");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
step4:Servlet(Filter)
package c8p5;
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.Cookie;
import javax.servlet.http.HttpServletRequest;
public class AutoLoginFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//类型强转为HttpServletRequest对象之后才有getCookies()方法
HttpServletRequest request = (HttpServletRequest) req;
// 1.获取所有的cookie信息
Cookie[] cookies = request.getCookies();
// 2.遍历cookie,找到保存了用户名与密码的cookie(autoLogin)
String username_passwd = null;
for (int i = 0; cookies != null && i < cookies.length; ++i) {
String cookieName = cookies[i].getName();
if ("autoLogin".equals(cookieName)) {
username_passwd = cookies[i].getValue();
break;
}
}
// 3.如果获取到的用户名与密码不为空,则做自动登录
if (username_passwd != null) {
String[] parts = username_passwd.split("-");
String username = parts[0];
String password = parts[1];
// (1)检查用户名和密码,若登录成功则将用户信息user对象存入session域中
if ("itcast".equals(username) && ("123456").equals(password)) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
request.getSession().setAttribute("user", user);
}
}
// 3.放行
chain.doFilter(request, response);
}
public void destroy() {
// TODO Auto-generated method stub
}
}
step5:xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!-- c8p5 -->
<filter>
<filter-name>AutoLoginFilter</filter-name>
<filter-class>c8p5.AutoLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AutoLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>c8p5.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>c8p5.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/LogoutServlet</url-pattern>
</servlet-mapping>
</web-app>
step6:自动登录检验
根据实验目的:在完成首次登录网站后,在一定时间内将能够直接访问index主页而跳过login界面:
测试1:尝试使用url地址直接访问主页(由于没有Cookie登录信息,将显示用户登录超链接)
测试2:尝试正常首次登录首页
测试3:尝试使用url地址直接访问主页(由于已经保存了Cookie信息,应自动登录)
注意:如果在首次登录之后点击了注销,则自动登录功能将失效(Logout.servlet删除客户端中的Cookie)
6.应用2:Filter实现全站编码统一
在web开发中经常会遇到中文乱码问题,根据之前知识可通过在Servlet程序中设置编码方式解决,
但如果多个Servlet程序都需要设置编码方式,则将导致大量的重复代码(可以在Filter中对获取到的请求和响应消息进行编码),
从而实现全站编码统一。
实现步骤:
- 提供form.jsp中文数据提交页面
- CharacterFilter对提交的参数进行中文乱码问题的处理
- CharacterServlet测试
难点:在于创建CharacterRequest对象,来增强getParameter方法用于处理get请求的中文乱码问题。
step1:form.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8" import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>form.jsp</title>
</head>
<body style="text-align: center;">
<h3>用户登录</h3>
<a href="<%=request.getContextPath() %>/CharacterServlet?name=lch&password=123456">点击超链接进行登录</a>
<form action="${pageContext.request.contextPath}/CharacterServlet" method="post">
<table border="1" width="600px" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
<tr>
<td height="30">用户名:</td>
<td> <input type="text" name="name" />${errerMsg}</td>
</tr>
<tr>
<td height="30">密 码:</td>
<td> <input type="password" name="password" /></td>
</tr>
<tr>
<td height="30" colspan="2">
<input type="submit" value="登录" />
<input type="reset" value="重置" />
</td>
</tr>
</table>
</form>
</body>
</html>
step2:Servlet(CharacterServlet)
package c8p6;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CharacterServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(request.getParameter("name"));
System.out.println(request.getParameter("password"));
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
step3:Servlet(Filter)
package c8p6;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.management.RuntimeErrorException;
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.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
public class CharacterFilter implements Filter {
public void init(FilterConfig arg0) throws ServletException {}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
// 1.处理响应中文乱码问题
request.setCharacterEncoding("text/html; charset = utf-8");
// 2.处理请求中文乱码问题
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
request.setCharacterEncoding("utf-8");
}
CharacterRequest characterRequest = new CharacterRequest(request);
chain.doFilter(characterRequest, response);
}
public void destroy() {}
}
// 利用装饰者模式增强getParameter方法
class CharacterRequest extends HttpServletRequestWrapper {
//step1.引入被增强的对象
private HttpServletRequest request;
public CharacterRequest(HttpServletRequest req) {
//step2.通过构造方法对引入的对象进行赋值
super(req);
this.request = req;
}
//step3.增强getParameter方法
public String getParameter(String name) {
// 1.调用被包装对象的getParameter()方法,获得请求参数
String value = super.getParameter(name);
if (value == null)
return null;
// 2.获取请求的方式
String method = super.getMethod();
// (1)处理get请求的中文乱码问题方式
if (method.equalsIgnoreCase("get")) {
try {
value = new String(value.getBytes("iso-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// 3.解决乱码后返回结果
return value;
}
}
step4:xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!-- c8p6 -->
<servlet>
<servlet-name>CharacterServlet</servlet-name>
<servlet-class>c8p6.CharacterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CharacterServlet</servlet-name>
<url-pattern>/CharacterServlet</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CharacterFilter</filter-name>
<filter-class>c8p6.CharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterFilter</filter-name>
<url-pattern>/7</url-pattern>
</filter-mapping>
</web-app>
step5:统一编码测试
POST方式提交测试:在form表单中输入账号与密码信息后,点击提交按钮进行数据提交,查看控制台输出:
GET方式提交测试:在form.jsp页面中直接点击超链接,进行直接登录,查看控制台输出信息:
总结:两次输出结果相同都没有出现乱码问题,说明Filter过滤器快捷的完成了统一编码的功能。
二、Listener监听器
1.Listener监听器概述:
在程序开发中需要对某些事件进行监听,监听器在监听的过程中主要有以下几个部分:
- 事件(Event):用户的任意一个操作
- 事件源:产生事件的对象
- 事件监听器(Listener):负责监听发生在事件源上的事件
- 事件处理器:监听器的成员方法,当事件发生时会触发对应的处理器(成员方法)
事件处理器在进行工作时,可分为几个步骤具体如下:
步骤 | 说明 |
---|---|
step1:注册监听器 | 将监听器绑定到事件源 |
step2:传递事件对象 | 事件发生时会触发监听器的成员方法(事件处理器) |
step3:处理事件源 | 事件处理器通过事件对象获得事件源,并对进行处理 |
在Web开发中使用的监听器(Servlet事件监听器),就是一个实现了特定接口的Java程序,
专门用于监听Web应用程序中ServletContext、HttpSession和ServletRequest等域对象的创建与销毁、监听域对象属性的修改,
及感知HttpSession域中某个对象的状态,具体如下图所示:
注意:HttpSessionActivationListener用于监听HttpSession中对象活化和钝化的过程
Web服务器会根据监听器实现的接口,将其注册到被监听的对象上,
当触发了某个对象的监听事件时,Web容器将会调用Servlet监听器与之相关的方法对事件进行处理。
2.应用1:监听域对象的生命周期:
要对Servlet域对象的生命周期进行监听,
首先要实现域对象对应的ServletContextListener、HttpSessionListener和ServletRequestListener3个接口,
这些接口中的方法和执行过程非常类似,可以为每一个监听器编写一个单独的类,也可以用一个类实现这3个接口(从而让这个类具有3个事件监听器的功能)。
step1:myjsp.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<title>this is MyJsp.jsp page</title>
</head>
<body>
这是一个测试监听器页面!
</body>
</html>
step2:Listener
package c8p7;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MyListener implements ServletContextListener, HttpSessionListener, ServletRequestListener {
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("ServletContext对象被创建了!");
}
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("ServletContext对象被销毁了!");
}
public void requestInitialized(ServletRequestEvent arg0) {
System.out.println("ServletRequest对象被创建了!");
}
public void requestDestroyed(ServletRequestEvent arg0) {
System.out.println("ServletRequest对象被销毁了!");
}
public void sessionCreated(HttpSessionEvent arg0) {
System.out.println("HttpSession对象被创建了!");
}
public void sessionDestroyed(HttpSessionEvent arg0) {
System.out.println("HttpSession对象被销毁了!");
}
}
step3:xml配置
<!-- c8p7 -->
<listener>
<listener-class>c8p7.MyListener</listener-class>
</listener>
<session-config>
<session-timeout>1</session-timeout>
</session-config>
step4:生命周期监听测试
在tomcat启动服务器后,通过浏览器访问myjsp.jsp页面,并同时观察eclipse控制台的输出情况如下:
一分钟后,ServletSession对象也被销毁了(调用Destroyed方法)
3.应用2:监听域对象的属性变更:
通过所学监听器知识,读者应学会使用监听器监听域对象的属性变更。
step1:testAttribute.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<title>testAttribute</title>
</head>
<body>
<h5>这是一个测试对象属性信息监听器的页面</h5>
<%
getServletContext().setAttribute("username", "lch");
getServletContext().setAttribute("username", "luochenhao");
getServletContext().removeAttribute("username");
session.setAttribute("username", "lch");
session.setAttribute("username", "luochenhao");
session.removeAttribute("username");
request.setAttribute("username", "lch");
request.setAttribute("username", "luochenhao");
request.removeAttribute("username");
%>
</body>
</html>
step2:Listener
package c8p8;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class MyAttributeListener implements ServletContextAttributeListener, ServletRequestAttributeListener, HttpSessionAttributeListener {
// 1.ServletContext属性监听
public void attributeAdded(ServletContextAttributeEvent sae) {
String name = sae.getName();
System.out.println("ServletContext添加属性:" + name + "=" + sae.getServletContext().getAttribute(name));
}
public void attributeRemoved(ServletContextAttributeEvent sae) {
String name = sae.getName();
System.out.println("ServletContext移除属性:" + name + "=" + name);
}
public void attributeReplaced(ServletContextAttributeEvent sae) {
String name = sae.getName();
System.out.println("ServletContext替换属性:" + name + "=" + sae.getServletContext().getAttribute(name));
}
// 2.HttpSession属性监听
public void attributeAdded(HttpSessionBindingEvent hbe) {
String name = hbe.getName();
System.out.println("HttpSession添加属性:" + name + "=" + hbe.getSession().getAttribute(name));
}
public void attributeRemoved(HttpSessionBindingEvent hbe) {
String name = hbe.getName();
System.out.println("HttpSession移除属性:" + name);
}
public void attributeReplaced(HttpSessionBindingEvent hbe) {
String name = hbe.getName();
System.out.println("HttpSession替换属性:" + name + "=" + hbe.getSession().getAttribute(name));
}
// 3.ServletRequest属性监听
public void attributeAdded(ServletRequestAttributeEvent srae) {
String name = srae.getName();
System.out.println("ServletRequest添加属性:" + name + "=" + srae.getServletRequest().getAttribute(name));
}
public void attributeRemoved(ServletRequestAttributeEvent srae) {
String name = srae.getName();
System.out.println("ServletRequest移除属性:" + name);
}
public void attributeReplaced(ServletRequestAttributeEvent srae) {
String name = srae.getName();
System.out.println("ServletRequest替换属性:" + name + "=" + srae.getServletRequest().getAttribute(name));
}
}
step3:xml配置
<!-- c8p8 -->
<listener>
<listener-class>c8p8.MyAttributeListener</listener-class>
</listener>
step4:属性监听测试
在tomcat启动服务器后,通过浏览器访问testAttribute.jsp页面,并同时观察eclipse控制台的输出情况如下:
可以看出在三个域对象中,分别完成了属性增加、替换与删除的监听操作