- 关于Servlet的相关内容及开发一个带有Servlet的WebApp
1.Servlet
1.1 系统架构
- 系统架构包括什么形式?
- C/S 架构
- B/S 架构
C/S 架构(
Client / Server
,客户端 / 服务器):- 特点:需要安装特定的客户端软件。
- 优点:
- 速度快:软件中的数据大部分都是集成到客户端软件当中的,很少量的数据从服务器端传送过来,所以 C/S 结构的系统速度快。
- 界面酷炫:专门的语言去实现界面,更加灵活。
- 服务器压力小:因为大量的数据都是集成在客户端软件当中,所以服务器只需要传送很少的数据量,当然服务器压力小。
- 安全:因为大量的数据是集成在客户端软件当中的,即使服务器发生故障了,数据在多个客户端上有缓存,有存储,所以不怕数据丢失问题。
- 缺点:
- 升级维护麻烦,每一个客户端软件都需要升级,成本较高。
- 娱乐性软件建议使用 C/S 架构。
B/S 架构(
Browser / Server
,浏览器 / 服务器):- 实际上 B/S 结构的系统还是一个 C/S,只不过这个 C 比较特殊,这个
Client
是一个固定不变的浏览器软件。
- 优点:
- 升级维护方便,成本较低。
- 不需要安装特定的客户端软件,用户操作方便,只需要打开浏览器,输入网址即可。
- 缺点:
- 速度慢:不是因为带宽低的问题,是因为所有的数据都是在服务器上,用户发送的每一个请求都需要服务器的响应,所以 B/S 结构的系统在网络中传送的数据量比较大。
- 不安全:所有的数据都在服务器上,只要服务器发生故障,软件服务就会瘫痪,数据也可能丢失。
- 公司内部使用的业务软件建议使用 B/S 架构。
开发 B/S 架构的系统,其实就是开发网站,就是开发一个 Web 系统。
- Java 做 Web 开发我们称为 JavaWeb 开发,JavaWeb 开发最核心的规范就是 Servlet(
Server Applet
,服务器端的 Java 小程序)。
- 实际上 B/S 结构的系统还是一个 C/S,只不过这个 C 比较特殊,这个
1.2 B/S架构通信原理
一个
Web
系统的通信原理(以百度为例):- 用户输入网址
URL
:http://www.baidu.com
。 - 域名系统
DNS
对URL
进行域名解析:http://153.3.238.110:80/index.html
。 - 浏览器软件在网络中搜索
153.3.238.110
这一台主机,直到找到这台主机。 - 该主机根据端口号
80
定位其上的服务器软件。 - 服务器上
80
端口对应的服务器软件得知浏览器请求的资源名是index.html
。 - 服务器软件找到
index.html
文件,并将该文件的内容直接输出到请求服务的浏览器软件上,作为响应。 - 浏览器软件接收到来自服务器的代码,并进行渲染,向用户展示结果。
- 用户输入网址
什么是请求,什么是响应:
请求和响应实际上说的是数据的流向不同。
从 Browser 端发送数据到 Server 端,我们称为请求,
request
。从 Server 端向浏览器 Browser 端发送数据,我们称为响应,
response
。B -> S(request)
S -> B(response)
1.3 Web服务器
Web服务器有哪些:
- Tomcat(Web服务器)
- jetty(Web服务器)
- JBOSS(应用服务器)
- WebLogic(应用服务器)
- WebSphere(应用服务器)
应用服务器和 Web服务器的关系:
应用服务器实现了JavaEE 的所有规范(JavaEE 有 13 个不同的规范)。
Web服务器只实现了 JavaEE 中的
Servlet + JSP
两个核心的规范。说明应用服务器是包含 Web服务器的。
1.4 Tomcat服务器
关于 Tomcat 服务器的目录:
bin
:Tomcat 服务器的命令文件存放目录,比如:启动Tomcat、关闭Tomcat等。conf
:Tomcat 服务器的配置文件存放目录(server.xml
文件中可以配置端口号,默认 Tomcat 端口是 8080)。lib
:Tomcat 服务器的核心程序目录,因为 Tomcat 服务器是 Java 语言编写的,这里的 jar 包里面都是 class 文件。logs
:Tomcat 服务器的日志目录,Tomcat 服务器启动等信息都会在这个目录下生成日志文件。temp
:Tomcat 服务器的临时目录,存储临时文件。webapps
:这个目录就是用来存放大量的 webapp(web application :web应用)。work
:这个目录用来存放 JSP 文件翻译之后的 Java 文件以及编译之后的 class 文件。
Tomcat 还有另外一个名字:catalina(catalina 是美国的一个岛屿,风景秀丽,据说作者是在这个风景秀丽的小岛上开发了一个轻量级的 Web 服务器,体积小,运行速度快,因此 Tomcat 又被称为 catalina)。
启动 Tomcat 服务器需要:
配置 Tomcat 服务器的环境变量,在
Path
中配置 Tomcat 的bin
目录:C:\Program Files\Apache Software Foundation\Tomcat 8.5_Running_Noob\bin
- 根据这个找到 Tomcat 启动的
startup.bat
文件
- 根据这个找到 Tomcat 启动的
配置 Tomcat 服务器的环境变量:
CATALINA_HOME=C:\Program Files\Apache Software Foundation\Tomcat 8.5_Running_Noob
因为
startup.bat
文件中需要用到CATALINA_HOME
环境变量:1
2
3
4
5
6
7
8
9
10
11
12
13
14rem Guess CATALINA_HOME if not defined
set "CURRENT_DIR=%cd%"
if not "%CATALINA_HOME%" == "" goto gotHome
set "CATALINA_HOME=%CURRENT_DIR%"
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome所以上述
Path
中的配置可以改为%CATALINA_HOME%\bin
配置
JDK
的环境变量:JAVA_HOME=D:\Program Files\Java\jdk1.8.0_261
startup.bat
会调用catalina.bat
文件,而在catalina.bat
文件中,需要用到JAVA_HOME
环境变量:1
2
3
4
5
6
7
8
9
10
11echo Using CATALINA_BASE: "%CATALINA_BASE%"
echo Using CATALINA_HOME: "%CATALINA_HOME%"
echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"
if ""%1"" == ""debug"" goto use_jdk
echo Using JRE_HOME: "%JRE_HOME%"
goto java_dir_displayed
:use_jdk
echo Using JAVA_HOME: "%JAVA_HOME%"
:java_dir_displayed
echo Using CLASSPATH: "%CLASSPATH%"
echo Using CATALINA_OPTS: "%CATALINA_OPTS%"
启动 Tomcat 服务器:
startup.bat
关闭 Tomcat 服务器:
shutdown.bat
1.5 ★B/S架构系统的角色和协议
1.5.1 角色
对于一个动态的 Web 应用来说,有哪些角色:
- 浏览器软件(谷歌浏览器、火狐浏览器、Edge浏览器 …)
- Web Server(Tomcat、jetty、WebLogic、JBOSS、WebSphere …)
- DB Server(Oracle、Mysql …)
- WebApp(Web应用,是我们作为 JavaWeb 程序员开发的)

注意:后端到底要执行哪个 Java 程序,取决于前端浏览器发送的请求路径。一个路径对应一个
Servlet Java 程序
。而由谁来定义请求路径和 Java程序之间的对应关系呢?
当然是 WebApp 的开发程序员,因为 Java程序是由 WebApp 的开发程序员开发的。
对于我们 JavaWeb 开发者来说,我们可以指定一个配置文件,在配置文件中描述请求路径和 Java程序之间的对照关系。
由请求路径找到对应的
Servlet Java 程序
的过程:- 将前端浏览器的请求路径发送给 Web服务器。
- Web服务器解析配置文件(该配置文件由 JavaWeb 开发者来配置),通过配置文件找到请求路径所对应的 Java程序的类名。
- 在得到 Java程序的类名后,通过反射机制创建对象。
- 然后 Web服务器开始执行该 Java程序对象实现的
Servlet
接口的service
方法。
对于 JavaWeb 程序员来说,只需要做两件事:
- 编写 Java类,该类要实现 Servlet 接口。-> 开发程序
- 将编写的类配置到配置文件中,在配置文件中指定请求路径和类名的关系。-> 设置配置文件
1.5.2 协议
对于一个动态的 Web 应用来说,角色和角色之间需要遵守哪些协议:
- 浏览器软件 和 Web Server 之间有一套传输协议:**
HTTP
协议**。 - WebApp 和 Web Server 之间有一套规范:JavaEE 的规范之一 ——
Servlet
规范。Servlet
规范的作用是什么 -> WebApp 和 Web Server 之间的解耦合。使得 WebApp 不管是在 Tomcat 上还是在 jetty 上等,都能正常运行。
- WebApp 和 DB Server 之间有一套规范:**
JDBC
规范**。

- 浏览器软件 和 Web Server 之间有一套传输协议:**
1.6 Servlet本质
前面已经说过,
Servlet
是 WebApp 和 Web Server 之间的规范,其中规定了:- 开发的 Java程序:
- 一个合格的 WebApp 应该是一个怎样的目录结构。
- 一个合格的 WebApp 中的 Java程序要放在哪里。
- 设置的配置文件:
- 一个合格的 WebApp 应该有一个怎样的配置文件。 -> 配置文件名和文件内容格式是固定的,不能乱起。
- 一个合格的 WebApp 的配置文件路径是什么。 -> 配置文件是放在特定位置的,不能乱放。
- 开发的 Java程序:
Servlet
规范是一个什么规范?- 遵循
Servlet
规范的 webapp,是可以放在不同的 Web服务器中运行的(因为这个 webapp 是遵循 Servlet 规范的)。 Servlet
规范包括什么:- 规范了哪些接口
- 规范了哪些类
- 规范了一个web应用中应该有哪些配置文件
- 规范了一个web应用中配置文件的名字
- 规范了一个web应用中配置文件存放的路径
- 规范了一个web应用中配置文件的内容
- 规范了一个web应用的目录结构应该是怎样的
- 遵循
1.7 ★★★开发一个带有Servlet的WebApp
开发步骤:
在 Tomcat 的 webapps 目录下新建一个目录,起名
crm
(可以起别的,随意),那这个crm
就是 webapp 的名字。crm
就是这个 webapp 的根。
在这个 webapp 的根下新建一个目录:
WEB-INF
。- 注意:这个目录的名字是
Servlet
规范中规定的,必须全部大写,必须一模一样。必须的必须。
- 注意:这个目录的名字是
在
WEB-INF
目录下新建一个目录:classes
。- 注意:这个目录的名字必须是全部小写的
classes
,这也是Servlet
规范中规定的。另外这个目录下存放的一定是 Java程序编译之后的.class
文件。
- 注意:这个目录的名字必须是全部小写的
在
WEB-INF
目录下新建一个目录:lib
。注意:这个目录不是必须的。但如果一个 webapp 需要第三方的 jar 包的话,这个 jar 包就要放到这个
lib
目录下,这个目录的名字也不能随意编写,必须是全部小写的lib
。例如 Java 语言连接数据库需要数据库的驱动 jar 包,那么这个 jar 包就一定要放到
lib
目录下,这也是Servlet
规范中规定的。
在
WEB-INF
目录下新建一个文件:web.xml
。注意:这个文件是必须的,这个文件名必须叫做
web.xml
,这个文件必须放在这里。一个合法的 webapp,
web.xml
文件是必须的,这个web.xml
文件就是一个配置文件,在这个配置文件中描述了请求路径和Servlet
类之间的对照关系。
1
2
3
4
5
6
7
8
9
10
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0"
metadata-complete="true">
</web-app>编写 Java程序,这个程序也不能随意开发,必须实现
Servlet
接口。注意:这个
Servlet
接口不在JDK
当中(因为Servlet
不属于 JavaSE,它属于 JavaEE,是另外一套类库)。Servlet
接口(Servlet.class
文件)是 Oracle 提供的,是 JavaEE 规范中的一员。注意:从 JakartaEE 9 开始,Servlet 接口的全名改变了,从
javax.servlet.Servlet
改为了jakarta.servlet.Servlet
。注意:编写 Java程序的时候,Java 源代码你愿意放在哪里就放在哪里,位置无所谓,你只需要将 Java 源代码编译之后的
.class
文件放到classes
目录下即可。
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
39package com.bjpowernode.servlet;
// 在Tomcat9以及Tomcat9之前的版本中使用javax.servlet这个包
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletConfig;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet implements Servlet{
// 5个方法
public void init(ServletConfig config) throws ServletException{
}
public void service(ServletRequest request,ServletResponse response)
throws ServletException , IOException{
// 向控制台打印输出
System.out.println("My First Servlet, Hello Servlet");
}
public void destroy(){
}
public String getServletInfo(){
return "";
}
public ServletConfig getServletConfig(){
return null;
}
}编译写好的 Java程序,将编译后的字节码文件放在
classes
目录下。注意:怎么能让 Java程序编译通过呢?因为 Java程序肯定需要
import jakarta.servlet.Servlet;
,而我们没有这个类。我们可以配置环境变量
CLASSPATH=.;%CATALINA_HOME%\lib\servlet-api.jar
。即,在 Tomcat 中的
lib
目录下,是有jakarta.servlet.Servlet
类的,因为 Tomcat 的运行也需要用到Servlet
类。所以我们可以用 Tomcat 中的这个 jar 包,作为 import 的来源。
在
web.xml
文件中编写配置信息,让请求路径和Servlet
类名能关联在一起。- 这一步用专业术语描述:在
web.xml
文件中注册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
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0"
metadata-complete="true">
<!--servlet描述信息-->
<!--任何一个servlet都对应一个servlet-mapping -->
<servlet>
<servlet-name>helloservlet</servlet-name>
<!--这个位置必须是带有包名的全限定类名-->
<servlet-class>com.bjpowernode.servlet.HelloServlet</servlet-class>
</servlet>
<!--servlet映射信息-->
<servlet-mapping>
<!--这个也是随便的,不过这里写的内容要和上面的一样。-->
<servlet-name>helloservlet</servlet-name>
<!--这里需要一个路径-->
<!--这个路径唯一的要求是必须以 / 开始-->
<!--当前这个路径名可以随便写-->
<!--这里就是浏览器网址中输入的请求路径-->
<url-pattern>/helloservlet</url-pattern>
</servlet-mapping>
</web-app>由
<url-pattern>
对应请求路径。由
<servlet-name>
将<servlet-mapping>
和<servlet>
对应。由
<servlet-class>
找到与请求路径相对应的 Java程序的全类名。
- 这一步用专业术语描述:在
启动 Tomcat 服务器:
startup.bat
。打开浏览器,输入请求路径:
127.0.0.1:8080/crm/helloservlet
,得到响应结果:- 这里的
/helloservlet
就是<url-pattern>
里的内容。

- 注意:浏览器上的请求路径和
web.xml
文件中的<url-pattern>
的唯一区别就是:浏览器上的请求路径带项目名:/crm
。
- 这里的
总结:一个合法的 webapp 目录结构应该是怎样的?
1
2
3
4
5
6
7
8
9
10webapproot
|------WEB-INF
|------classes(存放字节码)
|------lib(第三方jar包)
|------web.xml(注册Servlet)
|------html
|------css
|------javascript
|------image
....补充:关于 JavaEE 的版本:
JavaEE 目前最高版本是 JavaEE8。
JavaEE 被 Oracle 捐献了,Oracle 将 JavaEE 规范捐献给了 Apache。
Apache 把 JavaEE 换名了,以后不叫 JavaEE了,以后叫做 Jakarta EE。
JavaEE8 版本升级之后的 “JavaEE9”,不再是 “JavaEE9” 这个名字了,而是叫做 JakartaEE9。
JavaEE8 的时候对应的
Servlet
类名是:javax.servlet.Servlet
。JakartaEE9 的时候对应的
Servlet
类名是:jakarta.servlet.Servlet
。
1.8 在IDEA中开发Servlet程序
使用 IDEA 集成开发工具开发 Servlet:
新建一个项目(可以先创建一个空项目,然后在空项目下创建模块),这个项目起名为
javaweb
,一般情况下新建的项目的名字最好和目录的名字一致。在
javaweb
项目下新建模块,起名为servlet01
。让新建的
servlet01
模块变为 JavaEE 的模块(让 Module 变为 webapp 模块,符合 webapp 规范。即让 Module 变为符合 Servlet 规范的 Module):在 Module 上点击右键,添加框架支持。
在弹出的窗口中,选择 Web Application(Web应用程序)。
在选择了这个 Web Application 支持之后,IDEA 会自动给你生成一个符合 Servlet 规范的 webapp 目录结构。
注意:在 IDEA 工具中根据 Web Application 模板生成的目录中有一个 web 目录,这个目录就代表 webapp 的根。
编写 Servlet 程序(
StudentServlet.java
)。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
90package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;
/**
* @author fzy
* @date 2023/10/24 17:55
*/
public class StudentServlet implements Servlet {
public void init(ServletConfig servletConfig) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
//设置相应内容的类型
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//连接数据库(JDBC)
Connection conn = null;
PreparedStatement ps = null;
ResultSet result = null;
try {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接
String url = "jdbc:mysql://localhost:3306/javaweb?useSSL=false";
String user = "root";
String pwd = "******"; //根据实际情况填写
conn = DriverManager.getConnection(url, user, pwd);
//获取预编译的数据库操作对象
String sql = "SELECT number, name FROM student";
ps = conn.prepareStatement(sql);
//执行SQL
result = ps.executeQuery();
//处理结果集
while (result.next()) {
String number = result.getString("number");
String name = result.getString("name");
out.print(number + " - " + name + "<br/>");
}
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException(e);
} finally {
//释放资源
if (result != null) {
try {
result.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}注意:这里要将
jsp-api.jar
、servlet-api.jar
的 jar包添加到 classpath 中(这里的 classpath 指的是 IDEA 的 classpath)。File --> Project Structure --> Modules --> 选中模块 --> 点击 “+” 添加 --> 添加 JAR包
。
注意:在
WEB-INF
目录下新建子目录lib
,将mysql
连接的 jar 包添加到lib
目录中。在
web.xml
文件中注册编写的StudentServlet
程序(请求路径和 Servlet 程序之间对应起来)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>studentServlet</servlet-name>
<servlet-class>com.f.javaweb.servlet.StudentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>studentServlet</servlet-name>
<url-pattern>/servlet/studentlist</url-pattern>
</servlet-mapping>
</web-app>- 此时如果想要请求
StudentServlet
程序的服务,浏览器网址是什么呢?是127.0.0.1:8080/web/servlet/studentlist
或者127.0.0.1:8080/servlet01/servlet/studentlist
吗?- 都不是,即根目录的名字其实还未确定,需要等待后面的配置。
- 此时如果想要请求
编写一个 html 页面,在 HTML 页面中编写一个超链接,用户点击这个超链接,发送请求,然后 Tomcat 执行后台的
StudentServlet
的service
方法。1
2
3
4
5
6
7
8
9
10
11
<html lang="en">
<head>
<meta charset="UTF-8">
<title>student page</title>
</head>
<body>
<!--这里的项目名是 /xmm, 先写死-->
<a href="/xmm/servlet/studentlist">学生列表</a>
</body>
</html>- 注意:这里的
/xmm
就是 webapp 的根目录名,是我们在后面配置的。
- 注意:这里的
让 IDEA 工具去关联 Tomcat 服务器。关联的过程当中将 webapp 部署到 Tomcat 服务器当中。
IDEA 工具右上角有
编辑配置...
选项。点击 “+” 添加 Tomcat服务器-本地。
在弹出的界面中设置服务器 Server 的参数:

在当前窗口中有一个 “部署” 按钮(点击这个用来部署 webapp),继续点击加号,部署即可。
修改
“应用程序上下文”
为/xmm
,就是在这里,我们指定了该 webapp 的根目录的名字。
启动 Tomcat 服务器,建议使用 debug 模式启动 Tomcat。
打开浏览器,在浏览器地址栏中输入
localhost:8080/xmm/student.html
访问 html 页面,并通过点击超链接<a href="/xmm/servlet/studentlist">学生列表</a>
来请求StudentServlet
程序的服务。
1.9 ★★★Servlet对象的生命周期
Servlet 对象从创建到销毁的整个过程的周期。
Servlet 对象是由谁来维护的?
Tomcat 服务器(WEB Server)全权负责。
Servlet 对象的创建,对象方法的调用,对象的销毁,Javaweb 程序员是无权干预的。
思考:我们自己 new 的 Servlet 对象受 WEB容器(即 WEB服务器)的管理吗?
我们自己 new 的 Servlet 对象是不受 WEB容器管理的。
WEB容器创建的 Servlet 对象,会被放到一个集合(HashMap)当中,只有放到这个 HashMap 集合中的 Servlet 对象才能够被 WEB容器管理,自己 new 的 Servlet 对象不会被 WEB容器管理(因为不在容器中)。

在启动服务器的时候,Servlet对象有没有被创建出来?
可以在 Servlet 中提供一个无参数的构造方法,启动服务器的时候看看构造方法是否执行。
经过测试发现,默认情况下,服务器在启动的时候 Servlet对象并不会被实例化。
怎么让服务器启动的时候创建Servlet对象呢?
可以在配置文件
web.xml
的 servlet标签中添加<load-on-startup>
子标签,在该子标签中填写整数,越小的整数优先级越高:1
2
3
4
5
6
7
8
9
10
11
12
13
14<servlet>
<servlet-name>aservlet</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class>
<!--
在服务器启动时创建Servlet对象,在该标签中填写整数
数字表示创建对象的先后顺序
数字越小优先级越高
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>aservlet</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>一般情况下,不会在服务器启动时就创建 Servlet对象。
Servlet对象生命周期:
默认情况下服务器启动的时候 Servlet对象并没有被实例化。
用户发送第一次请求的时候,控制台输出了以下内容:
1
2
3AServlet无参数构造方法执行了
AServlet's init method execute!
AServlet's service method execute!根据以上输出内容得出结论:
- 用户在发送第一次请求的时候 Servlet对象被实例化(AServlet的构造方法被执行了,并且执行的是无参数构造方法)。
- AServlet对象被创建出来之后,Tomcat服务器马上调用了 AServlet对象的
init
方法(init
方法在执行的时候,AServlet对象已经存在了。已经被创建出来了)。 - 用户发送第一次请求的时候,
init
方法执行之后,Tomcat服务器马上调用 AServlet对象的service
方法。
用户继续发送第二次请求,控制台输出了以下内容:
1
AServlet's service method execute!
根据以上输出结果得知,用户在发送第二次,或者第三次,或者第四次请求的时候,Servlet对象并没有新建,还是使用之前创建好的 Servlet对象,直接调用该 Servlet对象的
service
方法,这说明:- 第一:Servlet对象是单例的(单实例的。但是要注意:Servlet对象是单实例的,但是 Servlet类并不符合单例模式。我们称之为假单例。之所以单例是因为 Servlet对象的创建我们 Javaweb程序员管不着,这个对象的创建只能是 Tomcat 来说了算,Tomcat只创建了一个,所以导致了单例,但是属于假单例。对于真单例模式,构造方法是私有化的。)
- 第二:无参数构造方法、
init
方法只在用户第一次发送请求的时候执行。也就是说无参数构造方法只执行一次。init
方法也只被 Tomcat 服务器调用一次。 - 第三:只要用户发送一次请求:
service
方法必然会被 Tomcat 服务器调用一次。发送 100 次请求,service
方法会被调用 100 次。
关闭服务器的时候,控制台输出了以下内容:
1
AServlet's destroy method execute!
通过以上输出内容,可以得出以下结论:
Servlet 的
destroy
方法只被 Tomcat 服务器调用一次。destroy
方法是在什么时候被调用的?- 在服务器关闭的时候。
- 因为服务器关闭的时候要销毁 AServlet对象的内存。
- 服务器在销毁AServlet对象内存之前,Tomcat服务器会自动调用 AServlet对象的
destroy
方法。
destroy
方法执行的时候 AServlet对象还在,没有被销毁,destroy
方法执行结束之后,AServlet对象的内存才会被 Tomcat 释放。
思考
关于 Servlet 类中方法的调用次数?
- 构造方法只执行一次。
init
方法只执行一次。service
方法:用户发送一次请求则执行一次,发送 N 次请求则执行 N 次。destroy
方法只执行一次。
当我们在 Servlet 类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现什么问题?
报错了:500 错误。
注意:500 是一个 HTTP 协议的错误状态码。500 一般情况下是因为服务器端的 Java 程序出现了异常(服务器端的错误都是 500 错误:服务器内部错误)。
如果没有无参数的构造方法,会导致出现 500 错误,无法实例化 Servlet 对象。
所以,一定要注意:在 Servlet 开发当中,不建议程序员手动定义构造方法,因为定义不当,一不小心就会导致无法实例化 Servlet 对象。
Servlet 的无参数构造方法是在对象第一次创建的时候执行,并且只执行一次;
init
方法也是在对象第一次创建的时候执行,并且只执行一次。那么这个无参数构造方法可以代替掉init
方法吗?- 不能。
- Servlet 规范中有要求,作为 Javaweb 程序员,编写 Servlet 类的时候,不建议手动编写构造方法,因为编写构造方法,很容易让无参数构造方法消失,这个操作可能会导致 Servlet 对象无法实例化。所以
init
方法是有存在的必要的。
init
、service
、destroy
方法中使用最多的是哪个方法?使用最多就是
service
方法,service
方法是一定要实现的,因为service
方法是处理用户请求的核心方法。什么时候使用
init
方法呢?init
方法很少用。通常在
init
方法当中做初始化操作,并且这个初始化操作只需要执行一次。例如:初始化数据库连接池,初始化线程池 ……
什么时候使用
destroy
方法呢?destroy
方法也很少用。通常在
destroy
方法当中,进行资源的关闭。对象马上要被销毁了,还有什么没有关闭的,抓紧时间关闭资源,还有什么资源没保存的,抓紧时间保存一下。
1.10 适配器模式Adapter改造Servlet
编写一个 Servlet 类直接实现 Servlet 接口有什么缺点?
- 我们只需要 service 方法,其他方法大部分情况下是不需要使用的。代码很丑陋。
为了解决这个问题,可以使用适配器设计模式 Adapter:
编写一个
GenericServlet
类,这个类是一个抽象类,其中有一个抽象方法service
: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
48package com.f.javaweb.adapter;
import jakarta.servlet.*;
import java.io.IOException;
/**
* @author fzy
* @date 2023/11/11 14:36
*/
/**
* 编写一个标准通用的Servlet,起名:GenericServlet
* 以后所有的Servlet类都不要直接实现Servlet接口了。
* 以后所有的Servlet类都要继承GenericServlet类。
* GenericServlet 就是一个适配器。
*/
public abstract class GenericServlet implements Servlet {
public void init(ServletConfig servletConfig) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
/**
* 抽象方法,这个方法最常用。所以要求子类必须实现service方法。
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
public String getServletInfo() {
return null;
}
public void destroy() {
}
}GenericServlet
实现Servlet
接口。GenericServlet
是一个适配器。以后编写的所有
Servlet
类继承GenericServlet
,重写service
方法即可。不需要每次都要实现 Servlet 接口的所有抽象方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.f.javaweb.adapter;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
/**
* @author fzy
* @date 2023/11/11 14:44
*/
public class StudentService extends GenericServlet {
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
System.out.println("Student service method execute!");
}
}
1.10.1 改造GenericServlet
思考:
GenericServlet
类是否需要改造一下?怎么改造?更利于子类程序的编写?- 第一个问题:创建了一个继承
GenericServlet
的类后,在使用该类时,init
方法还会执行吗?- 还会执行,会执行
GenericServlet
类中的init
方法。子类如果没有重写父类的方法,就会默认执行父类的方法。
- 还会执行,会执行
- 第二个问题:
init
方法是谁调用的?- Tomcat服务器调用的。
- 第三个问题:
init
方法中的ServletConfig
对象是谁创建的?是谁传过来的?- 都是 Tomcat 干的。Tomcat 服务器先创建了
ServletConfig
对象,然后调用init
方法,将ServletConfig
对象传给了init
方法。
- 都是 Tomcat 干的。Tomcat 服务器先创建了
- 第一个问题:创建了一个继承
目前,
init
方法中的ServletConfig
对象是 Tomcat 创建好的,这个ServletConfig
对象在init
方法的参数上,属于局部变量:public void init(ServletConfig servletConfig) throws ServletException
。但是,
ServletConfig
对象以后肯定要在service
方法中使用,怎么才能保证ServletConfig
对象在service
方法中使用呢?- 可以将
GenericServlet
按如下几条进行改造:- 在
GenericServlet
中创建一个私有的ServletConfig
成员变量, 在init
方法中给其赋值,当继承GenericServlet
的子类对象需要使用ServletConfig
时,通过调用getServletConfig
方法即可。 - 为了防止子类重写已经写好的
init
方法(“在init
方法中给其赋值”),所以将该init
方法声明为final
,并提供一个单独的init
方法供子类重写,在该方法中调用允许子类重写的init
方法。
- 在
改造后的
GenericServlet
的代码如下: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
60package com.f.javaweb.adapter;
import jakarta.servlet.*;
import java.io.IOException;
/**
* @author fzy
* @date 2023/11/11 14:36
*/
/**
* 编写一个标准通用的Servlet,起名:GenericServlet
* 以后所有的Servlet类都不要直接实现Servlet接口了。
* 以后所有的Servlet类都要继承GenericServlet类。
* GenericServlet 就是一个适配器。
*/
public abstract class GenericServlet implements Servlet {
//创建一个私有的 `ServletConfig` 成员变量, 在 `init` 方法中给其赋值
private ServletConfig servletConfig;
//防止子类重写已经写好的 `init` 方法
public final void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
this.init(); //在该方法中调用允许子类重写的 `init` 方法
}
//提供一个单独的 `init` 方法供子类重写
public void init(){
}
public ServletConfig getServletConfig() {
return servletConfig;
}
/**
* 抽象方法,这个方法最常用。所以要求子类必须实现service方法。
*
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
public String getServletInfo() {
return null;
}
public void destroy() {
}
}实现
GenericServlet
的子类的代码如下: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
28package com.f.javaweb.adapter;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
/**
* @author fzy
* @date 2023/11/11 14:44
*/
public class StudentService extends GenericServlet {
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
//调用父类的 getServletConfig 方法得到 ServletConfig 对象
ServletConfig servletConfig = this.getServletConfig();
System.out.println("ServletConfig:" + servletConfig);
System.out.println("Student service method execute!");
}
public void init() {
System.out.println("Student service init method execute!");
}
}- 可以将
1.10.2 jakarta.servlet.GenericServlet
1.10.1
节所述是为了说明GenericServlet
的源码,其实在jakarta.servlet.GenericServlet
中已经实现了上述功能,代码如下: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//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package jakarta.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String message) {
ServletContext var10000 = this.getServletContext();
String var10001 = this.getServletName();
var10000.log(var10001 + ": " + message);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}transient
表示修饰的属性不可序列化。
1.11 ServletConfig接口详解
ServletConfig
:ServletConfig
是什么?jakarta.servlet.ServletConfig
,是 Servlet 规范中的一员。ServletConfig
是一个接口(jakarta.servlet.ServletConfig
是一个接口)。
谁去实现了这个接口呢? -> WEB服务器实现了。
打印输出
ServletConfig
对象时,输出结果为org.apache.catalina.core.StandardWrapperFacade implements ServletConfig
结论:Tomcat服务器实现了
ServletConfig
接口。思考:如果把 Tomcat服务器换成 jetty服务器,输出
ServletConfig
对象的时候,还是这个结果吗?不一定一样,包名类名可能和 Tomcat 不一样,但是他们都实现了
ServletConfig
这个规范。
一个
Servlet
对象中有一个ServletConfig
对象(Servlet
和ServletConfig
对象是一对一的)。100 个
Servlet
对象,就应该有 100 个ServletConfig
对象。ServletConfig
对象是谁创建的?在什么时候创建的?- Tomcat 服务器(WEB服务器)创建了
ServletConfig
对象。 - 在创建
Servlet
对象的时候,同时创建了ServletConfig
对象。
- Tomcat 服务器(WEB服务器)创建了
ServletConfig
接口到底是干啥的?有什么用呢?ServletConfig
对象被翻译为:Servlet
对象的 “配置信息对象”。- 一个
Servlet
对象就有一个配置信息对象。两个Servlet
对象就有两个配置信息对象。
ServletConfig
对象中到底包装了什么信息呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<servlet>
<servlet-name>configTest</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
<!--这里是可以配置一个Servlet对象的初始化信息的。-->
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/bjpowernode</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>root1234</param-value>
</init-param>
</servlet>ServletConfig
对象中包装的信息是:web.xml
文件中<servlet></servlet>
标签的配置信息。其中
<init-param></init-param>
是初始化参数。Tomcat 解析
web.xml
文件,将web.xml
文件中<servlet></servlet>
标签中的配置信息自动包装到ServletConfig
对象中。
ServletConfig
接口中有哪些方法?-> 共有 4 个方法:public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
通过
ServletConfig
对象的上面两个方法,可以获取到web.xml
文件中Servlet
对象的初始化参数配置<init-param></init-param>
的信息。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
41package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
/**
* @author fzy
* @date 2023/11/22 14:43
*/
public class TestServletConfig extends GenericServlet {
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 获取ServletConfig对象
ServletConfig config = this.getServletConfig();
// 输出该对象
//ServletConfig: org.apache.catalina.core.StandardWrapperFacade@607693fa
out.print("ServletConfig: " + config);
out.print("<br/>");
// 获取<servlet-name></servlet-name>
String servletName = config.getServletName();
out.print("<servlet-name>" + servletName + "</servlet-name>");
out.print("<br/>");
// java.util.Enumeration<java.lang.String> getInitParameterNames() 获取所有的初始化参数的name
Enumeration<String> initParameterNames = config.getInitParameterNames();
// 遍历集合
while (initParameterNames.hasMoreElements()) { // 是否有更多元素
String parameterName = initParameterNames.nextElement(); // 取元素
String parameterVal = config.getInitParameter(parameterName); // 通过name获取value
out.print(parameterName + "=" + parameterVal);
out.print("<br/>");
}
}
}public ServletContext getServletContext();
public String getServletName();
注意:其实
ServletConfig
接口的四个方法不需要专门通过ServletConfig
对象去调用,在自己的Servlet
对象中可以通过this
去调用(因为自己的Servlet
继承了GenericServlet
,而GenericServlet
实现了ServletConfig
接口。可以看1.10.2
中GenericServlet
的源码,里面已经实现了这四个方法)。所以上面的代码可以改为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
34package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
/**
* @author fzy
* @date 2023/11/22 14:43
*/
public class TestServletConfig extends GenericServlet {
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 获取<servlet-name></servlet-name>
String servletName = this.getServletName();
out.print("<servlet-name>" + servletName + "</servlet-name>");
out.print("<br/>");
// java.util.Enumeration<java.lang.String> getInitParameterNames() 获取所有的初始化参数的name
Enumeration<String> initParameterNames = this.getInitParameterNames();
// 遍历集合
while (initParameterNames.hasMoreElements()) { // 是否有更多元素
String parameterName = initParameterNames.nextElement(); // 取元素
String parameterVal = this.getInitParameter(parameterName); // 通过name获取value
out.print(parameterName + "=" + parameterVal);
out.print("<br/>");
}
}
}即,以上四个方法在
Servlet
类当中,都可以使用this
去调用。因为GenericServlet
实现了ServletConfig
接口。
1.12 ★ServletContext接口详解
ServletContext
:ServletContext
是什么?jakarta.servlet.ServletContext
,是 Servlet 规范中的一员。ServletContext
是一个接口(jakarta.servlet.ServletContext
是一个接口)。
谁去实现了这个接口呢? -> WEB服务器实现了。
public class org.apache.catalina.core.ApplicationContextFacade implements ServletContext {}
怎么获取
ServletContext
对象呢?通过
ServletConfig
对象获取ServletContext
对象。通过
this
也可以获取ServletContext
对象。
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
29package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author fzy
* @date 2023/11/22 16:50
*/
public class TestServletContext extends GenericServlet {
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 1.通过 `ServletConfig` 对象获取 `ServletContext` 对象。
ServletConfig config = this.getServletConfig();
ServletContext application1 = config.getServletContext();
out.print("ServletContext: " + application1);
out.print("<br/>");
// 2.通过 `this` 也可以获取 `ServletContext` 对象。
ServletContext application2 = this.getServletContext();
out.print("ServletContext: " + application2);
out.print("<br/>");
}
}ServletContext
对象是谁创建的?在什么时候创建的?Tomcat 服务器(WEB服务器)创建了
ServletContext
对象。在 WEB服务器启动的时候创建了
ServletContext
对象。在 WEB服务器关闭的时候销毁ServletContext
对象。
对于一个 webapp 来说,
ServletContext
对象只有一个。一个
<web-app></web-app>
标签对应一个ServletContext
对象。ServletContext
接口到底是干啥的?有什么用呢?ServletContext
对象被翻译为:Servlet
对象的 “环境对象”(Servlet
对象的“上下文对象”)。ServletContext
对象对应的其实就是整个web.xml
文件。
举个例子:50个学生,每个学生都是一个
Servlet
对象,这 50 个学生都在同一个教室当中,那么这个教室就相当于ServletContext
对象。- 放在
ServletContext
对象中的数据,一定是所有Servlet
对象共享的。只要在同一个webapp当中,只要在同一个应用当中,所有的Servlet
对象都是共享同一个ServletContext
对象的。比如:一个教室中的空调是所有学生共享的,一个教室中的语文老师是所有学生共享的。 - Tomcat 是一个容器,一个容器当中可以放多个 webapp,一个 webapp 对应一个
ServletContext
对象。
ServletContext
接口中有哪些常用的方法?public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
通过
ServletContext
对象的上面两个方法,可以获取到web.xml
文件中的初始化参数配置<context-param></context-param>
的信息(注意和ServletConfig
中的方法的区别)。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
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!--上下文的初始化参数,以下的这些配置信息,可以通过ServletContext对象来获取-->
<context-param>
<param-name>pageSize</param-name>
<param-value>30</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<servlet>
<servlet-name>testServletContext</servlet-name>
<servlet-class>com.f.javaweb.servlet.TestServletContext</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testServletContext</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>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
33package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
/**
* @author fzy
* @date 2023/11/22 21:04
*/
public class TestServletContext2 extends GenericServlet {
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext application = this.getServletContext();
// 获取上下文的初始化参数
Enumeration<String> initParameterNames = application.getInitParameterNames();
/**
* 浏览器显示:
* startIndex = 0
* pageSize = 30
*/
while (initParameterNames.hasMoreElements()) {
String paramName = initParameterNames.nextElement();
String paramVal = application.getInitParameter(paramName);
out.print(paramName + " = " + paramVal + "<br/>");
}
}
}public String getContextPath();
获取应用的根路径。public String getRealPath(String path);
获取文件的绝对路径(真实路径)。public void log(String message);
记录日志信息。public void log(String message, Throwable t);
除了记录日志信息外,还记录异常信息,控制台不会发送异常,只是记录异常信息。
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
54package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author fzy
* @date 2023/11/22 21:25
*/
public class TestServletContext3 extends GenericServlet {
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext application = this.getServletContext();
/**
* 1.`public String getContextPath();` 获取应用的根路径。
* 因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径。
* 在java源码当中,不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候,会起一个什么名字。
*/
String contextPath = application.getContextPath();
out.print(contextPath + "<br/>"); // "/context"
/**
* 2.`public String getRealPath(String path);` 获取文件的绝对路径(真实路径).
* 传入的参数中,加了一个“/”,这个“/”代表的是web的根
* String realPath = application.getRealPath("/index.html"); // 可以
* 你不加“/”,默认也是从根下开始找。
* String realPath = application.getRealPath("index.html"); // 不加“/”也可以
* out.print(realPath + "<br/>");
*/
String realPath = application.getRealPath("/index.html");
out.print(realPath + "<br/>"); // C:\Users\Running Noob\Code Project\javaweb\out\artifacts\servlet04_war_exploded\index.html
/**
* 3.`public void log(String message);` 记录日志信息。
* 这些日志信息记录到哪里了? -> %CATALINA_HOME%logs 目录下
* // 注意:IDEA工具的Tomcat服务器是根据下载的Tomcat服务器生成的副本
* // Tomcat服务器的logs目录下都有哪些日志文件?
* //catalina.2021-11-05.log 服务器端的java程序运行的控制台信息。
* //localhost.2021-11-05.log ServletContext对象的log方法记录的日志信息存储到这个文件中。
* //localhost_access_log.2021-11-05.txt 访问日志。
*/
application.log("hello,world!");
/**
* 4.`public void log(String message, Throwable t);`
* 除了记录日志信息外,还记录异常信息,控制台不会发送异常,只是记录异常信息。
*/
int age = 1;
if (age < 18) {
application.log("年龄未满18岁", new RuntimeException("age < 18"));
}
}
}public void setAttribute(String name, Object value);
向 ServletContext 应用域中存数据。public Object getAttribute(String name);
从 ServletContext 应用域中取数据。public void removeAttribute(String name);
删除 ServletContext 应用域中的数据。
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
32package com.f.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author fzy
* @date 2023/11/23 10:50
*/
public class TestServletContext4 extends GenericServlet {
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext application = this.getServletContext();
/**
* 1.`public void setAttribute(String name, Object value);` 向 ServletContext 应用域中存数据。
*/
application.setAttribute("name", "jack");
/**
* 2.`public Object getAttribute(String name);` 从 ServletContext 应用域中取数据。
*/
Object name = application.getAttribute("name");
out.print(name);
/**
* 3.`public void removeAttribute(String name);` 删除 ServletContext 应用域中的数据。
*/
application.removeAttribute("name");
}
}注意:上面存取的数据,是所有
Servlet
对象共享的,即,可以在AServlet
对象中setAttribute
,然后在BServlet
对象中getAttribute
。ServletContext
对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)。如果所有用户共享一份数据,并且这个数据很少被修改,并且这个数据量很少,那么就可以将这些数据放到
ServletContext
这个应用域中。为什么是所有用户共享的数据?
-> 不是共享的没有意义,因为
ServletContext
这个对象只有一个,只有共享的数据放进去才有意义。为什么这些共享数据很少被修改,或者说几乎不修改?
-> 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在
ServletContext
对象中的数据一般都是只读的。为什么数据量要小?
-> 因为数据量比较大的话,太占用堆内存,并且
ServletContext
对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁,大数据量会影响服务器的性能,因此占用内存较小的数据量才可以考虑放进去。
数据量小、所有用户共享、又不修改,这样的数据放到
ServletContext
这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次再用的时候,不需要从数据库中再次获取,大大提升了执行效率。
思考
什么时候使用
<context-param></context-param>
来配置信息,什么时候使用<init-param></init-param>
来配置信息?<context-param></context-param>
中的信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到该标签当中。通过
ServletContext
对象的下面两个方法来获取<context-param></context-param>
中的信息:public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
<init-param></init-param>
中的信息仅适用于当前所在的Servlet
对象,如果你的配置信息只是想给某一个Servlet
对象使用,那么配置到 servlet 标签当中即可。通过
this
或者ServletConfig
对象的下面两个方法来获取<init-param></init-param>
中的信息:public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
注意
以后我们编写
Servlet
类的时候,实际上是不会去直接继承GenericServlet
类的,因为我们是 B/S 结构的系统,这种系统是基于 HTTP 超文本传输协议的。在 Servlet 规范当中,提供了一个类叫做
HttpServlet
,它是专门为 HTTP 协议准备的一个Servlet
类,我们编写的Servlet
类要继承HttpServlet
(HttpServlet
是 HTTP 协议专用的),使用HttpServlet
处理 HTTP 协议更便捷,但是你需要知道它的继承结构:1
2
3
4
5jakarta.servlet.Servlet(接口)【爷爷】
jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
我们以后编写的Servlet要继承HttpServlet类。
另:缓存机制
- 大家到目前为止都接触过哪些缓存机制了?
- 堆内存当中的字符串常量池。
- 如果要使用字符串 “abc”,会先在字符串常量池中查找,如果有,直接拿来用。如果没有则新建,然后再放入字符串常量池。
- 堆内存当中的整数型常量池。
- [-128 ~ 127] 一共 256 个 Integer 类型的引用,放在整数型常量池中。没有超出这个范围的话,可以直接从常量池中取。
- 连接池(Connection Cache)
- 这里所说的连接池中的连接是 java 语言连接数据库的连接对象:
java.sql.Connection
对象。 - JVM 是一个进程,MySQL 数据库是一个进程,进程和进程之间建立连接,打开通道是很费劲的,是很耗费资源的。怎么办?可以提前先创建好 N 个
Connection
连接对象,将连接对象放到一个集合当中,我们把这个放有Connection
对象的集合称为连接池。每一次用户连接的时候不需要再新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率。 - 连接池
- 最小连接数
- 最大连接数
- 连接池可以提高用户的访问效率。当然也可以保证数据库的安全性。
- 这里所说的连接池中的连接是 java 语言连接数据库的连接对象:
- 线程池
- Tomcat 服务器本身就是支持多线程的。
- Tomcat 服务器是在用户发送一次请求,就新建一个Thread线程对象吗?
- 当然不是,实际上是在 Tomcat 服务器启动的时候,会先创建好 N 个线程
Thread
对象,然后将线程对象放到集合当中,称为线程池。用户发送请求过来之后,需要有一个对应的线程来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率比较高。 - 所有的 WEB 服务器,或者应用服务器,都是支持多线程的,都有线程池机制。
- 当然不是,实际上是在 Tomcat 服务器启动的时候,会先创建好 N 个线程
- redis
- NoSQL数据库。非关系型数据库。缓存数据库。
- 向 ServletContext 应用域中存储数据,也等于是将数据存放到缓存 cache 当中了。
- 堆内存当中的字符串常量池。