第9章 Servlet高级
学习目标
- 了解Filter及其相关API、熟悉Filter的生命周期
- 掌握Filter的实现、映射与使用
- 熟悉Listener及相关API
- 熟悉Servlet 3.0新特性
- 掌握文件上传和下载
Servlet规范有三个高级特性,分别是Filter、Listener和文件的上传下载。
- Filter用于修改request、response对象,
- Listener用于监听context、session、request事件。
- 文件的上传下载
1. Filter
1.1 什么是Filter
在Servlet高级特性中,Filter被称为过滤器,Filter基本功能就是对Servlet容器调用Servlet的过程进行拦截,它位于客户端和处理程序之间,能够对请求和响应进行检查和修改。
Filter在Web应用中的拦截过程,当客户端对服务器资源发出请求时,服务器会根据过滤规则进行检查,如果客户的请求满足过滤规则,则对客户请求进行拦截,对请求头和请求数据进行检查或修改,并依次通过过滤器链,最后把过滤之后的请求交给处理程序,处理程序处理后再将响应依次逆向通过过滤器对响应进行处理。请求信息在过滤器链中可以被修改,也可以根据客户端的请求条件不将请求发往处理程序。
Filter除了可以实现拦截功能,还可以提高程序的性能,在Web开发时,不同的Web资源中的过滤操作可以放在同一个Filter中完成,这样可以不用多次编写重复代码,从而提高了程序的性能。
1.2 Filter相关API
Filter接口
方法声明 | 功能描述 |
---|---|
init(FilterConfig filterConfig) | init()方法是Filter的初始化方法,创建Filter实例后将调用init()方法。该方法的参数filterConfig用于读取Filter的初始化参数。 |
doFilter(ServletRequest request,ServletResponse response,FilterChain chain) | doFilter()方法完成实际的过滤操作,当客户的请求满足过滤规则时,Servlet容器将调用过滤器的doFilter()方法完成实际的过滤操作。doFilter()方法有多个参数,其中,参数request和response为Web服务器或Filter链中的上一个Filter传递过来的请求和响应对象;参数chain代表当前Filter链的对象。 |
destroy() | 该方法用于释放被Filter对象打开的资源,例如关闭数据库和 IO流。destroy()方法在Web服务器释放Filter对象之前被调用。 |
FilterConfig接口
方法声明 | 功能描述 |
---|---|
String getFilterName() | 返回Filter的名称 |
ServletContext getServletContext() | 返回FilterConfig对象中封装的ServletContext对象 |
String getInitParameter(String name) | 返回名为name的初始化参数值 |
Enumeration getInitParameterNames() | 返回Filter所有初始化参数的枚举 |
FilterChain接口
1.3 Filter的生命周期
Filter的生命周期可分为创建、执行、销毁三个阶段。
- 创建阶段: Web服务器启动的时候会创建Filter实例对象,并调用init()方法,完成对象的初始化。Filter对象只会创建一次,init()方法也只会执行一次。
- 执行阶段: 当客户端请求目标资源时,服务器会筛选出符合映射条件的Filter,并按照Filter名(若没有Filter名则采用类名)的先后顺序依次执行doFilter() 方法。doFilter()方法可以执行多次。
- 销毁阶段: 服务器关闭时,Web服务器调用destroy()方法销毁Filter对象
1.4 实现第一个Filter
参照ch04创建ch09_ls项目,创建ch09首页index.jsp
<%@ page contentType="text/html;charset=UTF-8"%>
<html>
<head>
<title>Servlet高级</title>
</head>
<body>
<h1>ch09 Servlet高级(ls)</h1>
<hr>
<a href="01myServlet">1. 实现第一个Filter</a><br>
<a href="02forwardServlet">2. Filter映射</a><br>
<a href="03myServlet">3. Filter链</a><br>
<a href="04/index.jsp">4. Filter在Cookie自动登录中的使用</a><br>
<a href="05myjsp.jsp">5. 监听域对象的生命周期</a><br>
<a href="06FileUpForm.jsp">6. 文件上传(commons-fileupload/commons-io)</a><br>
<a href="07FileUpForm.html">7. 文件上传(使用javax.servlet.http.Part)</a><br>
<a href="08download.jsp">8. 下载</a><br>
<a href="09/index_ls.jsp">9. 首页(09/index_ls.jsp)访问次数统计</a><br>
<a href="10index_ls.jsp">10. 图片资源防盗过滤器(保护/images/下资源)</a><br>
<a href="11/index.jsp">11. Login过滤器(只有登录的用户才能访问/ch09_ls/11/目录及其子目录的资源)</a><br>
<a href="12/index.jsp">12. 敏感文字过滤(过滤:糟糕,混蛋,他妈的,他奶奶的)</a><br>
<a href="13/index.jsp">13. 登录(使用监听查看在线用户)</a> <a href="13/showUser.jsp">查看在线用户</a><br>
<hr>
<p><a href="http://101.42.158.247/21javaweb.html">返回课程首页</a>
ls <script>document.write(document.lastModified); </script> 制作</p>
</body>
</html>
运行结果如下:
cn.ls.servlet.MyServlet.java
package cn.ls.servlet;
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 java.io.IOException;
@WebServlet("/01myServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("--------Hello MyServlet. 嗨,ls。<br>");
}
}
运行结果如下:
cn.ls.filter.MyFilter.java
package cn.ls.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter("/01myServlet")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter 初始化...应用程序启动时初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletResponse.setContentType("text/html;charset=utf-8");
PrintWriter out=servletResponse.getWriter();
out.write("Hello MyFilter. ls的过滤器。<br>");
out.write("--------- filterChain.doFilter 之前: 请求预处理 ----------<br>");
filterChain.doFilter(servletRequest, servletResponse);
out.write("--------- filterChain.doFilter 之后:响应后处理 -----------<br>");
}
@Override
public void destroy() {
System.out.println("MyFilter 销毁... 应用程序停止时销毁");
}
}
运行结果如下:
@WebFilter注解的常用属性
属性名 | 类型 | 描述 |
---|---|---|
filterName | String | 指定过滤器的名称。默认是过滤器类的名称。 |
urlPatterns | String[] | 指定一组过滤器的URL匹配模式。 |
value | String[] | 该属性等价于urlPatterns属性。urlPatterns和value属性不能同时使用。 |
servletNames | String[] | 指定过滤器将应用于哪些Servlet。取值是 @WebServlet 中的 name 属性的取值。 |
dispatcherTypes | DispatcherType | 指定过滤器的转发模式。具体取值包括:ERROR、FORWARD、INCLUDE、REQUEST。 |
initParams | WebInitParam[] | 指定过滤器的一组初始化参数。 |
1.5 Filter映射
Filter的映射方式可分为两种:
使用通配符“*”拦截用户所有请求
使用dispatcherTypes属性拦截不同访问方式的请求
@WebFilter注解有一个特殊的属性dispatcherTypes,它可以指定过滤器的转发模式,dispatcherTypes属性有4个常用值:REQUEST、INCLUDE、FORWARD和ERROR。
- REQUEST:如果用户通过RequestDispatcher对象的include()方法或forward()方法访问目标资源,那么过滤器不会被调用;除此之外,该过滤器会被调用。默认的方式:对请求进行拦截
- INCLUDE:如果用户通过RequestDispatcher对象的include()方法访问目标资源,那么过滤器将被调用;除此之外,该过滤器不会被调用。
- FORWARD:如果通过RequestDispatcher对象的forward()方法访问目标资源,那么过滤器将被调用;除此之外,该过滤器不会被调用。
- ERROR:如果通过声明式异常处理机制调用目标资源,那么过滤器将被调用。除此之外,过滤器不会被调用。
cn/ls/servlet/ForwardServlet.java
package cn.ls.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;
@WebServlet("/02forwardServlet")
public class ForwardServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/02first.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
cn/ls/filter/ForwardFilter.java
package cn.ls.filter;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
/*@WebFilter("/02first.jsp")*/
@WebFilter(urlPatterns = "/02first.jsp", dispatcherTypes = DispatcherType.FORWARD)
public class ForwardFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
out.write("Hello FilterTest. ForwardFilter过滤器。");
}
}
web/02first.jsp
<%@ page contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<html>
<head>
<title></title>
</head>
<body>
first.jsp
</body>
</html>
运行结果如下:
1.6 Filter链
cn.ls.servlet.MyServlet03.java
package cn.ls.servlet;
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 java.io.IOException;
@WebServlet("/03myServlet")
public class MyServlet03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("Hello MyServlet03. 嗨,ls。<br>");
}
}
cn.ls.filter.MyFilter01.java
package cn.ls.filter;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
@WebFilter("/03myServlet")
public class MyFilter01 implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
// 过滤器对象在初始化时调用,可以配置一些初始化参数
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
out.println("MyFilter01 之前 ls<br>");
chain.doFilter(request, response);
out.println("MyFilter01 之后 ls<br>");
}
public void destroy() {
// 过滤器对象在销毁时自动调用,释放资源
}
}
cn.ls.filter.MyFilter02.java
package cn.lp.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter("/03myServlet")
public class MyFilter02 implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
out.println("MyFilter02 Before 之前<br>");
chain.doFilter(request, response);
out.println("MyFilter02 After 之后<br>");
}
}
运行结果如下:
任务:Filter在Cookie自动登录中的使用
要实现Filter对Cookie进行拦截并实现自动登录,需要有以下几个文件。
- (1) 需要有一个User实体类,用于封装用户的信息。
- (2) 需要有一个login.jsp的登录页面,在该页面中编写一个用户登录表单。用户登录表单需要填写用户名和密码,以及用户自动登录的时间。
- (3) 需要编写一个Servlet类用于处理用户的登录请求,如果输入的用户名和密码正确,则发送一个用户自动登录的Cookie,并跳转到首页,否则提示输入的用户名或密码错误,并跳转至登录页面(login.jsp),让用户重新登录;
- (4) 需要一个登录成功后的index.jsp页面。
- (5) 需要一个Servlet类用于拦截用户登录的访问请求,判断请求中是否包含用户自动登录的Cookie,如果包含则获取Cookie中的用户名和密码,并验证用户名和密码是否正确,如果正确,则将用户的登录信息封装到User对象存入Session域中,完成用户自动登录。
- (6) 需要编写一个Servlet类用于处理用户的登出请求。
(1) cn/ls/entity/User.java
package cn.ls.entity;
public class User {
private String 用户名;
private String 密码;
// getter | setter
}
(2) web/04/login.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<html>
<head>
<title>用户登录</title>
</head>
<body style="text-align: center;">
<center><h3>用户登录</h3></center>
<form action="${pageContext.request.contextPath }/04/loginServlet"
method="post">
<table border="1" width="600px" cellpadding="0" cellspacing="0"
align="center" >
<tr>
<td height="30" align="center">用户名:</td>
<td>
<input type="text" name="用户名" />${errerMsg }</td>
</tr>
<tr>
<td height="30" align="center">密 码:</td>
<td>
<input type="password" name="密码" /></td>
</tr>
<tr>
<td height="35" align="center">自动登录时间</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" align="center">
<input type="submit" value="登录" />
<input type="reset" value="重置" />
</td>
</tr>
</table>
</form>
</body>
</html>
(3) cn/ls/servlet/LoginServlet.java
package cn.ls.servlet;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import cn.ls.entity.User;
@WebServlet("/04/loginServlet")
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 获得用户名和密码
request.setCharacterEncoding("utf-8");
String username = request.getParameter("用户名");
String password = request.getParameter("密码");
// 检查用户名和密码
if ("ls".equals(username) && "123456".equals(password)) {
// 登录成功
// 将用户状态 user 对象存入 session域
User user = new User();
user.set用户名(username);
user.set密码(password);
request.getSession().setAttribute("user", user);
// 发送自动登录的cookie
String autoLogin = request.getParameter("autologin");
if (autoLogin != null) {
// 注意 cookie 中的密码要加密
Cookie cookie = new Cookie("autologin", URLEncoder.encode( username,"utf-8") + "-"
+ password);
cookie.setMaxAge(Integer.parseInt(autoLogin));
cookie.setPath(request.getContextPath()+"/04");
response.addCookie(cookie);
}
// 跳转至首页
response.sendRedirect(request.getContextPath()+"/04/index.jsp");
} else {
request.setAttribute("errerMsg", "用户名或密码错误");
request.getRequestDispatcher("/04/login.jsp")
.forward(request,response);
}
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
(4) web/04/index.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>显示登录的用户信息</title>
</head>
<body>
<br>
<center>
<h3>欢迎光临</h3>
</center>
<c:choose>
<c:when test="${sessionScope.user==null }">
<a href="${pageContext.request.contextPath }/04/login.jsp">用户登录</a>
</c:when>
<c:otherwise>
欢迎你,${sessionScope.user.用户名 }!
<a href="${pageContext.request.contextPath }/04/logoutServlet">注销</a>
</c:otherwise>
</c:choose>
<hr>
</body>
</html>
(5) cn/ls/filter/AutoLoginFilter.java
@WebFilter("/04/*") ---- 对 /04/目录下所有的web资源都执行此过滤器。
package cn.ls.filter;
import java.io.IOException;
import java.net.URLDecoder;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.*;
import cn.ls.entity.User;
@WebFilter("/04/*")
public class AutoLoginFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
// 获得一个名为 autologin 的cookie
Cookie[] cookies = request.getCookies();
String autologin = null;
for (int i = 0; cookies != null && i < cookies.length; i++) {
if ("autologin".equals(cookies[i].getName())) {
// 找到了指定的cookie
autologin = cookies[i].getValue();
break;
}
}
if (autologin != null) {
// 做自动登录
String[] parts = autologin.split("-");
String username = URLDecoder.decode(parts[0],"utf-8");
String password = parts[1];
// 检查用户名和密码
if ("ls".equals(username)&& ("123456").equals(password)) {
// 登录成功,将用户状态 user 对象存入 session域
User user = new User();
user.set用户名(username);
user.set密码(password);
request.getSession().setAttribute("user", user);
}
}
// 放行
chain.doFilter(request, response);
}
}
(6) cn.ls.servlet.LogoutServlet.java
package cn.ls.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/04/logoutServlet")
public class LogoutServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 用户注销
request.getSession().removeAttribute("user");
// 从客户端删除自动登录的cookie
Cookie cookie = new Cookie("autologin", "msg");
cookie.setPath(request.getContextPath()+"/04");
cookie.setMaxAge(0);
response.addCookie(cookie);
response.sendRedirect(request.getContextPath()+"/04/index.jsp");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
运行结果如下:
2. Listener
2.1 Listener概述
在Web程序开发中,经常需要对某些事件进行监听,以便及时作出处理,如监听鼠标单击事件、监听键盘按下事件等。为此,Servlet提供了监听器(Listener),专门用于监听Servlet事件。
Listener的重要组成部分
Listener在监听过程中会涉及几个重要组成部分,具体如下。
- (1) 事件:用户的一个操作,如单击一个按钮、调用一个方法、创建一个对象等。
- (2) 事件源:产生事件的对象。
- (3) 事件监听器:负责监听发生在事件源上的事件。
- (4) 事件处理器:监听器的成员方法,当事件发生的时候会触发对应的处理器(成员方法)。
注意:当用户执行一个操作触发事件源上的事件时,该事件会被事件监听器监听到,当监听器监听到事件发生时,相应的事件处理器就会对发生的事件进行处理。
事件监听器的工作过程
- (1) 将监听器绑定到事件源,也就是注册监听器。
- (2) 监听器监听到事件发生时,会调用监听器的成员方法,将事件对象传递给事件处理器,即触发事件处理器。
- (3) 事件处理器通过事件对象获得事件源,并对事件源进行处理。
2.2 Listener的API
Listener中8个不同的监听器接口
类型 | 描述 |
---|---|
ServletContextListener | 用于监听ServletContext对象的创建与销毁过程 |
HttpSessionListener | 用于监听HttpSession对象的创建和销毁过程 |
ServletRequestListener | 用于监听ServletRequest对象的创建和销毁过程 |
ServletContextAttributeListener | 用于监听ServletContext对象中的属性变更 |
HttpSessionAttributeListener | 用于监听HttpSession对象中的属性变更 |
ServletRequestAttributeListener | 用于监听ServletRequest对象中的属性变更 |
HttpSessionBindingListener | 用于监听JavaBean对象绑定到HttpSession对象和从HttpSession对象解绑的事件 |
HttpSessionActivationListener | 用于监听HttpSession中对象活化和钝化的过程 |
Listener中的8种Servlet事件监听器可以分为三类,具体如下。
- (1) 用于监听域对象创建和销毁的监听器:ServletContextListener接口、HttpSessionListener接口和ServletRequestListener接口。每个域对象有创建和销毁方法2个,共6个。
- (2) 用于监听域对象属性增加、更新和删除的监听器:ServletContextAttributeListener接口、HttpSessionAttributeListener接口和ServletRequestAttributeListener接口。每个域对象有增加、更新和删除属性方法3个,共9个。
- (3) 用于监听绑定到HttpSession域中某个对象状态的事件监听器:HttpSessionBindingListener接口和HttpSessionActivationListener接口。
Web对象 | Listener接口 | Event(事件) | 方法 |
---|---|---|---|
application(应用) 或(ServletContext上下文) |
ServletContextListener(Servlet上下文监听器) | ServletContextEvent(生命周期事件) | contextInitialized(ServletContextEvent arg0)--初始化 contextDestroyed(ServletContextEvent arg0)--销毁 |
ServletContextAttributeListener(Servlet上下文属性监听器) | ServletContextAttributeEvent(属性改变事件) | attributeAdded(ServletContextAttributeEvent arg0)--增加后 attributeRemoved(ServletContextAttributeEvent event)--移除后 attributeReplaced(ServletContextAttributeEvent event)--替换 |
|
session(会话) 或(HttpSession Http会话) |
HttpSessionListener(Http会话监听器) | HttpSessionEvent(生命周期事件) | sessionCreated(HttpSessionEvent se)--创建后 sessionDestroyed(HttpSessionEvent se)--销毁后 |
HttpSessionAttributeListener(Http会话属性监听器) | HttpSessionBindingEvent(属性改变事件) | attributeAdded(HttpSessionBindingEvent event)--增加后 attributeRemoved(HttpSessionBindingEvent event)--移除后 attributeReplaced(HttpSessionBindingEvent event)--替换 |
|
HttpSessionActivationListener(http会话激活监听器) | HttpSessionEvent(会话迁移事件) | sessionDidActivate(HttpSessionEvent se)--会话激活 sessionWillPassivate(HttpSessionEvent se)--会话钝化 |
|
HttpSessionBindingListener(http会话激活监听器) | HttpSessionBindingEvent(对象绑定事件) | valueBound(HttpSessionBindingEvent event)--绑定 valueUnbound(HttpSessionBindingEvent event)--解绑 |
|
HttpSessionIdListener(http会话ID改变监听器) | HttpSessionEvent(会话迁移事件) | sessionDidActivate(HttpSessionEvent se)--会话激活 sessionWillPassivate(HttpSessionEvent se)--会话钝化 |
|
request(请求) 或(ServletRequest Servlet请求) |
ServletRequestListener(Servlet请求监听器) | ServletRequestEvent(生命周期事件) | requestInitialized(ServletRequestEvent arg0)--请求初始化 requestDestroyed(ServletRequestEvent arg0)--请求销毁 |
ServletRequestAttributeListener(Servlet请求属性监听器) | ServletRequestAttributeEvent(请求属性改变事件) | attributeAdded(ServletRequestAttributeEvent arg0)--增加后 attributeRemoved(ServletRequestAttributeEvent arg0)--移除后 attributeReplaced(ServletRequestAttributeEvent arg0)--替换 |
|
AsyncListener(异步请求监听器) | AsyncEvent(异步请求事件) | onStartAsync(AsyncEvent arg0)--异步开始 onComplete(AsyncEvent arg0)--异步完成 onTimeout(AsyncEvent arg0)--异步超时 onError(AsyncEvent arg0)--异步出错 |
任务:监听域对象的生命周期
(1) cn/ls/listener/MyListener.java
package cn.ls.listener;
import javax.servlet.*;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
//@WebListener
public class MyListener implements
ServletContextListener, HttpSessionListener,ServletRequestListener {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(" yyyy-MM-dd HH:mm:ss ");
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("ServletContext被创建了【杜】"+ LocalDateTime.now().format(dateTimeFormatter)+arg0.getServletContext().getContextPath());
}
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("ServletContext被销毁了【杜】"+ LocalDateTime.now().format(dateTimeFormatter)+arg0.getServletContext().getContextPath());
}
public void requestInitialized(ServletRequestEvent arg0) {
System.out.println(" ServletRequest被创建了【杜】"+ LocalDateTime.now().format(dateTimeFormatter)+arg0.getServletRequest().getRemoteAddr());
}
public void requestDestroyed(ServletRequestEvent arg0) {
System.out.println(" ServletRequest被销毁了【杜】"+ LocalDateTime.now().format(dateTimeFormatter)+arg0.getServletRequest().getRemoteAddr());
}
public void sessionCreated(HttpSessionEvent arg0) {
System.out.println(" HttpSession被创建了【杜】"+ LocalDateTime.now().format(dateTimeFormatter)+arg0.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent arg0) {
System.out.println(" HttpSession被销毁了【杜】"+ LocalDateTime.now().format(dateTimeFormatter)+arg0.getSession().getId());
}
}
(2) web/05myjsp.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
<title>this is MyJsp.jsp page</title>
</head>
<body>
这是一个测试监听器的页面
<p>//@WebListener 运行时放开此句;在控制台截图;测试万屏蔽此句。</p>
<%
System.out.println(" 这是一个测试监听器的页面【ls】。");
%>
</body>
</html>
(3) web/WEB-INF/web.xml
<session-config>
<session-timeout>1</session-timeout>
</session-config>
(4) 运行测试
开启ch09_ls应用程序、3次单击测试网页、等待几分钟、停止ch09_ls应用程序):
已连接到服务器
[2022-10-06 12:32:53,521] 工件 ch09_ls: 正在部署工件,请稍候…
ServletContext被创建了【杜】 2022-10-06 00:32:54 /ch09_ls
...
ServletRequest被创建了【杜】 2022-10-06 00:32:54 127.0.0.1
HttpSession被创建了【杜】 2022-10-06 00:32:55 9323175E2E7F95D2703299EAFFBF09BD
ServletRequest被销毁了【杜】 2022-10-06 00:32:55 127.0.0.1
ServletRequest被创建了【杜】 2022-10-06 00:32:55 127.0.0.1
HttpSession被创建了【杜】 2022-10-06 00:32:55 0965BC39BA900B491CAA293F070880A2
ServletRequest被销毁了【杜】 2022-10-06 00:32:55 127.0.0.1
ServletRequest被创建了【杜】 2022-10-06 00:33:02 127.0.0.1
这是一个测试监听器的页面【ls】。
ServletRequest被销毁了【杜】 2022-10-06 00:33:03 127.0.0.1
...
ServletRequest被创建了【杜】 2022-10-06 00:33:10 127.0.0.1
这是一个测试监听器的页面【ls】。
ServletRequest被销毁了【杜】 2022-10-06 00:33:10 127.0.0.1
HttpSession被销毁了【杜】 2022-10-06 00:34:53 9323175E2E7F95D2703299EAFFBF09BD
HttpSession被销毁了【杜】 2022-10-06 00:34:53 0965BC39BA900B491CAA293F070880A2
ServletRequest被创建了【杜】 2022-10-06 00:35:34 127.0.0.1
HttpSession被创建了【杜】 2022-10-06 00:35:34 730CB686A570F522C77C08DA3C745B36
这是一个测试监听器的页面【ls】。
ServletRequest被销毁了【杜】 2022-10-06 00:35:34 127.0.0.1
HttpSession被销毁了【杜】 2022-10-06 00:36:53 730CB686A570F522C77C08DA3C745B36
C:\apache-tomcat-9.0.65\bin\catalina.bat stop
...
ServletContext被销毁了【杜】 2022-10-06 00:37:04 /ch09_ls
...
已与服务器断开连接
(5) 测试后善后
注意:测试完本题后web.xml屏蔽到有关session超时的设置、MyListener中的监听器注解,以防引起其他的应用程序的运行激活本监听器的运行。代码如下。
<!-- <session-config>
<session-timeout>1</session-timeout>
</session-config>-->
//@WebListener
public class MyListener implements
3. Servlet 3.0新特性
web.xml配置 --新增-->注解配置、同步Servlet执行流程 --新增--> 异步Servlet执行流程
(1) Servlet 3.0注解新特性
Servlet 3.0的新特性,例如,已经使用过的@WebServlet注解、@WebFilter注解,注解就是Servlet 3.0的新特性之一,通过使用注解的方式简化了Servlet的配置。在Servlet 3.0之前,web框架需要在web.xml中配置。在Servlet 3.0之后,可以用注解的方式配置web框架,简化了web框架的开发。
(2) Servlet 3.0常见的注解:
- @WebServlet :修饰Servlet类,用于部署Servlet类。
- @WebFilter:修饰Filter类,用于部署Filter类
- @WebListener:修饰Listener类,用于部署Listener类。
- @WebInitParam:与@WebServlet或@WebFilter注解连用,为@WebServlet或@WebFilter注解配置参数。
- @MultipartConfig:修饰Servlet类,指定Servlet类负责处理multipart/form-data类型的请求(主要用于处理上传文件)
- @ServletSecurity:修饰Servlet类,与JAAS(Java验证和授权API)有关的注解。
(3) 普通Servlet的工作流程
在Servlet 3.0之前,一个普通Servlet的工作流程大致如下。
- (1)Servlet接收到请求之后,对请求携带的数据进行一些预处理。
- (2)调用业务接口的某些方法,完成业务处理。
- (3)最后根据处理的结果提交响应,Servlet 线程结束。
在上述的Servlet工作流程的第(2)步是业务处理通常是最耗时的,这主要体现在数据库操作,以及其他的跨网络调用等。在此过程中,Servlet线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务的过程中,Servlet资源一直被占用而得不到释放,对于并发较大的应用,可能造成性能瓶颈。对于这个问题,Servlet 3.0之前,通常是采用提前结束Servlet线程的方式,及时释放资源。
(4) 异步处理的Servlet工作流程
Servlet 3.0的异步处理特性可以提高Web程序的接口处理速度。
Servlet 3.0通过异步处理,将之前的Servlet工作流程进行了调整,具体如下。
- (1)Servlet接收到请求之后,首先对请求携带的数据进行一些预处理。
- (2)Servlet线程将请求转交给一个异步线程执行业务处理。
- (3)线程本身返回至Web容器,此时Servlet还没有生成响应数据。
- (4)异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest 和ServletResponse对象的引用),或者将请求继续转发给其他Servlet。
注意:如此一来,Servlet线程不再是一直处于阻塞状态等待业务逻辑处理完成,而是启动异步线程之后可以立即返回。
(5) Web.xml配置开启异步处理
对于使用web.xml文件配置Servlet和过滤器的情况,Servlet 3.0在Servlet标签中增加了async-supported子标签,该标签的默认取值为false,要启用异步处理支持,将其设置为 true 即可。
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>cn.itcast.chapter09.filter.MyServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
(6) 注解配置开启异步处理
对于使用Servlet 3.0提供的@WebServlet和@WebFilter注解对 Servlet 或过滤器进行配置的情况,由于这两个注解都提供了asyncSupported属性,因此可以通过设置asyncSupported属性值开启异步处理。asyncSupported默认值为false,要启用异步处理支持,只需将该属性设置为true即可。
@WebFilter(filterName="MyFilter",urlPatterns="/MyServlet",asyncSupported=true)
4. 文件的上传和下载
4.1 文件上传的原理
文件上传
要实现Web开发中的文件上传功能,通常需完成两步操作:一是在Web项目的页面中添加上传输入项,二是在Servlet中读取上传文件的数据,并保存到目标路径中。 由于大多数文件的上传都是通过表单的形式提交给服务器的,因此,要想在程序中实现文件上传功能,首先要创建一个用于提交上传文件的表单页面。在表单页面中,需要使用标签在jsp页面中添加文件上传输入项。
<input type="file">标签
<input type="file">标签的使用需要注意以下两点:
- 必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。
- 必须把将表单页面的method属性设置为post方式,enctype属性设置为“multipart/form-data”类型。
<%--指定表单数据的enctype属性以及提交方式--%>
<form enctype="multipart/form-data" method="post">
<%--指定标记的类型和文件域的名称--%>
选择上传文件:<input type="file" name="myfile"/><br />
</form>
4.2 认识Commons-FileUpload组件
Commons-FileUpload组件
当浏览器通过表单提交上传文件时,文件数据都附带在HTTP请求消息体中,并且采用MIME类型(多用途互联网邮件扩展类型)进行描述,在后台可以使用request对象提供的getInputStream()方法读取客户端提交过来的数据。但由于用户可能会同时上传多个文件,而在Servlet端直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作。为了方便处理用户上传的数据,Apache组织提供了一个开源组件Commons-FileUpload,该组件可以方便地将“multipart/form-data”类型请求中的各种表单域解析出来,并实现一个或多个文件的上传,同时也可以限制上传文件的大小等。Commons-FileUpload组件性能十分优异,并且使用非常简单。
需要注意的是,在使用Commons-FileUpload组件时,需要导入commons-fileupload.jar和commons-io.jar两个JAR包,这两个JAR包可以在Apache官网下载。
http://archive.apache.org/dist/commons/fileupload/binaries
commons-fileupload-1.4-bin.zip
commons-fileupload-1.4.jar
https://commons.apache.org/proper/commons-io/download_io.cgi
commons-io-2.11.0-bin.zip
commons-io-2.11.jar
FileItem接口
FileItem接口主要用于封装单个表单字段元素的数据,一个表单字段元素对应一个FileItem对象。Commons-FileUpload组件在处理文件上传的过程中,将每一个表单域(包括普通的文本表单域和文件域)封装在一个FileItem对象中。
为了便于讲解,在此将FileItem接口的实现类称为FileItem类,FileItem类实现了序列化接口Serializable,因此,FileItem类支持序列化操作。
FileItem类的方法
方法声明 | 功能描述 |
---|---|
boolean isFormField() | isFormField()方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通文本表单字段则返回true,否则返回false。 |
String getName() | getName()方法用于获取文件上传字段中的文件名。如果FileItem类对象对应的是普通文本表单字段,getName()方法将返回null,否则,只要浏览器将文件的字段信息传递给服务器,getName()方法就会返回一个字符串类型的结果,如C:\Sunset.jpg。 |
String getFieldName() | getFieldName()方法用于获取表单字段元素描述头的name属性值,也是表单标签name属性的值。例如“name=file1”中的“file1”。 |
void write(File file) | write()方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,那么该方法顺利完成后,临时文件有可能会被清除。另外,该方法也可将普通表单字段内容写入到一个文件中,但它主要用于将上传的文件内容保存到本地文件系统中。 |
String getString() | getString()方法用于将FileItem对象中保存的数据流内容以一个字符串形式返回。它有两个重载的定义形式:①public String getString()②public String getString(java.lang.String encoding)前者使用默认的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。 |
String getContentType() | getContentType()方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。 |
DiskFileItemFactory类
DiskFileItemFactory类用于将请求消息实体中的每一个文件封装成单独的FileItem对象。如果上传的文件比较小,将直接保存在内存中,如果上传的文件比较大,则会以临时文件的形式,保存在磁盘的临时文件夹中。默认情况下,不管文件保存在内存还是磁盘临时文件夹,文件存储的临界值是10240字节,即10KB。
DiskFileItemFactory类的构造方法
方法声明 | 功能描述 |
---|---|
DiskFileItemFactory() | 采用默认临界值和系统临时文件夹构造文件项工厂对象 |
DiskFileItemFactory(int sizeThreshold,File repository) | 采用参数指定临界值和系统临时文件夹构造文件项工厂对象 |
上表中列举了DiskFileItemFactory类的两个构造方法,其中,第二个构造方法需要传递两个参数,第一个参数sizeThreshold表示文件保存在内存还是磁盘临时文件夹中的临界值,第二个参数repository表示临时文件的存储路径。
ServletFileUpload类
ServletFileUpload类是Apache组件处理文件上传的核心高级类,通过调用parseRequest(HttpServletRequest) 方法可以将HTML中每个表单提交的数据封装成一个FileItem对象,然后以List列表的形式返回。
ServletFileUpload类的构造方法
方法声明 | 功能描述 |
---|---|
ServletFileUpload() | 构造一个未初始化的ServletFileUpload实例对象 |
ServletFileUpload(FileItemFactory fileItemFactory) | 根据参数指定的FileItemFactory 对象创建一个ServletFileUpload对象 |
上表中列举了ServletFileUpload类的两个构造方法。在文件上传过程中,在使用第一个构造方法创建ServletFileUpload对象时,需要在解析请求之前调用setFileItemFactory()方法设置fileItemFactory属性。
4.3 动手实践:实现文件上传
将commons-fileupload-1.4.jar和commons-io-2.11.jar复制到项目后还需要在“Molsles”中配置jar包。
(1) cn/ls/servlet/UploadServlet.java
package cn.ls.servlet;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
//上传文件的Servlet类
@WebServlet("/06uploadServlet")
//该注解用于标注文件上传的Servlet
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request,
HttpServletResponse response)throws ServletException, IOException {
try {
//设置ContentType字段值
response.setContentType("text/html;charset=utf-8");
// 创建DiskFileItemFactory工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置文件缓存目录,如果该目录不存在则新创建一个
File f = new File("E:\\TempFolder");
if (!f.exists()) {
f.mkdirs();
}
// 设置文件的缓存路径
factory.setRepository(f);
// 创建 ServletFileUpload对象
ServletFileUpload fileupload = new ServletFileUpload(factory);
//设置字符编码
fileupload.setHeaderEncoding("utf-8");
// 解析 request,得到上传文件的FileItem对象
List<FileItem> fileitems = fileupload.parseRequest(request);
//获取字符流
PrintWriter writer = response.getWriter();
// 遍历集合
for (FileItem fileitem : fileitems) {
// 判断是否为普通字段
if (fileitem.isFormField()) {
// 获得字段名和字段值
String name = fileitem.getFieldName();
if(name.equals("name")){
//如果文件不为空,将其保存在value中
if(!fileitem.getString().equals("")){
String value = fileitem.getString("utf-8");
writer.print("上传者:" + value + "<br />");
}
}
} else {
// 获取上传的文件名
String filename = fileitem.getName();
//处理上传文件
if(filename != null && !filename.equals("")){
writer.print("上传的文件名称是:" + filename + "<br />");
// 截取出文件名
filename = filename.substring(filename.lastIndexOf("\\") + 1);
// 文件名需要唯一
filename = UUID.randomUUID().toString() + "_" + filename;
// 在服务器创建同名文件
String webPath = "/upload/";
//将服务器中文件夹路径与文件名组合成完整的服务器端路径
String filepath = getServletContext()
.getRealPath(webPath + filename);
// 创建文件
File file = new File(filepath);
file.getParentFile().mkdirs();
file.createNewFile();
// 获得上传文件流
InputStream in = fileitem.getInputStream();
// 使用FileOutputStream打开服务器端的上传文件
FileOutputStream out = new FileOutputStream(file);
// 流的拷贝
byte[] buffer = new byte[1024];//每次读取1个字节
int len;
//开始读取上传文件的字节,并将其输出到服务端的上传文件输出流中
while ((len = in.read(buffer)) > 0)
out.write(buffer, 0, len);
// 关闭流
in.close();
out.close();
// 删除临时文件
fileitem.delete();
writer.print("上传文件成功!<br />");
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)throws ServletException, IOException {
doGet(request, response);
}
}
(2) web/06FileUpForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="06uploadServlet" method="post"
enctype="multipart/form-data">
<table width="600px">
<tr>
<td>上传者</td>
<td><input type="text" name="name" /></td>
</tr>
<tr>
<td>上传文件</td>
<td><input type="file" name="myfile"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="上传" /></td>
</tr>
</table>
</form>
</body>
</html>
运行结果如下:
4.4 使用API实现文件上传
核心:servlet3.0的Part接口
- 通过@MultipartConfig注解和HttpServletRequest的两个新方法getPart()和getParts()相互辅助来达到当前端form表单使用 enctype="multipart/form-data"上传文件和文本两种数据时的功能。
- .使用@MultipartConfig注解后不仅可以使后端的request.getParameter(NAME)能够获取到文本数据的值,还可以使用servlet3新增的part接口,该接口中有两个新方法可以直接获取文件数据并写入保存路径中。所以说这样是非常的方便简洁。
- HttpServletRequest的两个新方法getPart()和getParts()是为了接收前端form表单传过来的文件数据,getPart()只能接收一个文件,getParts()可以接收多个文件
- part.getSubmittedFileName() 直接获取文件名,不需要去截取那么麻烦了。
@MultipartConfig注解有四个属性
- fileSizeThreshold:整数值设置,默认值为0,若上传文件的大小超过了这个值,就会先写入缓存文件。
- location:上传文件的保存路径。
- maxFileSize:限制文件上传大小。默认值为-1L,表示不限制大小。
- maxRequestSize:限制multipart/form-data请求格式,默认值为-1L,表示不限制个数
(1) cn/ls/servlet/UploadServlet2.java
package cn.ls.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
@WebServlet("/uploadServlet2")
// 使用注解@MultipartConfig将一个Servlet标识为支持文件上传
@MultipartConfig
public class UploadServlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
// 存储路径
String savePath = request.getServletContext().getRealPath("/upload");
// 获取上传的文件集合
Collection<Part> parts = request.getParts();
String desc = request.getParameter("desc");
PrintWriter out = response.getWriter();
out.println("上传者="+desc+"<br>");
for (Part part : parts) {
// 获取文件名
String fileName = part.getSubmittedFileName();
// 把文件写到指定路径
if (fileName != null) {
part.write(savePath + File.separator + fileName);
out.println("文件名称="+fileName+"<br>");
}
}
out.println("上传成功<br>");
out.flush();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
(2) web/07FileUpForm.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Servlet3.1实现文件上传</title>
</head>
<body>
<h2>Servlet3.1 API实现文件上传</h2>
<hr>
<fieldset>
<legend> 上传多个文件 </legend>
<form action="uploadServlet2" method="post" enctype="multipart/form-data">
上传者: <input type="text" name="desc"> <br>
上传文件: <input type="file" name="file" multiple> <br>
<input type="submit" value="上传多个文件">
</form>
</fieldset>
</body>
</html>
运行结果如下:
4.5 文件下载原理
文件下载的要求
实现文件下载功能比较简单,文件下载一般不需要使用第三方组件实现,而是直接使用Servlet类和输入/输出流实现。与访问服务器文件不同的是,要实现文件的下载,不仅需要指定文件的路径,还需要在HTTP协议中设置两个响应消息头,具体如下:
// 设定接收程序处理数据的方式
Content-Disposition: attachment;
// 设定实体内容的MIME类型
filename = Content-Type:application/x-msdownload
文件下载的过程
浏览器通常会直接处理响应的实体内容,需要在HTTP响应消息中设置两个响应消息头字段,用来指定接收程序处理数据内容的方式为下载。当单击“下载”超链接时,系统将请求提交到对应的Servlet。在Servlet中,首先获取下载文件的地址,并根据文件下载地址创建文件字节输入流,然后通过输入流读取要下载的文件内容,最后将读取的内容通过输出流写到目标文件中。
4.6 动手实践:实现文件下载
cn/ls/servlet/DownloadServlet.java
package cn.ls.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/08downloadServlet")
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
//设置ContentType字段值
response.setContentType("text/html;charset=utf-8");
//获取所要下载的文件名称
String filename = request.getParameter("filename");
//下载文件所在目录
String folder = "/download/";
// 通知浏览器以下载的方式打开
response.addHeader("Content-Type", "application/octet-stream");
response.addHeader("Content-Disposition",
"attachment;filename="+filename);
folder=folder+filename;
// 通过文件流读取文件
InputStream in = getServletContext().getResourceAsStream(folder);
// 获取response对象的输出流
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
//循环取出流中的数据
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
doGet(request, response);
}
}
web/08download.jsp
<%@ page contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件下载</title>
</head>
<body>
<a href= "08downloadServlet?filename=1.png">
文件下载</a>
</body>
</html>
运行结果如下:
5. 过滤器、监听器应用
5.1 首页(09/index_ls.jsp)访问次数统计
cn/ls/filter/CountFilter_ls.java
package cn.ls.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(
urlPatterns = { "/09/index_ls.jsp" },
initParams = {
@WebInitParam(name = "09count", value = "5000")
})
public class CountFilter_ls implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String param = filterConfig.getInitParameter("09count");
filterConfig.getServletContext().setAttribute("count",param);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 将ServletRequest转换成HttpServletRequest
HttpServletRequest req = (HttpServletRequest) servletRequest;
// 获取ServletContext
ServletContext context = req.getServletContext();
int count = Integer.parseInt((String) context.getAttribute("count")); // 访问数量自增
count ++;
// 将来访数量值放入到ServletContext中
context.setAttribute("count", Integer.toString(count));
// 向下传递过滤器
filterChain.doFilter(req, servletResponse);
}
}
web/09/index_ls.jsp
<%@ page contentType="text/html;charset=UTF-8"%>
<html>
<head>
<meta charset="UTF-8">
<title>首页访问次数统计</title>
</head>
<body>
<h2>首页访问次数统计</h2>
<hr>
<h2>欢迎光临,您是本站的第【 <%=application.getAttribute("count") %> 】次访访问!</h2>
</body>
</html>
运行结果如下:
5.2 图片资源防盗过滤器(保护/images/下资源)
web/images 4张图片
web/images/126.jpg
web/images/127.jpg
web/images/128.jpg
web/images/129.jpg
web/error.gif
web/images/test.txt
春风不度玉门关
cn/ls/filter/ImageRedirectFilter.java
package cn.ls.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/images/*")
public class ImageRedirectFilter implements Filter {
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);
// 链接来源地址
String referer = request.getHeader("referer");
if (referer == null || !referer.contains(request.getServerName())) {
// 如果 链接地址来自其他网站,则返回错误图片
request.getRequestDispatcher("/error.gif").forward(request, response);
} else {
//图片资源请求通过
chain.doFilter(request, response);
}
}
}
web/10index_ls.jsp
<%@ page contentType="text/html;charset=UTF-8"%>
<html>
<head>
<meta charset="UTF-8">
<title>图片防盗链</title>
</head>
<body>
<h2>图片防盗链</h2>
<hr>
<img alt="可爱的孩子1" src="images/126.jpg"><br>
<img alt="可爱的孩子2" src="images/127.jpg"><br>
<img alt="可爱的孩子3" src="images/128.jpg"><br>
<img alt="可爱的孩子4" src="images/129.jpg"><br>
<a href="images/test.txt">文本连接</a><br>
</body>
</html>
运行结果如下:
5.3 Login过滤器(只有登录的用户才能访问/ch09_ls/11/目录及其子目录的资源)
web/11/test.html, web/11/sub/test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>劝君更尽一杯酒,西出阳关无故人。</title>
</head>
<body>
劝君更尽一杯酒,西出阳关无故人。
</body>
</html>
web/11/test.txt, web/11/sub/test.txt
莫愁前路无知己,天下谁人不识君。
web/11/test.jpg, web/11/sub/test.jpg
cn/ls/servlet/LoginServlet11.java
package cn.ls.servlet;
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 java.io.IOException;
@WebServlet("/11/loginServlet")
public class LoginServlet11 extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
// 获取用户名和密码
String name = request.getParameter("username");
String pass = request.getParameter("password");
// 验证用户名和密码是否正确
if (checkLogin(name, pass)) {
// 判断是自动登录处理
request.getSession().setAttribute("user11", name);
request.getSession().removeAttribute("msg");
// 成功登陆后的操作
response.sendRedirect("index.jsp");
} else {
request.getSession().removeAttribute("user11");
request.getSession().setAttribute("msg", "用户名或者密码错误");
response.sendRedirect("login.jsp");
}
}
protected boolean checkLogin(String name, String pass) {
return "ls".equals(name) && "123456".equals(pass);
}
}
cn/ls/filter/LoginFilter.java
package cn.ls.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(urlPatterns = { "/11/*" }, dispatcherTypes = {
DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE,
DispatcherType.ERROR })
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 把ServletRequest和ServletResponse转换成真正的类型
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
// 由于web.xml中设置Filter过滤全部请求,可以排除不需要过滤的url
String requestURI = req.getRequestURI();
if (requestURI.endsWith("/11/loginServlet")) {
chain.doFilter(request, response);
return;
}
if (requestURI.endsWith("/11/login.jsp")) {
chain.doFilter(request, response);
return;
}
// 判断用户是否登录,进行页面的处理
if (null == session.getAttribute("user11")) {
// 未登录用户,重定向到登录页面
((HttpServletResponse) response).sendRedirect(req.getContextPath()
+ "/11/login.jsp");
return;
} else {
// 已登录用户,允许访问
chain.doFilter(request, response);
}
}
}
web/11/login.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>系统登录</title>
</head>
<body>
${sessionScope.msg}<br>
<form method="post" action="loginServlet">
<label for="user">用户名</label><input type="text" name="username" id="user" required><br>
<label for="pass">密码</label><input type="password" name="password" id="pass" required><br>
<input type="submit" name="Submit" value="登录">
</form>
<hr>
<a href="index.jsp">登录前访问index.jsp</a><br>
<a href="test.html">登录前访问test.html</a><br>
<a href="test.txt">登录前访问test.txt</a><br>
<a href="test.jpg">登录前访问test.jpg</a><br>
<a href="abcd.jpg">登录前访问不存在的abcd.jpg</a><br>
<a href="sub/test.html">登录前访问sub/test.html</a><br>
<a href="sub/test.txt">登录前访问sub/test.txt</a><br>
<a href="sub/test.jpg">登录前访问sub/test.jpg</a><br>
<a href="sub/abcd.jpg">登录前访问不存在的sub/abcd.jpg</a><br>
</body>
</html>
web/11/logOut.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
session.invalidate();
%>
<jsp:forward page="login.jsp" />
web/11/index.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<title>XXXX管理系统</title>
</head>
<body>
<h1>XXXX管理系统</h1>
<hr>
用户名:${sessionScope.user}|<a href="logOut.jsp">用户注销</a>
<hr>
<a href="index.jsp">登录后访问index.jsp</a><br>
<a href="test.html">登录后访问test.html</a><br>
<a href="test.txt">登录后访问test.txt</a><br>
<a href="test.jpg">登录后访问test.jpg</a><br>
<a href="abcd.jpg">登录后访问不存在的abcd.jpg</a><br>
<a href="sub/test.html">登录后访问sub/test.html</a><br>
<a href="sub/test.txt">登录后访问sub/test.txt</a><br>
<a href="sub/test.jpg">登录后sub/test.jpg</a><br>
<a href="sub/abcd.jpg">登录后访问不存在的sub/abcd.jpg</a><br>
</body>
</html>
运行结果如下:
5.4 敏感文字过滤(过滤:糟糕,混蛋,他妈的,他奶奶的)
cn/ls/servlet/MessageServlet.java
package cn.ls.servlet;
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 java.io.IOException;
@WebServlet("/12/MessageServlet")
public class MessageServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取标题, 内容
String title = request.getParameter("title");
String content = request.getParameter("content");
// 将标题、内容放置到request中
request.setAttribute("title", title);
request.setAttribute("content", content);
// 转发到result.jsp页面
request.getRequestDispatcher("result.jsp").forward(request, response);
}
}
cn/ls/filter/WordFilter.java
package cn.ls.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
@WebFilter(urlPatterns = { "/12/*" }, initParams = { @WebInitParam(name = "encoding", value = "utf-8") })
public class WordFilter implements Filter {
private String[] words; // 非法字符数组
private String encoding; // 字符编码
@Override
public void init(FilterConfig fConfig) throws ServletException {
// 获取字符编码
encoding = fConfig.getInitParameter("encoding");
// 初始化非法字符数组
words = new String[] { "糟糕", "混蛋", "他妈的", "他奶奶的" };
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 判断字符编码是否有效
if (encoding != null) {
// 设置request字符编码
request.setCharacterEncoding(encoding);
// 将request转换为重写后的Request对象
request = new Request((HttpServletRequest) request);
// 设置response字符编码
response.setContentType("text/html; charset=" + encoding);
}
chain.doFilter(request, response);
}
/**
* 内部类重写HttpServletRequestWrapper
*/
class Request extends HttpServletRequestWrapper {
// 构造方法
public Request(HttpServletRequest request) {
super(request);
}
// 重写getParameter()方法
@Override
public String getParameter(String name) {
// 返回过滤后的参数值
return filter(super.getRequest().getParameter(name));
}
// 重写getParameterValues()方法
@Override
public String[] getParameterValues(String name) {
// 获取所有参数值
String[] values = super.getRequest().getParameterValues(name);
// 通过循环对所有参数值进行过滤
for (int i = 0; i < values.length; i++) {
values[i] = filter(values[i]);
}
// 返回过滤后的参数值
return values;
}
}
/**
* 过滤非法字符
* @param param 参数值
* @return 过滤后的参数值
*/
public String filter(String param) {
try {
// 判断非法字符是否被初始化
if (words != null && words.length > 0) {
// 循环替换非法字符
for (String word : words) {
// 判断是否包含非法字符
if (param.contains(word)) {
// 将非法字符替换为"****"
param = param.replaceAll(word, "****");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return param;
}
}
web/12/index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>意见反馈</title>
</head>
<body>
<form action="MessageServlet" method="post">
<fieldset>
<legend>意见反馈</legend>
<label>标题:</label><input type="text" name="title" size="30"><br>
<label>内容:</label><textarea rows="5" cols="40" name="content"></textarea><br>
<input type="submit" value="提 交">
</fieldset>
</form>
</body>
web/12/result.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String title = (String) request.getAttribute("title");
String content = (String) request.getAttribute("content");
if (title != null && !title.isEmpty()) {
out.println("<h2'>标题:" + title + "</h2>");
}
if (content != null && !content.isEmpty()) {
out.println("<p>内容:" + content + "</p>");
}
%>
运行结果如下:
5.5 登录(使用监听查看在线用户)
cn.ls.entity.UserInfo.java
package cn.ls.entity;
public class UserInfo {
private String 登录名;
private String 会话Id; //当前用户的session id
private String ip; //当前用户的ip地址
private String 登录时间;
@Override
public String toString() {
return "UserInfo{" +
"登录名='" + 登录名 + '\'' +
", 会话Id='" + 会话Id + '\'' +
", ip='" + ip + '\'' +
", 登录时间='" + 登录时间 + '\'' +
'}';
}
public String get登录名() {
return 登录名;
}
public void set登录名(String 登录名) {
this.登录名 = 登录名;
}
public String get会话Id() {
return 会话Id;
}
public void set会话Id(String 会话Id) {
this.会话Id = 会话Id;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String get登录时间() {
return 登录时间;
}
public void set登录时间(String 登录时间) {
this.登录时间 = 登录时间;
}
}
cn.ls.util.SessionUtil.java
package cn.ls.util;
import cn.ls.entity.UserInfo;
import java.util.ArrayList;
public class SessionUtil {
//根据sessionId判断当前用户是否存在在集合中 如果存在 返回当前用户 否则返回null
public static UserInfo getUserBySessionId(ArrayList<UserInfo> userList, String sessionId) {
for (UserInfo user : userList) {
if(sessionId.equals(user.get会话Id())){
return user;
}
}
return null;
}
}
cn/ls/listener/MyHttpSessionListener.java
package cn.ls.listener;
import cn.ls.entity.UserInfo;
import cn.ls.util.SessionUtil;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.ArrayList;
@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//sessionDestroyed 用户数-1
int userCounts = (int)se.getSession().getServletContext().getAttribute("userCounts");
if (userCounts > 0 ) userCounts--;
//重新在servletContext中保存userCounts
se.getSession().getServletContext().setAttribute("userCounts", userCounts);
@SuppressWarnings("unchecked")
ArrayList<UserInfo> userList=(ArrayList<UserInfo>) se.getSession().getServletContext().getAttribute("userList");
String sessionId=se.getSession().getId();
//如果当前用户在userList中 在session销毁时 将当前用户移出userList
if(SessionUtil.getUserBySessionId(userList, sessionId)!=null){
userList.remove(SessionUtil.getUserBySessionId(userList, sessionId));
}
//将userList集合 重新保存到servletContext
se.getSession().getServletContext().setAttribute("userList", userList);
}
}
cncn/ls/listener/MyServletRequestListener.java
package cn.ls.listener;
import cn.ls.entity.UserInfo;
import cn.ls.util.SessionUtil;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
/**
* ServletRequestListener 监听器
*
*/
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
//用户集合
private ArrayList<UserInfo> userList;
@SuppressWarnings("unchecked")
@Override
public void requestInitialized(ServletRequestEvent arg0) {
//从servletContext中获的userList
userList = (ArrayList<UserInfo>) arg0.getServletContext().getAttribute("userList");
//如果servletContext中没有userList对象 初始化userList
if (userList == null) {
userList = new ArrayList<UserInfo>();
}
HttpServletRequest request = (HttpServletRequest) arg0.getServletRequest();
try {
request.setCharacterEncoding("utf-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
if(request.getRequestURI().endsWith("/13/adlsser.jsp")) {
// 用户数+1
int userCounts;
Object userCounts1 = arg0.getServletContext().getAttribute("userCounts");
if (userCounts1 == null)
userCounts=0;
else
userCounts=(int) userCounts1;
userCounts++;
//重新在servletContext中保存userCounts
arg0.getServletContext().setAttribute("userCounts", userCounts);
//sessionId
String sessionId = request.getSession().getId();
String username = request.getParameter("登录名");
request.getSession().setMaxInactiveInterval(10);
//如果当前sessionId不存在集合中 创建当前user对象
if (SessionUtil.getUserBySessionId(userList, sessionId) == null) {
UserInfo user = new UserInfo();
user.set会话Id(sessionId);
user.set登录名(username);
user.setIp(request.getRemoteAddr());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E hh:mm:ss");
user.set登录时间(sdf.format(new Date()));
userList.add(user);
}
//将userList集合 保存到ServletContext
arg0.getServletContext().setAttribute("userList", userList);
}
}
}
web/13/adlsser.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%
response.sendRedirect("showUser.jsp");
%>
web/13/showUser.jsp
<%@ page contentType="text/html; charset=UTF-8" errorPage=""%>
<%@ page import="java.util.*"%>
<%@ page import="cn.ls.entity.UserInfo" %>
<html>
<head>
<meta charset="UTF-8">
<title>使用监听查看在线用户</title>
</head>
<body>
<%
ArrayList<UserInfo> userList = (ArrayList<UserInfo>) request.getServletContext().getAttribute("userList");
%>
<h2>查看在线用户(20秒不和服务器交互、服务器端自动断开)</h2><hr>
在线人数:${userCounts} <br>
<textarea rows="8" cols="160">
<%
if(userList!=null && userList.size()>0){
for(UserInfo userInfo : userList ){
out.println(userInfo.toString());
}
}
%>
</textarea> <br>
<a href="loginOut.jsp">安全退出(注销、登出)</a>
</body>
</html>
web/13/loginOut.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%
session.invalidate();
out.println("<script>parent.location.href='index.jsp';</script>");
%>
web/13/index.jsp
<%@ page contentType="text/html; charset=UTF-8" errorPage=""%>
<html>
<head>
<meta charset="UTF-8">
<title>使用监听查看在线用户</title>
</head>
<body>
<form name="form" method="post" action="adlsser.jsp">
<input type="text" name="登录名" required><br>
<input type="submit" name="Submit" value="登录">
</form>
</body>
</html>
运行结果如下:
Servlet3.0、4.0的新特性
概述3.0新特性:
- 异步处理的支持:支持异步处理之后Servlet线程将不会因为等待业务处理完成而产生阻塞。支持异步处理之后,Servlet线程可以将耗时的操作委派给另一个线程进行操作,自己则在不生成响应的情况下返回容器。当面对业务处理耗时较长的情况时,这将大大降低对服务器资源的占用,并提高并发处理的速度。
- 新增的注解支持:3.0版本增加了若干注解,用于简化Servlet、过滤器(Filter)和监听器(Listener)中对Servlet的声明,这使得web.xml部署描述文件从3.0版本开始不再是必选的了。
- 可插性支持:3.0版本中,开发者可以通过插件的方式很方便的扩充已有web应用的功能,而不需要修改原有的应用。
以前的Servlet的流程:
- Servlet接收到请求之后,需要对请求携带的数据进行一些处理
- 调用业务接口的某些方法,已完成业务处理
- 根据处理的结果提交响应,Servlet线程结束
其中第二步的业务处理通常是最耗时的,这主要体现数据库操作,以及其它的跨网络调用等、在此过程中、Servlet线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务过程中、Servlet资源一直被占用而得不到释放,对于并发较大的应用,这有可能造成性能瓶颈,对此,在以前通常采用私有解决方案,来提前结束Servlet线程,并及时释放资源。
现在的Servlet的流程:
- Servlet接收到请求之后,需要对请求携带的数据进行一些处理
- Servlet线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器。此时Servlet还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest和ServletResponse对象的引用),或者将请求继续转发给其它的Servlet
如此一来,Servlet线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回。
概述4.0新特性:
- 服务器推送:最直观的 HTTP/2 强化功能,通过 PushBuilder 接口在 servlet 中公开。服务器推送功能还在 JavaServer Faces API 中实现,并在 RenderResponsePhase 生命周期内调用,以便 JSF 页面可以利用其增强性能。
- 全新 servlet 映射发现接口:HttpServletMapping 使框架能够获取有关激活给定 servlet 请求的 URL 信息。这可能对框架尤为有用,这些框架需要这一信息来运行内部工作
Servlet4.0的优点:
- 服务器推送使服务器能预测客户端请求的资源需求。然后,在完成请求处理之前,它可以将这些资源发送到客户端。
- 要了解服务器推送的好处,可以考虑一个包含图像和其他依赖项(比如 CSS 和 JavaScript 文件)的网页。客户端发出一个针对该网页的请求。服务器然后分析所请求的页面,确定呈现它所需的资源,并主动将这些资源发送到客户端的缓存。
在执行所有这些操作的同时,服务器仍在处理原始网页请求。客户端收到响应时,它需要的资源已经位于缓存中。Servlet 4.0 通过 PushBuilder 接口公开服务器推送、也可以推送静态资源等。