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、使用

  1. 添加 web 框架支持

  2. 导入依赖: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>
    
  3. 创建 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>
    
  4. 启动 Tomcat

    image-20220326140357418

1.3、原理

1.3.1、JSP 就是 Servlet

  1. hello.jsp 首次被访问

  2. Tomcat 将 hello.jsp 转换为 Servlet (hello_jsp.java

  3. Tomcat 将 Servlet 编译成字节码文件(hello_jsp.class

  4. Tomcat 执行该字节码文件,向外提供服务

    image-20210818111039350

1.3.2、xxx_ jsp.java

文件位置

Tomcat 项目可能存在的位置(不同电脑、不同版本有所不同)

  1. C:\Users\用户名\.IntelliJIdea\system\tomcat

  2. C:\Users\用户名\AppData\Local\JetBrains\IntelliJIdea\system\tomcat,项目名可能是随机字符串,根据文件修改日期即可确定。

    image-20220326144224255

Java 文件位置\work\Catalina\localhost\项目名\org\apache\jsp

image-20220326144613671

查看 hello_jsp.java 文件

继承自 HttpJspBase

image-20220326144911945

1.3.3、HttpJspBase

查看 Tomcat 源码中的 HttpJspBase

  • 位置java\org\apache\jasper\runtime

  • 继承自 HttpServlet

    image-20220326145757169

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 值。

    image-20220303180154142

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

两种查看方式

  1. Chrome 浏览器设置-安全和隐私设置-Cookie,查看所有当前浏览器保存的所有 cookie。

  2. 控制台Network-Headers,查看访问当前 URL 的涉及的 cookie。

    image-20220322010925549

获取 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、特点

优点

  1. 简单性:cookie 是基于文本的轻量结构,由键值对形式表示。
  2. 状态管理:为 HTTP 无状态协议提供状态管理功能。
  3. 数据持久性:cookie 在到期之前,可以一直保存在客户端。
  4. 可配置到期规则、路径等。

缺点

  1. 大小限制:大多数浏览器对 cookie 有大小限制(如 4K)
  2. 功能限制:如果用户禁用了客户端设备(如浏览器)接收 cookie 的能力,则无法使用。
  3. 安全风险:cookie 可能被篡改或盗取,造成安全性风险。

2.2、Session

Session(会话)

在一段时间内,单个客户端与 Web 服务器的一连串交互过程。

  • 同一个浏览器从打开到关闭之前,就是一次会话。
  • Session 用于记录用户的状态。
  • 开启会话时,服务器自动创建 Session,给客户端响应一个存储 sessionId 的 cookie。

2.2.1、查看、获取

浏览器查看 Session

参考查看 Cookie 的方式,因为 SessionId 是存储在 cookie 中的。

  1. Chrome 浏览器设置-安全和隐私设置-Cookie,查看所有当前浏览器保存的所有 cookie。

  2. 控制台Network-Headers,查看访问当前 URL 的涉及的 cookie。

    image-20220322103916965

获取 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、生命周期

  • 开始:打开一个会话,第一次向服务器发出请求。

  • 结束

    1. 浏览器关闭

    2. Session 超时setMaxlnactivelnterval(seconds)

    3. Session 销毁invalidate()

      // 1小时后失效
      session.setMaxInactiveInterval(60 * 60);
      // 失效
      session.invalidate();
      

2.2.4、应用:用户认证

思路

  1. 用户登录后,将用户信息存入 Session。
  2. 用户进行相应业务操作时,判断 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");
    }
}

输出情况

image-20220322121855135

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);
posted @ 2021-06-05 14:34  Jaywee  阅读(195)  评论(0编辑  收藏  举报

👇