JavaWeb③JSP、状态管理、ServletContext
1、JSP
Java Server Pages:Java 服务端页面
已过时,本节简单介绍原理。
1.1、动态网页技术
JSP 是动态网页技术
-
既可以定义 HTML、JS、CSS 等静态内容,还可以定义 Java 代码的动态内容。
-
即
JSP = HTML + Java
<html> <head> <title>Title</title> </head> <body> <h1>JSP,Hello World</h1> <% System.out.println("hello,jsp~"); %> </body> </html>
1.2、使用
-
添加 web 框架支持
-
导入依赖:Servlet、JSP
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency>
-
创建 JSP:webapp(web) 目录下
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Hello</title> </head> <body> <h1>Hello JSP!</h1> <% System.out.println("Hello JSP!"); %> </body> </html>
-
启动 Tomcat
1.3、原理
1.3.1、JSP 就是 Servlet
-
hello.jsp
首次被访问 -
Tomcat 将
hello.jsp
转换为 Servlet (hello_jsp.java
) -
Tomcat 将 Servlet 编译成字节码文件(
hello_jsp.class
) -
Tomcat 执行该字节码文件,向外提供服务
1.3.2、xxx_ jsp.java
文件位置
Tomcat 项目可能存在的位置(不同电脑、不同版本有所不同)
-
C:\Users\用户名\.IntelliJIdea\system\tomcat
-
C:\Users\用户名\AppData\Local\JetBrains\IntelliJIdea\system\tomcat
,项目名可能是随机字符串,根据文件修改日期即可确定。
Java 文件位置:\work\Catalina\localhost\项目名\org\apache\jsp
查看
hello_jsp.java
文件
继承自 HttpJspBase
1.3.3、HttpJspBase
查看 Tomcat 源码中的 HttpJspBase
-
位置:
java\org\apache\jasper\runtime
-
继承自
HttpServlet
1.3.4、结论
Tomcat 将 JSP 转换为 xxx_ jsp.java
文件
- 该 Java 文件继承自
HttpJspBase
HttpJspBase
继承自HttpServlet
即:JSP 就是 Servlet。
2、状态管理
HTTP 无状态协议
HTTP 不保存通信状态,对于发送过的请求或响应都不做持久化处理。
- 每当有新的请求发出,都会有新的响应。
- 单凭 HTTP 无法完成登录等操作。
- HTTP/1.1 中引入 Cookie 技术,实现保持状态功能。
参考 HTTP 知识点
- 无状态协议【2.1】
- HTTP 缓存【5】
状态管理
-
概念:将客户端与服务器的多次交互视为一个整体,并保存多次交互的数据(即状态)。
-
分类
-
客户端技术:Cookie,将状态保存在客户端。
-
服务器端技术:Session,将状态保存在服务器端。
(注:服务器通过 Cookie 传递 SessionId)
-
2.1、Cookie
Cookie 以键值对形式表示(name-value)
通过在请求和响应报文中写入 Cookie 信息,来控制客户端的状态。
-
服务器端:在响应报文中添加 Set-Cookie 首部字段,通知客户端保存 Cookie。
-
客户端:将 Cookie 信息保存在本地,在请求报文中添加 Cookie 值。
2.1.1、创建(!)
使用 new 关键字,实例化 Cookie 类。
(参数:
String name, String value
)
Cookie 常用方法
- setPath():设置路径,访问该路径时会产生 cookie
- setMaxAge():设置最大寿命(有效期、过期时间),默认 -1。
- 正数:有效期的秒数。
- 负数:不持久存储 cookie,相当于
no-store
。 - cookie 值为 0 时将被删除。
设置 cookie:response 的 addCookie()
相当于在响应报文中添加 Set-Cookie 首部字段。
doGet(req, resp) {
// 创建
Cookie cookie = new Cookie("username", "jaywee");
// 路径
cookie.setPath("/cc");
// 有效期:1小时
cookie.setMaxAge(60 * 60);
// 设置cookie
resp.addCookie(cookie);
}
2.1.2、查看、获取
浏览器查看 Cookie
两种查看方式
-
Chrome 浏览器:
设置-安全和隐私设置-Cookie
,查看所有当前浏览器保存的所有 cookie。 -
控制台:
Network-Headers
,查看访问当前 URL 的涉及的 cookie。
获取 Cookie
通过 request 对象获取
-
可能存在多个 cookie,因此获取的是数组类型。
-
可以遍历检索需要的 cookie。
doGet(req, resp) { // 获取 Cookie[] cookies = req.getCookies(); // 检索 if (cookies != null) { for (Cookie cookie : cookies) { if ("username".equals(cookie.getName())) { // 取得cookie break; } } } }
2.1.3、修改
思路:创建一个同名同路径的 Cookie,覆盖原有 cookie。
注:只要名称或路径不同,就是不同的 cookie。
// 原Cookie
doGet(req, resp) {
Cookie cookie = new Cookie("username", "jaywee");
cookie.setPath("/cc");
cookie.setMaxAge(60 * 60);
// 设置
resp.addCookie(cookie);
}
// 新cookie
doGet(req, resp) {
Cookie cookie = new Cookie("username", "secretmrj");
cookie.setPath("/cc");
// 覆盖
resp.addCookie(cookie);
}
2.1.4、编码问题(!)
Cookie 默认不支持中文,只能包含 ASCII 字符。
使用 Cookie 需要对 Unicode 字符编码,避免乱码。
编码 | 解码 | |
---|---|---|
名称 | encode | decode |
相关 API | java.net.URLEncoder |
java.net.URLDecoder |
相关方法 | encode(String str,String encoding) |
decode(String str,String encoding) |
示例
// 设置带中文的Cookie
@WebServlet("/ec")
public class EncodeSevlet extends HttpServlet {
doGet(req, resp) {
// 编码
Cookie cookie = new Cookie(URLEncoder.encode("用户名", "UTF-8"), "jaywee");
cookie.setPath("/ec, /dc");
// 设置cookie
resp.addCookie(cookie);
}
}
// 获取带中文的Cookie
@WebServlet("/ec")
public class DecodeSevlet extends HttpServlet {
doGet(req, resp) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
// 解码
String username = URLDecoder.decode(cookie.getName(), "UTF-8");
String value = URLDecoder.decode(cookie.getValue(), "utf-8");
}
}
}
}
2.1.5、特点
优点
- 简单性:cookie 是基于文本的轻量结构,由键值对形式表示。
- 状态管理:为 HTTP 无状态协议提供状态管理功能。
- 数据持久性:cookie 在到期之前,可以一直保存在客户端。
- 可配置到期规则、路径等。
缺点
- 大小限制:大多数浏览器对 cookie 有大小限制(如 4K)
- 功能限制:如果用户禁用了客户端设备(如浏览器)接收 cookie 的能力,则无法使用。
- 安全风险:cookie 可能被篡改或盗取,造成安全性风险。
2.2、Session
Session(会话)
在一段时间内,单个客户端与 Web 服务器的一连串交互过程。
- 同一个浏览器从打开到关闭之前,就是一次会话。
- Session 用于记录用户的状态。
- 开启会话时,服务器自动创建 Session,给客户端响应一个存储 sessionId 的 cookie。
2.2.1、查看、获取
浏览器查看 Session
参考查看 Cookie 的方式,因为 SessionId 是存储在 cookie 中的。
-
Chrome 浏览器:
设置-安全和隐私设置-Cookie
,查看所有当前浏览器保存的所有 cookie。 -
控制台:
Network-Headers
,查看访问当前 URL 的涉及的 cookie。
获取 Session
通过 request 对象获取
-
Session 在一次会话中有效,期间的多次请求同属于一次 Session,因此获取的是 Session 对象(而非数组)
-
Session 的唯一标记是 SessionId。
@WebServlet("/gs") public class GetSessionServlet extends HttpServlet { doGet(req, resp) { HttpSession session = req.getSession(); session.getId(); } }
2.2.2、作用域
类似 Request,Session 也有作用域。
-
作用域范围:一次会话。
-
数据存取(键值对)
- 存数据:
setAttribute(String key, Object value)
,可存储任意类型数据 - 取数据:
getAttribute(String key)
- 删数据:
req.removeAttribute(String name)
// 存数据 @WebServlet("/ss1") public class ScopeSessionServlet1 extends HttpServlet { @Override doGet(req, resp) { HttpSession session = req.getSession(); // 存数据 User user = new User("Jaywee"); session.setAttribute("user", user); } } // 取数据 @WebServlet("/ss2") public class ScopeSessionServlet2 extends HttpServlet { doGet(req, resp) { HttpSession session = req.getSession(); // 取数据 User user = (User) session.getAttribute("user"); System.out.println("获取用户:" + user); } } // 删数据 @WebServlet("/ss3") public class ScopeSessionServlet3 extends HttpServlet { doGet(req, resp) { HttpSession session = req.getSession(); session.removeAttribute("user"); } }
- 存数据:
2.2.3、生命周期
-
开始:打开一个会话,第一次向服务器发出请求。
-
结束
-
浏览器关闭
-
Session 超时:
setMaxlnactivelnterval(seconds)
-
Session 销毁:
invalidate()
// 1小时后失效 session.setMaxInactiveInterval(60 * 60); // 失效 session.invalidate();
-
2.2.4、应用:用户认证
思路
- 用户登录后,将用户信息存入 Session。
- 用户进行相应业务操作时,判断 Session 中是否存在用户信息。
实现
登录:设置 Session(通常用户信息是从前端表单接收)
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
doGet(req, resp) {
HttpSession session = req.getSession();
if (session.getAttribute("user") == null) {
session.setAttribute("user", user);
}
// 其他操作
}
// doPost()
}
业务操作:判断 Session
@WebServlet("/operate")
public class OperateServlet extends HttpServlet {
doGet(req, resp) {
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
// 重定向到登录页
}
// 完成业务操作
}
// doPost()
}
3、ServletContext
Servlet 上下文
ServletContext 具有全局作用域,范围是一个 Web 应用。
- 创建:Web 服务器启动时,为每个 Web 应用程序创建一个 ServletContext。
- 销毁:服务器关闭(重启)时销毁。
3.3.1、获取
getServletContext()
说明:有三种获取方式,获取的是同一个 ServletContext 对象。
说明 | 备注 | |
---|---|---|
GenericServlet | 通过当前 Servlet 获取(推荐) | HttpServlet 继承自该类,因此可通过 this 调用 |
ServletRequest | 通过当前请求获取(推荐) | HttpServletRequest 继承自该类,因此可通过 req 调用 |
HttpSession | 通过当前会话获取 | 需要先获取 Session,再通过 Session 调用 |
伪代码
doGet(req, resp) {
ServletContext context1 = this.getServletContext();
ServletContext context2 = req.getServletContext();
ServletContext context3 = req.getSession().getServletContext();
}
3.3.2、作用域
-
作用域范围:一个 Web 应用。
-
数据存取(键值对)
-
存数据:
setAttribute(String key, Object value)
,可存储任意类型数据 -
取数据:
getAttribute(String key)
-
删数据:
req.removeAttribute(String name)
ServletContext context = req.getServletContext(); // 存 User user = new User("Jaywee"); context.setAttribute("user", user); // 取 context.getAttribute("user"); // 删 context.removeAttribute("user");
-
3.3.3、获取路径
ServletContext 提供了很多方法:获取路径、添加过滤器、添加监听器等。
有三种常用路径:真实路径、上下文路径、资源路径。
说明 | 方法 | |
---|---|---|
真实路径 | 抽象 URL 对应的真实项目路径 | getRealPath(String) |
上下文路径 | 项目访问路径,即为模块配置 Tomcat 时的路径 | getContextPath() |
资源路径 | 资源 | getResourcesPath(String) |
示例
伪代码
@WebServlet("/gp")
public class GetPathServlet extends HttpServlet {
doGet(req, resp) {
ServletContext context = req.getServletContext();
// 真实路径
String realPath = context.getRealPath("/");
String realPath1 = context.getRealPath("/gp");
// 上下文路径
String contextPath = context.getContextPath();
// 资源路径
Set<String> resourcePaths = context.getResourcePaths("/WEB-INF");
}
}
输出情况
4、作用域小结
4.1、对比
相同点:有作用域,可以存取、删除数据。
Request | Session | ServletContext | |
---|---|---|---|
范围 | 一次请求 | 一次会话 | 一个 web 应用 |
创建时期 | 客户端发送请求 | 客户端开启会话(打开浏览器访问服务器) | 启动 web 服务器 |
销毁时期 | 请求响应后 | 会话关闭或超时 | 服务器关闭(重启) |
获取方式 | 通过 Request 对象 | 通过 Request 对象 | 通过 GenericServlet、Request 、Session 对象 |
应用场景 | 通过转发,在页面之间传递数据 | 权限验证 | 统计网站访问量 |
4.2、开发技巧
作用域中的数据都是以键值对的形式保存(String-Object)
通常声明一个枚举类(常量类),作为各种数据的键。
示例
不使用枚举(常量)类:每个地方都定义字面量(魔法值),代码重复且容易出错。
// 存数据
User user = new User("Jaywee");
session.setAttribute("user", user);
// 取数据
User user = (User) session.getAttribute("user");
// 删数据
session.removeAttribute("user");
使用枚举(常量)类:提高代码复用性,减少出错。
// 常量类
public class UserConstant {
public static final String LOGIN_USER = "user";
}
// 存数据
User user = new User("Jaywee");
session.setAttribute(UserConstant.LOGIN_USER, user);
// 取数据
User user = (User) session.getAttribute(UserConstant.LOGIN_USER);
// 删数据
session.removeAttribute(UserConstant.LOGIN_USER);