JavaWeb - Note02 Servlet & GenericServlet
Servlet
Servlet(Server Applet ),服务器端的 Java 小程序,JavaWeb 开发最核心的规范
合法的 webapp 目录结构
- WEB-INF
- classes
- lib
- web.xml
- html
- css
- javascript
- image
- …
第一个 Servlet webapp
步骤:
在 webapps 目录下新建
项目目录
vv:不能包含子目录
项目目录下新建一个
WEB-INF
目录Servlet 规范,目录内的资源受保护,在浏览器上不能通过路径直接访问
像 HTML、CSS、JS、image 等静态资源需放在 WEB-INF 目录之外
WEB-INF 目录下新建一个
classes
目录Servlet 规范,存放 Java 程序编译后的 class 文件
WEB-INF 目录下新建一个
lib
目录非必须,存放第三方 jar 包
WEB-INF 目录下新建一个
web.xml
文件描述请求路径与 Servlet 类之间对照关系
建议直接复制
1
2
3
4
5
6
7
8
9
10
11<?xml version="1.0" encoding="UTF-8"?>
<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_5_0.xsd"
version="5.0"
metadata-complete="true">
</web-app>编写一个 Java 程序
必须实现 Servlet 接口,该接口不在 JDK 中,属于 JavaEE 中的类库
Tomcat 实现了 Servlet 规范,所以拥有该接口
关于 JavaEE 版本:
JavaEE 目前最高版本是 JavaEE 8,被 Oracle 捐献给了 Apache,升级后更名为 JakartaEE 9
JavaEE 8 对应的 Servlet 类名为:javax.servlet.Servlet
JakartaEE 9 以后对应的 Servlet 类名为:jakarta.servlet.Servler
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
46package io.github.wtaaaame.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("Hello Servlet");
// 设置流对象响应类型(普通文本或 html 代码)
// 在流对象之前设置有效
response.setContentType("text/html");
// 如果出现中文乱码,可以加上:
// response.setContentType("text/html; charset=UTF-8");
// 创建流对象,将信息直接输出到浏览器
// 使用 ServletResponse 接口:response
// 输出流,负责输出字符串到浏览器,不需要刷新和关闭,由 Tomcat 管理
PrintWriter out = response.getWriter();
out.println("Response Servlet");
// 未设置响应类型,无格式输出,需在开启流对象之前设置
out.println("<h1>This is a h1,测试乱码问题</h1>");
}
public void destroy() {}
public String getServletInfo() {
return "";
}
public ServletConfig getServletConfig() {
return null;
}
}编译
配置环境变量:
CLASSPATH=.;[servlet-aip].jar
将编译后的包放到
tomcat/webapps/[project]/WEB-INF/classes
中注册 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
29
30
31<?xml version="1.0" encoding="UTF-8"?>
<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_5_0.xsd"
version="5.0"
metadata-complete="true">
<!-- Servlet 描述信息 -->
<!-- 任何一个 servlet 都对应一个 servlet-mappint -->
<servlet>
<servlet-name>vv</servlet-name>
<!-- 这个位置必须是带有包名的全限定类名-->
<servlet-class>io.github.wtaaaame.servlet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet 映射信息 -->
<servlet-mapping>
<!-- 这里随便,但需要与上面的 name 相同 -->
<servlet-name>vv</servlet-name>
<!--
这里需要一个路径
唯一的要求就是以 / 开始
当前随便写
-->
<url-pattern>/vv/vv</url-pattern>
</servlet-mapping>
</web-app>启动 Tomcat 服务器
打开浏览器
地址栏输入 URL:
http://localhost:8080/[project_name][url-pattern]
浏览器路径与 url-pattern 路径唯一区别是:浏览器路径带 /[project_name]
超链接
若浏览器上编写的路径太复杂,可以使用超链接(html 页面只能放到 WEB-INF 目录上一层)
1 |
|
访问时只需访问该 html 页面(http://localhost:8080/crm/index.html
),通过页面中的超链接跳转
Servlet 连接数据库
Servlet 是 Java 程序,所以在 Servlet 中可直接编写 JDBC 代码连接数据库
Servlet 连接数据库需新写一个 class,在 service() 方法中编写程序,并将数据库驱动 jar 包放到 webapp 中的 lib 下
在 IDEA 中开发
让 Module 变成 JavaEE 模块(符合 Servlet 规范的 Module):
模块右键 -> Add Frameworks Support -> Java EE - Web Application
- 生成的 web 目录,就是 webapp 的根
配置 IEAD 的 classpath:
将
CATALINA_HOME\lib
下的servlet-api.jar
添加到Libraries
中(IDEA -> File -> Project Structure -> Project Settings - Libraries
)- 也可以在
Project Settings - Modules - [Module] - Dependencies
中点击+
号单独添加 jar 包
- 也可以在
IEAD 工具关联 Tomcat 服务器
关联的过程中将 webapp 部署到 Tomcat 服务器
- IDEA 工具右上角,Build 按钮右边,点击
Add Configuration...
- 点击左上角
+
号,找到Tomcat Server
,选择local
- 配置 Tomcat 路径、服务器、JRE 等
- 当前窗口点击
Deployment
部署 webapp- 点击
+
配置Artifact
- 修改
Application context
,重命名为项目名
- 点击
- 建议以 Debug 模式启动服务器
- IDEA 工具右上角,Build 按钮右边,点击
Servlet 对象生命周期
Servlet 对象的创建、方法调用、销毁,JavaWeb 程序员无权干预
Servlet 对象的生命周期由 Tomcat 服务器(WEB Server)全权负责
Tomcat 服务器通常又称为:WEB 容器(WEB Container)
自己 new 的 Servlet 对象不受 Tomcat 服务器管理,WEB 服务器创建的 Servlet 对象都会被放到一个 HashMap 中,只有放在这个 HashMap 集合中的 Servlet 才能够被 Tomcat 管理
实例化时机
在 Servlet 中提供一个无参数的构造方法,启动服务器时看看构造方法是否执行?
默认情况下,服务器在启动时 Servlet 对象并不会被实例化
用户没有发送请求之前,如果提前创建出所有 Servlet 对象,必然要耗费大量内存
若想在服务器启动时创建对象,只需在
web.xml
中的servlet
标签中加上:1
<load-on-startup>0</load-on-startup>
- 中间整数决定创建顺序(从小到大)
生命周期
- 默认用户发送第一次请求时,实例化 Servlet 对象
- 对象被创建出来后,Tomcat 服务器马上调用对象 init 方法
- 以后每次发起请求都只调用 service 方法,不新建对象
- 关闭服务器时调用 destroy 方法
说明:
Servlet 对象是单实例的(假单例,并不符合单例模式)
程序员无法管理 Servlet 对象,由于 Tomcat 只创建了一个,所以导致了单例,但是属于假单例,真单例构造方法是私有化的
无参数构造方法、init 方法只在第一次用户发送请求时执行
只要用户发送一次请求,service 方法必然被调用一次
Servlet 的 destroy 方法只被 Tomcat 服务器调用一次
destroy 方法属于实例方法,方法结束后才会释放
5个对象方法
1 |
|
当 Servlet 类中编写有参数构造方法,如果没有手动编写无参数构造方法会出现
500 错误
(HTTP 协议的错误状态码,500一般属于服务器的 Java 程序出现了异常,服务器内部错误)Servlet 开发中不建议定义构造方法,容易导致无参构造方法消失,可能无法实例化对象,所以 init 方法有存在的必要
GenericServlet
注意:以后编写 Servlet 类,不会直接继承 GenericServlet,因为 B/S 架构系统基于 HTTP 超文本传输协议,在 Servlet 规范中提供了一个类叫做
HttpServlet
,它是专门为 HTTP 协议准备的一个 Servlet 类(HTTP 协议专用)jakarta.servlet.Servlet(接口)
jakarta.servlet.GenericServlet implement Servlet(接口的抽象类)
jakarta.servlet.http.HttpServlet implement GenericServlet(抽象类的抽象类)
Servlet 中需要实现 Servlet 接口所有方法,包含可能用不上的方法,所以不够美观
可以利用适配器设计模式(Adapter),编写一个 GenericServlet 抽象类,类中实现 Servlet 接口所有方法,并把想要重写的方法定义为抽象方法,用以继承实现
- 子类依旧会调用父类 init 方法
ServletConfig
ServletConfig 是一个接口,Tomcat 先创建了 ServletConfig 对象,然后调用 init 方法,将 ServletConfig 对象传给 init 方法
若 service 中想要使用 ServletConfig 对象,可以在 Servlet 类中创建一个私有的成员变量,然后在 init 方法中初始化赋值
若 GenericServlet 中重写的 init 方法不想让子类覆盖,可以使用 final 关键字,然后重载一个无参数的 init 方法调用,并让子类重写;与此同时,通过 getServletConfig 对象返回 config
示例代码如下:
1 |
|
GenericServlet 已有现成抽象类,可以直接继承
扩展:关联源码
IDEA 可以反编译 class 文件,可以在顶部提示框点击 Choose Source
选择源码文件,以展示注释信息
ServletConfig 详解
ServletConfig 是 Servlet 对象的配置信息对象
Tomcat 解析 web.xml 文件,包装了文件中 <servlet></servlet>
标签的配置信息
Servlet 规范中的一员,是一个接口,由以下类实现
1 |
|
- 若换作其他服务器,则包名、类名会不同,但都实现了 ServletConfig 接口
一个 Servlet 对象中有一个 ServletConfig 对象(一对一)
在 Servlet 对象创建的时候,同时创建了 ServletConfig 对象,然后传参到 init 方法
方法
1 |
|
通过 ServletConfig 对象的前两个方法,可以获取到 web.xml 中的初始化参数配置信息
Enumeration<java.lang.String> getInitParameterNames()
获取所有初始化参数的 name
hasMoreElements() 判断是否有下一个元素
nextElement() 获取下一个元素
1
2
3
4
5
6
7// 获取所有初始化参数名称
Enumeration<String> parameterNames = getServletConfig().getInitParameterNames();
// 遍历集合
while (parameterNames.hasMoreElements()) { // 是否有更多元素
// 取出元素并打印
out.print(parameterNames.nextElement() + "<br>");
}String getInitParameter(String name)
获取 name 对应的 value
1
2// 获取 name 对应的 value
out.print(getServletConfig().getInitParameter("driver"));ServletContext getServletContext()
获取 ServletContext 对象
1
2out.print(getServletContext() + "<br>");
// org.apache.catalina.core.ApplicationContextFacadeString getServletName()
获取 servlet 配置中的 servlet-name
1
out.print(getServletConfig().getServletName()); // config
以上方法可以通过 ServletConfig 调用,也可通过继承 GenericSerlvet 直接调用
ServletContext 详解
Servlet 的环境(上下文)对象,是一个接口,是 Servlet 规范中的一员,包含 web.xml 中所有配置信息(属于公用配置)
- Tomcat 是一个容器,可以存放多个 webapp,一个 webapp 对应一个 ServletContext 对象
由 Tomcat 服务器实现:
1 |
|
在 WEB 服务器启动时创建,关闭时结束,属于应用级对象(全局配置)
全局配置信息使用 ServletContext 的
<context-param>
局部 servlet 配置信息使用 ServletConfig 的
<init-param>
ServletContext 对象还有另一个名字:应用域(以后还有请求域、会话域)
如果所有用户共享一份数据(对象只有一个,不共享没有意义),并且这个数据很少被修改(共享数据修改涉及线程安全问题,所以这里数据一般只读)、数据量很少(过大占用太多堆内存,且这个数据生命周期长,会影响服务器性能),则可以考虑放到 ServletContext 这个应用域中
重要方法
1 |
|
String getInitParameter(String name)
通过初始化参数的 name 获取 value
Enumeration<java.lang.String> getInitParameterNames()
获取所有的初始化参数的 name
hasMoreElements() 判断是否有下一个元素
nextElement() 获取下一个元素
1
2
3
4
5
6
7// 获取 ServletContext 上下文的初始化参数
Enumeration<String> initParameterNames = application.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String name = initParameterNames.nextElement();
String value = application.getInitParameter(name);
out.print(name + " " + value + "<br>");
}vv:一定需要调用 ServletContext 的1、2方法,否则调用的是 ServletConfig 中的 init-parameter
String getContextPath()
获取应用地址的根路径
1
out.print(application.getContextPath() + "<br>"); // /context
String getRealPath(String path)
获取 webapp 文件真实路径
1
2
3// 获取文件真实路径
out.print(application.getRealPath("/html.html") + "<br>");
// C:\vv\code\Java\JavaWeb\out\artifacts\servlet03_war_exploded\html.html路径为 webapp 的根路径为起点
void log(String msg)
记录日志,记录到
CATALINA_HOME\ligs
下1
2
3
4// 输出日志文件
application.log("It's VV");
// 29-Jul-2022 12:11:32.764 信息 [http-nio-8080-exec-7]
// org.apache.catalina.core.ApplicationContext.log It's VVvoid log(String message, Throwable throwable)
1
2
3
4
5
6
7
8// 输出日志文件(Throwable)
int age = 12;
if (age < 18) {
application.log("未成年", new RuntimeException("FBI WARNING"));
}
// 29-Jul-2022 12:11:32.764 严重 [http-nio-8080-exec-7]
// org.apache.catalina.core.ApplicationContext.log 未成年
// java.lang.RuntimeException: FBI WARNING使用原始方法肯定会直接操作
CATALINA_HOME
,而 IDEA 中只会生成到 IDEA 指定的目录,须在命令台找到存放路径Tomcat 日志文件:
- localhost_access_log:服务器访问日志
- catalina:服务器启动日志
- localhost:方法记录的日志
应用域方法
1 |
|
void setAttribute(String name, object value)
存数据到 ServletContext 应用域
1
2
3// 存
application.setAttribute("user1", u1);
application.setAttribute("user2", u2);Object getAttribute(String name)
从 ServletContext 应用域取数据
1
2// 取
out.print(application.getAttribute("user1") + "<br>"); // User{name='vv', age=22}void removeAttribute(String name)
从 ServletContext 应用域删数据
1
2// 删
application.removeAttribute("user1");
扩展:缓存机制
堆内存中的字符串缓存机制
“abc” 先在字符串常量池中查找,如果有,直接拿来用;如果没有则新建,然后放入字符串常量池
堆内存中的整数型常量池
[-128, 127] 一共256个 Integer 类型的引用,放在整数型常量池中,没有超出范围则直接从常量池中取
连接池(Connection Cache)
这里说的连接池中的连接是 Java 语言连接数据库的连接对象:java.sql.Connection 对象
JVM 与 MySQL 皆是进程,进程之间建立连接,打开通道很费资源
可以提前创建好 n 个 Connection 连接对象,将连接对象放到一个集合中,我们把这个放有 Connection 对象的集合称为连接池。这样,每次用户连接不需要新建连接对象,直接从连接池中获取连接对象,大大提升访问效率
连接池可以提高用户的访问效率,也可以保证数据库的安全性,实际开发中需考虑:
- 最小连接数
- 最大连接数
线程池
Tomcat 服务器本身支持多线程
Tomcat 服务器启动时,会先创建好 n 多个线程对象,然后将线程对象放到集合中,称为线程池
用户发送请求后,需要有对应的线程来处理请求,此时线程对象就会直接从线程池中获取,效率较高
- 所有的 WEB 服务器或应用服务器都有线程池机制
Redis
NoSQL 数据库,非关系型数据库、缓存数据库
向 ServletContext 应用域中存储数据,也等于是将数据存放到缓存 cache 中了