17、JavaWeb
1. JavaWeb
JavaWeb 是一种使用 Java 技术开发的 Web 应用程序,包括服务器端和客户端的 Java 应用。它使用 JSP 或 JSF 等技术来创建动态、交互式的 Web 页面,可以用于开发企业级应用、电子商务平台、社交网络、在线服务等各种 Web 应用。
Java 在客户端的应用有 Java Applet,不过使用得很少;Java 在服务器端的应用非常的丰富,比如 Servlet,JSP、第三方框架等等。
2. Web 应用程序
Web 应用程序是一种可以通过 Web 浏览器访问的应用程序。
2.1 静态 Web
当不同的用户或在不同的时间访问静态 Web 网页时,其内容或外观不会改变。
缺点:
- Web 页面无法动态更新
- 无法与数据库交互
2.2 动态 Web
动态 Web 的网页文件中除了 HTML 标记以外,还包括一些特定功能的程序代码,这些代码可以使得浏览器和服务器可以交互,服务器端根据客户的不同请求从而动态生成网页内容。
3. 技术名词
架构:B/S 架构、C/S 架构
Web 服务器:IIS 服务器、Tomcat 服务器…
4. Tomcat
- 默认端口号:8080
- 默认 Web 网站存放位置:
webapps
文件夹
5. HTTP
HTTP 默认端口:80
HTTPS 默认端口:443
HTTP 大致发展历程:
flowchart LR HTTP/1.0 --> HTTP/1.1 --> HTTP/2.0
5.1 HTTP 请求
HTTP 请求方法主要有以下几种:GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE 和 CONNECT。这些方法用于不同的网络通信场景,实现了数据的获取、提交、更新和删除等功能。
GET 请求与 POST 请求区别:
- 功能:
- GET 用于从服务器获取数据。它通过 URL 传递数据,并且数据在 URL 参数中可见。
- POST 用于向服务器提交数据。它将数据放在 HTTP 请求的正文中,不会在 URL 中显示。
- 数据大小:
- GET 请求的数据大小有限制(通常限制在 1024 字节左右),因为它将数据作为 URL 参数传递。
- POST 请求的数据大小通常没有限制,但是由服务器定义。
5.2 HTTP 响应
HTTP 响应状态码用来表明特定 HTTP 请求是否成功完成。 响应被归为以下五大类:
- 信息响应 (
100
–199
) - 成功响应 (
200
–299
)- 200 (请求成功)
- 重定向消息 (
300
–399
) - 客户端错误响应 (
400
–499
)- 404 (服务器找不到请求的资源)
- 服务端错误响应 (
500
–599
)
6. Maven
Maven 是一款专门为 Java 项目提供构建和依赖管理支持的工具,可以实现自动化构建、jar 包下载、依赖传递和冲突管理等功能。
基于 Maven 的 JavaWeb 项目结构
1 | project-name |
project-name
是项目名pom.xml
是项目描述文件src/main/java
是存放 Java 源码的目录src/main/resources
是存放资源文件的目录src/main/webapp
文件夹是 JavaWeb 项目的主要文件夹,用于存放应用程序。将这个文件夹放入 Tomcat 的 webapps 文件夹中,就能在 Tomcat 启动后访问到对应的 web 项目src/main/webapp/WEB-INF
下的web.xml
文件是 JavaWeb 项目的配置文件src/main/webapp
下的index.jsp
文件是 Web 应用的默认页面,后续的网页资源文件都在这里src/test/java
是存放测试源码的目录src/test/resources
是存放测试资源的目录- 所有编译、打包生成的文件都放在
target
目录里。
注意!应该将 web.xml
文件中的头文件替换为新的版本(4.0),代码如下:
1 |
|
推荐操作
构建一个普通的 Maven 项目,删掉里面的 src 目录,后续就在这个项目里面新建 JavaWeb 项目的 Module。这个空的项目工程就是 Maven 主工程,内部的 Module 就是其子工程。
6.1 配置 Maven
-
配置环境变量
-
配置镜像
1
2
3
4
5
6<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror> -
配置本地仓库
1
2
3
4
5
6
7
8<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<!-- 重新设置 Maven 本地仓库地址 -->
<localRepository>D:\Maven_Repository</localRepository> -
在 IDEA 中配置 Maven
6.2 pom.xml
文件
pom.xml
文件是 Maven 项目的核心配置文件。
示例:
1 |
|
Maven 中央仓库:Maven Repository
7. Servlet
Servlet 是运行在 Web 服务器或应用服务器上的 Java 程序,它可以处理来自浏览器或其他 HTTP 客户端的请求,并生成动态网页。
实现一个简单的 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
24package pers.afelixliu.javaweb.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello Servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello Servlet</h1>");
out.println("</body>");
out.println("</html>");
}
} -
实现 Servlet 的注册和映射(在
src/main/webapp/WEB-INF/web.xml
中实现)1
2
3
4
5
6
7
8
9
10
11<!-- Servlet注册 -->
<servlet>
<servlet-name>hello_servlet</servlet-name>
<servlet-class>pers.afelixliu.javaweb.servlet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet映射 -->
<servlet-mapping>
<servlet-name>hello_servlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
7.1 Servlet 层次结构
classDiagram direction TB class Servlet { <<interface>> noOfVertices void service(...) ...(...) } class GenericServlet { <<abstract>> ... abstract void service(...) ...(...) } class HttpServlet { <<abstract>> ... void doGet(...) void doPost(...) void service(...) ...(...) } class CustomServlet { void doGet(...) void doPost(...) ...(...) } Servlet <|.. GenericServlet GenericServlet <|-- HttpServlet HttpServlet <|-- CustomServlet
7.2 补充:Servlet 容器、Web 容器
Servlet 容器就是用来管理和加载 Servlet 类的,根据 HTTP 请求找到对应的 Servlet 类,这就是 Servlet 容器要做的事情。
Web 容器其实就是 HTTP 服务器 + Servlet 容器,因为单单 Servlet 容器没有解析 HTTP 请求、通信等相关功能。所以把 Tomcat、Jetty 等实现包含了 HTTP 服务器和 Servlet 容器的功能,称之为 Web 容器。
参考来源:[为什么要有 Servlet ,什么是 Servlet 容器,什么是 Web 容器?](https://www.cnblogs.com/yescode/p/14099868.html#:~:text=其实指代的是实现 S)
7.3 Servlet 生命周期
Servlet 的生命周期共分为 4 大过程:
-
实例化 —— 先创建 Servlet 实例
启动 Servlet 容器,它会查找并加载部署的 Servlet 类。一旦Servlet类加载完成,Servlet 容器会创建 Servlet 的实例,这是通过调用Servlet的无参数构造函数来完成的。
注意:每个 Servlet 只会有一个实例,所有请求都共享这个实例。
-
初始化 ——
init()
在 Servlet 实例创建后,容器会调用 Servlet 的
init()
方法初始化 Servlet 的信息,init()
方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。-
init()
方法是初始化阶段的入口点,可以在这里进行一些初始化工作,如读取配置文件、建立数据库连接等。 -
init()
方法只会在 Servlet 对象创建后被调用一次,在 Servlet 实例创建后立即执行,在后续每次用户请求时不再调用。
-
-
处理请求 ——
service()
初始化完成后调取
service()
方法,由service()
判断客户端的请求方式:- 如果是 get 请求,则执行
doGet()
方法 - 如果是 post 请求,则执行
doPost()
方法 - 处理方法完成后会作出相应的结果返回给客户端,单次请求处理完毕。
当用户发送第二次以后的请求时,会判断对象是否存在,但是不再执行
init()
,而直接执行service()
方法调取doGet()
/doPost()
方法。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。
- 如果是 get 请求,则执行
-
服务终止 ——
destroy()
在长时间没有被调用或者是服务器关闭时,会调用
destroy()
方法来销毁 Servlet 对象。destroy()
方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy()
方法可以让 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。- 在调用
destroy()
方法之后,Servlet 对象被标记为垃圾回收。
每个 Servlet 在容器中是单实例的,所有请求都共享这个实例。因此,Servlet 的方法需要是线程安全的。
7.4 ServletContext
ServletContext 是 Servlet 容器提供的一个对象,用于在整个 Web 应用程序中共享信息和资源。它是一个接口,位于 javax.servlet
包中,定义了一组方法,允许开发人员与 Web 应用程序的上下文进行交互。每个 Web 应用程序都有一个对应的 ServletContext 对象,并且是唯一的,整个 Web 应用程序都可以访问它。
具体来说,ServletContext 允许开发人员在 Web 应用程序范围内存储数据和资源,并可以被所有 Servlet 和 JSP 页面访问。这意味着开发者可以在不同的 Servlet 之间共享数据,而不仅仅是在同一个 Servlet 内。
获取 ServletContext 的方式
-
先用
this.getServletConfig()
方法获取到 ServletConfig 对象,然后再用ServletConfig
对象的getServletContext()
方法获得ServletContext
对象1
ServletContext servletContext = this.getServletConfig().getServletContext();
-
直接获取(推荐)
1
ServletContext context = this.getServletContext();
ServletContext 常见作用
-
共享数据,代码示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package pers.afelixliu.javaweb.servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 保存数据
*/
public class SetContext extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String user = "afelixliu";
ServletContext context = this.getServletContext();
context.setAttribute("user", user);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package pers.afelixliu.javaweb.servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 获取数据
*/
public class GetContext extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
ServletContext context = this.getServletContext();
System.out.println(context.getAttribute("user"));
}
} -
获取当前的工程名/模块名,代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package pers.afelixliu.javaweb.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class GetContextPath extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String contextPath = this.getServletContext().getContextPath();
System.out.println(contextPath);
}
} -
获取web.xml中配置的初始化参数context-param,代码示例如下:
1
2
3
4<context-param>
<param-name>name</param-name>
<param-value>afelixliu</param-value>
</context-param>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package pers.afelixliu.javaweb.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class GetContextParam extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String name = context.getInitParameter("name");
System.out.println("param name is " + name);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
} -
读取资源文件,代码示例如下:
第一步、在
src/main/resources
目录(必须在该目录)下新建user.properties
文件1
2username=afelixliu
age=18第二步、读取文件
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 pers.afelixliu.javaweb.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class GetResource extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*
* "/WEB-INF/classes/user.properties" 为target文件夹下工程名/模块名中对应文件的路径
* 第一个“/”表示工程名/模块名
*/
try (InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/user.properties")) {
Properties prop = new Properties();
prop.load(is);
String name = prop.getProperty("username");
int age = Integer.parseInt(prop.getProperty("age"));
System.out.println(name + " " + age);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
} -
请求转发,代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package pers.afelixliu.javaweb.servlet;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RequestDispatcherDemo extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
/*
* "/hello" 为转发的目标地址,可以是Servlet映射地址,也可以是一个jsp页面...
* 可以使用相对路径,也可以使用绝对路径
* 第一个“/”表示工程名/模块名
*/
context.getRequestDispatcher("/hello").forward(req, resp);
}
} -
…
7.5 HttpServletResponse
HttpServletResponse 对象代表服务器的响应。这个对象中封装了向客户端发送数据、发送响应头和发送响应状态码的方法。
HttpServletResponse 是 ServletResponse 的子接口。
(一)向客户端发送数据的相关方法
-
ServletOutputStream getOutputStream() throws IOException;
-
PrintWriter getWriter() throws IOException;
(二)向客户端发送响应头的相关方法
void addDateHeader(String var1, long var2)
void setDateHeader(String var1, long var2)
void addHeader(String var1, String var2)
void setHeader(String var1, String var2)
void addIntHeader(String var1, int var2)
void setIntHeader(String var1, int var2)
(三)向客户端发送响应状态码的相关方法
-
void setStatus(int var1)
响应状态码(位于
HttpServletResponse
接口)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
41int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
使用场景
-
文件下载: 通过设置响应头部,可以将文件下载链接提供给客户端,实现文件下载功能。代码示例如下:
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
36package pers.afelixliu.javaweb.servlet;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FileServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.获取文件信息
String filePath = "D:\\IdeaProjects\\javaweb-study\\servlet\\src\\main\\resources\\MVDP.jpg";
String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1);
// 2.设置响应头(中文文件名使用URLEncoder.encode编码,否则有可能乱码)
resp.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
try {
// 3.获取文件输入流
FileInputStream fis = new FileInputStream(filePath);
// 4.缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 5.获取文件输出流,下载文件
while ((len = fis.read(buffer)) != -1) {
resp.getOutputStream().write(buffer, 0, len);
}
// 6.关闭流
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} -
动态内容生成: 使用
HttpServletResponse
可以在服务器端生成动态的 HTML、JSON 等响应内容。代码示例如下(随机验证码):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
56package pers.afelixliu.javaweb.servlet;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ImageServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("image/jpeg");
// 浏览器自动刷新
resp.setIntHeader("Refresh", 2);
// 禁用浏览器缓存
resp.setDateHeader("Expires", -1);
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Pragma", "no-cache");
// 内存中创建一个图片
BufferedImage image = new BufferedImage(180, 90, BufferedImage.TYPE_INT_RGB);
// 设置画笔
Graphics g = image.getGraphics();
g.setColor(Color.white);
g.setFont(new Font("Times New Roman", Font.PLAIN, 60));
g.fillRect(0, 0, 180, 90);
// 绘制图片
g.setColor(Color.black);
g.drawString(getRand(), 30, 66);
// 将图片响应给浏览器
ImageIO.write(image, "jpeg", resp.getOutputStream());
}
public String getRand() {
Random random = new Random();
String num = random.nextInt(9999) + "";
StringBuilder complete = new StringBuilder();
for (int i = 0; i < 4 - num.length(); i++) {
complete.append(0);
}
num += complete.toString();
return num;
}
} -
错误处理: 在发生错误时可以使用
HttpServletResponse
发送合适的错误响应给客户端。 -
重定向,代码示例如下(登录跳转):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package pers.afelixliu.javaweb.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginAuthServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if ("admin".equals(req.getParameter("name")) && "123".equals(req.getParameter("password"))) {
resp.sendRedirect(req.getServletContext().getContextPath() + "/login-success.jsp");
} else {
resp.sendRedirect("index.jsp");
}
}
}
请求转发与重定向的异同:
- 两者都可以实现页面的跳转;
- 请求转发是服务器内部转发,浏览器只发出一次请求;重定向是浏览器发出了两次请求,第一次请求得到一个地址,根据得到的地址发出第二次请求才得到内容。
- 请求转发时浏览器上的地址不会改变;重定向时浏览器上的地址会改变。
- 请求转发的效率更高,重定向的效率低。
- 请求转发可以使用 request 对象在多个页面间传递参数;重定向不可以。
- 请求转发可能造成表单的重复提交;重定向不会,在表单处理时经常使用重定向解决表单重复提交问题。
- 请求转发只能在服务器内部转发;重定向可以跳转到其他服务器进行转发。
7.6 HttpServletRequest
HttpServletRequest 对象代表客户端的请求,当客户端通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。
HttpServletReques 是 ServletRequest 的子接口。
HTTP 请求消息分为请求行、请求消息头和请求消息体三部分,所以 HttpServletRequest 接口中定义了获取请求行、请求头和请求消息体的相关方法。
(一)获取请求行信息
HTTP 请求的请求行中包含请求方法、请求资源名、请求路径等信息。
getMethod()
- 获取 HTTP 请求方式getRequestURI()
- 获取请求行中的资源名称部分getQueryString()
- 获取请求行中的参数部分getContextPath()
- 返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""getServletPath()
- 获取 Servlet 所映射的路径getRemoteAddr()
- 获取客户端的 IP 地址getRemoteHost()
- 获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址- …
(二)获取请求消息头信息
当浏览器发送请求时,需要通过请求头向服务器传递一些附加信息,例如客户端可以接收的数据类型、压缩方式、语言等。
getHeader(String name)
- 获取一个指定头字段的值,如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值getHeaders(String name)
-返回指定头字段的所有值的枚举集合,在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次getHeaderNames()
- 返回请求头中所有头字段的枚举集合getContentType()
- 获取 Content-Type 头字段的值getContentLength()
- 获取 Content-Length 头字段的值getCharacterEncoding()
- 返回请求消息的字符集编码- …
(三)获取请求消息体信息
getParameter(String name)
- 返回指定参数名的参数值getParameterNames()
- 以枚举集合的形式返回请求中所有参数名getParameterValues (String name)
- 以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)getParameterMap()
- 将请求中的所有参数名和参数值装入一个 Map 对象中返回- …
8. Cookie 与 Session
由于 Http 是一种无状态的协议,服务器单从网络连接上无从知道客户身份。
会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用会话跟踪技术是 Cookie 与 Session。
Cookie 和 Session 主要区别:
- 存储位置:Cookie 存储在客户端浏览器上,而 Session 存储在服务器端。
- 安全性:由于 Cookie 存储在客户端,因此容易受到攻击(如跨站脚本攻击 XSS),而 Session 存储在服务器端,相对更安全。
- 容量限制:Cookie 的大小有限制(通常为 4KB),而 Session 的大小限制取决于服务器的内存。
8.1 Cookie
Cookie 是一种客户端存储技术,它将少量数据存储在用户的浏览器上,以便后续请求时可以读取这些数据。
8.1.1 Cookie 工作步骤:
- 客户端请求服务器,如果服务器需要记录该用户的状态,就是用 response 向客户端浏览器颁发一个 Cookie。
- 客户端浏览器会把 Cookie 保存下来。
- 当浏览器再请求该网站时,浏览器把该请求的网址连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。
注意:Cookie 功能需要浏览器的支持,如果浏览器不支持 Cookie 或者 Cookie 禁用了,Cookie 功能就会失效。
8.1.2 Cookie 常用方法
1 | // 服务端向客户端返回Cookie |
8.1.3 Cookie 使用场景
当需要在客户端持久化少量数据时,比如用户偏好设置或语言选择等,可以选择使用 Cookie。
8.2 Session
Session 是一种服务器端存储技术,它将用户会话数据保存在服务器上,并通过某种机制(通常是 Cookie)将 Session ID 传送给客户端。
8.2.1 Session 工作原理:
当第一次调用 request.getSession()
时,此时 Servlet 容器会创建一个 Session ,并且将其 sessionId 保存到 Cookie 中,然后回写给 response。因此,首次创建 Session 时的响应头中有 JSESSIONID 这个字段,后续的 request 默认都会带上 JSESSIONID 这个字段,而 response 中则不会再有该字段,服务器则根据 JSESSIONID 这个字段值查找对应的 Session。
具体来说,首次请求时响应头中的 Cookie 会有 JSESSIONID 字段,但请求头中是没有 Cookie 的;后续的请求中,响应头中则没有了 Cookie,但是请求头中会有包含 JSESSIONID 字段的 Cookie。
但是,如果浏览器禁用了 Cookie,request 中无法携带 Cookie 数据,那么,每次请求都会重新创建 Session,并且将 sessionId 回写到 response 的 Cookie 中。因为服务器没有获取到 JSESSIONID 这个值,也就无法根据 JSESSIONID 的值查找相应的 Session,也就是说,如果客户端禁用了 Cookie,那么每次请求得到的 sessionId 是不一样的。
8.2.2 Session 常用方法
1 | // 获取Session,没有则创建 |
同样可以在 web.xml
实现 Session 超时时间:
1 | <!-- Session设置 --> |
8.2.3 Session 使用场景
当需要在服务器端持久化用户会话状态时,比如用户登录状态、购物车信息等,可以选择使用 Session。
9. JSP
JSP 全称 Java Server Pages,是一种动态网页开发技术,主要用于实现 JavaWeb 应用的用户界面部分。
JSP 文件必须放到 /src/main/webapp
下,文件名必须以 .jsp
结尾,整个文件与 HTML 并无太大区别,主要使用 JSP 标签在 HTML 网页中嵌入 Java 代码,标签通常以 <%
开头,以 %>
结束。
访问 JSP 页面时,直接指定完整路径,如:localhost:8080/login_demo_war_exploded/login.jsp
9.1 JSP 原理
JSP 和 Servlet 并无任何区别,JSP 本质上就是一个 Servlet,只不过无需配置映射路径,Web Server 会根据路径查找对应的 .jsp
文件,如果找到了,就自动编译成 Servlet 再执行,Servlet 会生成 HTML 内容并发送回客户端。在服务器运行过程中,如果修改了 JSP 的内容,那么服务器会自动重新编译。
如果使用 IDEA 配置运行 Tomcat,JSP 文件编译后对应的 Servlet 目录位于:
C:\Users\用户名\AppData\Local\JetBrains\当前IDEA版本\tomcat\修改日期为最新的一个文件\work\Catalina\localhost\项目名\org\apache\jsp
代码示例:
1 | /* |
其中主要方法是 public void _jspService(...)
9.2 JSP 基本语法
本节主要参考:
[1]. 【JavaWeb】JSP入门学习(全面)-CSDN博客
9.2.1 JSP 脚本元素
-
表达式:
<%= ... %>
,用于输出表达式的值1
2
3
4
5
6<%
String name = "afelixliu";
%>
name: <%= name %>
<%= new java.util.Date() %> -
脚本片段:
<% ... %>
,包含任意Java代码,可以用来定义变量或编写控制结构,最终代码会放在service()
方法中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<%
int sum = 0;
for (int i = 0; i <3; i++) {
sum += i;
}
out.print("<h1>0+1+2="+sum+"</h1>");
%>
<%
int x = 100;
out.print(x);
%>
<%
for (int i = 0; i < 3; i++) {
out.write("<h3>hello, jsp_" + i + "</h6>");
}
%> -
声明:
<%! ... %>
,用于定义类级别的变量和方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<%!
static {
System.out.println("loading jsp");
}
private int t = 0;
public void setT(int t) {
this.t = t;
}
public int getT() {
return t;
}
public void say() {
System.out.println("hello, everyone");
}
%>
9.2.2 JSP 指令元素
JSP 指令用于配置 JSP 页面、导入资源文件,位于文件顶部。
JSP 指令分为三种:page
、include
、taglib
-
页面指令
<%@ page ... %>
用于设置JSP页面属性。其常见属性有:
-
contentType
:等同于response.setContentType()
,作用:- 设置响应体的 mime 类型以及字符集
- 设置当前 JSP 页面的编码(只能是高级的 IDE 才能生效,如果使用低级工具,则需要设置 pageEncoding 属性设置当前页面的字符集)
-
import
:导包 -
errorPage
:当前页面发生异常后,会自动跳转到指定的错误页面。 -
isErrorPage
:标识当前页面是否是错误页面。属性值:- true:是,可以使用内置对象 exception
- false:否,默认值。不可以使用内置对象 exception
- true:是,可以使用内置对象 exception
-
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
- 包含指令
<%@ include file="..." %>
用于包含其他文件的内容。
1 | <%@ include file="common/header.jsp"%> |
-
标签库指令
<%@ taglib uri="..." prefix="..." %>
用于引入自定义标签库。其属性有:
-
prefix
:前缀名称,自定义的,一般自定义为c
-
uri
:统一资源标识,导入的资源
-
9.2.3 JSP 动作元素
在 JSP 中,动作元素(也叫动作标签)用于执行特定的任务或操作,例如包含其他页面、转发请求、设置属性等。动作标签可以在 JSP 页面中以 XML 风格的标签形式使用,让开发者可以更方便地管理和控制页面的行为。
常用的动作标签及其用法:
<jsp:include>
标签用于在当前页面中包含其他页面的内容。被包含的页面可以是 JSP 页面、HTML 页面或者其他文本文件。<jsp:forward>
标签用于将请求转发到其他页面,通常用于实现请求的重定向。<jsp:useBean>
标签用于在页面中创建或定位一个 JavaBean 实例,使得页面可以使用该 JavaBean 中的属性和方法。<jsp:setProperty>
标签用于设置 JavaBean 实例的属性值,<jsp:getProperty>
标签用于获取 JavaBean 实例的属性值。<jsp:param>
标签用于在包含其他页面或转发请求时,传递参数给被包含的页面或转发的目标页面。<jsp:plugin>
标签用于嵌入 Applet 或者其他对象到生成的页面中。
1 | <jsp:forward page="/jsp-forward.jsp"> |
1 | name: <%= request.getParameter("name") %> <br /> |
注意:使用 <%@ include file="..." %>
会将两个页面合二为一,使用 <jsp:include>
拼接页面,本质还是分开的。
9.3 JSP 内置对象
JSP 的内置对象是指在 JSP 页面中不需要获取和创建,可以直接使用的对象。JSP 一共有 9 个内置对象:
变量名 | 真实类型 | 作用 |
---|---|---|
pageContext | PageContext | 提供对各种范围对象(如 page、request、session、application)的统一访问 |
request | HttpServletRequest | 表示客户端的请求,包含请求参数、头信息、请求方法等 |
response | HttpServletResponse | 表示服务器对客户端的响应,可以设置响应头、状态码和发送数据 |
out | JspWriter | 用于向客户端输出内容,类似于标准的Java PrintWriter |
session | HttpSession | 表示一个用户会话,用于在多个请求间保持用户数据 |
application | ServletContext | 表示 Web 应用程序的上下文,可以在整个应用中共享数据 |
config | ServletConfig | Servlet的配置对象 |
page | Object | 表示当前的 JSP 页面对象,等同于 this 关键字 |
exception | Throwable | 在 JSP 页面中处理未捕获的异常时使用,通常在错误页面中使用 |
JSP 四大作用域对象
对象名称 | 作用范围 |
---|---|
application | 整个应用都有效 |
session | 在当前会话中有效 |
request | 在当前请求中有效 |
page | 在当前页面有效 |
同页:
1 | <% |
跨页:
1 | <% |
response.getWriter()
和 out.write()
的区别:
- 在 Tomcat 服务器真正给客户端做出响应之前,会先找 response 缓冲区数据,再找 out 缓冲区数据
response.getWriter()
数据输出永远在out.write()
之前
9.4 JSP 注释
有两种注释的方式:
<!-- 内容 -->
:html 注释,只能注释 html 代码片段<%-- 内容 --%>
:ISP 注释,可以注释所有(推荐使用)
JSP 注释不会在客户端显示,HTML 注释就会!
9.5 JSTL 标签库
JSTL(Java server pages standard tag library,即 JSP 标准标签库),JSTL 标签是基于 JSP 页面的,这些标签可以插入在JSP代码中,本质上 JSTL 也是提前定义好的一组标签,这些标签封装了不同的功能,在页面上调用标签时,就等于调用了封装起来的功能。JSTL 的目标是使 JSP 页面的可读性更强、简化 JSP 页面的设计、实现了代码复用、提高效率。
9.5.1 JSTL 标签分类
根据 JSTL 标签所提供的功能,可以将其分为 5 个类别:
-
核心标签
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" %>
9.5.2 JSTL 使用步骤
-
导入 jstl 相关 jar 包
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.taglibs/taglibs-standard-impl -->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
<scope>runtime</scope>
</dependency> -
在 JSP 页面中引入对应的 taglib
1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-
使用标签(具体标签使用可参考:JSP 标准标签库(JSTL) | 菜鸟教程 (runoob.com))
1 | <form method="get" action="${pageContext.request.contextPath}/demo.jsp"> |
1 | <c:set var="score" value="80" /> |
1 | <% |
9.6 EL 表达式
JSTL 仅是 JSP 表达式语言,而 EL 既是 HTML 也是 JSP 表达式语言,全称是Expression Language,可以用来替换和简化 JSP 页面中 Java 代码的编写。
语法格式
1 | ${表达式} |
注意事项
JSP 默认支持 EL 表达式。如果要忽略 EL 表达式,有以下两种方法实现:
- 设置 jsp 中 page 指令中:属性
isELIgnored="true"
忽略当前 jsp 页面中所有的 EL 表达式 \${表达式}
:在 $ 符号前面加上反斜杠 \,表示忽略当前这个 EL 表达式。
EL 表达式 的使用
(1)运算
表达式中支持以下运算符:
- 算数运算符: + - * /(或div) %(或mod)
- 比较运算符: > < >= <= == !=
- 逻辑运算符: &&(and) ||(or) !(not)
- 空运算符: empty
空运算符empty的功能比较强大,用于判断字符串、集合、数组对象是否为 null 或者长度是否为 0,使用如下:
${empty list}
:判断字符串、集合、数组对象是否为 null 或者长度为 0${not empty str}
:表示判断字符串、集合、数组对象是否不为 null 并且长度 >0
(2)获取值
EL表达式只能从域对象中获取值,
语法:${域名称.键名}
:从指定域中获取指定键的值。
常见域有:
域名称 | 对应的域 |
---|---|
pageScope | pageContext域 |
requestScope | request域 |
sessionScope | session域 |
applicationScope | application(ServletContext)全局域 |
域大小:pageContext < request < session < application(ServletContext)
简化语法:
${键名}
:表示依次从最小的域中查找是否有该键名对应的值,直到找到为止。没有则不显示
(3)获取对象、List 集合、Map 集合的值
-
获取对象的值,语法如下:
${域名称.键名}
:域中某个键的值是一个对象${域名称.键名.属性名}
:获取对象的属性值, 本质上会去调用对象的 getter 方法
-
获取 List 集合的值,语法如下:
${域名称.键名[索引]}
:域中某个键的值是一个 List 对象,加上索引可以获取到集合中对应的值 -
获取 Map 集合的值,有两种方式:
${域名称.键名.key名称}
${域名称.键名["key名称"]}
(4)使用 JSP 内置对象
一般利用 pageContext 来获取 JSP 其他八个内置对象
pageContext 对象常用于动态获取虚拟目录:${pageContext.request.contextPath}
9.7 配置 error-page
在 web.xml
中有两种配置 error-page 的方法:
一、通过错误码来配置 error-page
1 | <error-page> |
当系统发生 404 错误时,跳转到错误处理页面 error-404.jsp
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
二、通过异常的类型配置 error-page
1 | <error-page> |
当系统发生 java.lang.NullPointException
时,跳转到处理页面 error.jsp
10. JavaBean
JavaBean 是一个 Java 类,满足以下条件:
- public 修饰的类
- public 无参构造
- 所有属性(如果有)都是 private,并且提供 set/get (如果 boolean 则 get 可以替换成 is)
1 | package pers.afelixliu.javaweb.jsp.entity; |
1 | <jsp:useBean id="user" class="pers.afelixliu.javaweb.jsp.entity.User" scope="page" /> |
11. MVC
Model:模型持有所有的数据、状态和程序逻辑
- 数据承载Bean,指实体类,专门承载业务数据
- 业务处理Bean,指Service或Dao对象,专门处理用户请求
View:负责界面的布局和显示
Controller:负责模型和界面之间的交互
12. Filter
过滤器(Filter)是 JavaWeb 应用中重要的组件之一,它用于在请求到达 Servlet 之前或响应返回客户端之前对请求和响应进行处理。
过滤器的生命周期
- 构造器方法
init()
初始化方法doFilter()
方法 (其中有filterChain.doFilter(...)
方法)- 在
doFilter()
方法中,可以通过filterChain.doFilter(...)
方法控制请求是否继续向后传递 - 在
doFilter()
方法中,同样可以使用 HttpRequest 处理请求,使用 HttpResponse 对象作出响应
- 在
destroy()
方法
其中:
- 1 和 2 在 web 工程启动的时候执行;
- 符合拦截路径的请求发送到服务器的时候,自动执行 3,若请求不属于拦截路径,则不会执行;
- 停止 web 工程的时候执行 4
过滤器的使用:
-
实现 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
25package pers.afelixliu.javaweb.jsp;
import java.io.IOException;
import javax.servlet.*;
public class CustomFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=UTF-8");
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
Filter.super.destroy();
}
} -
注册配置 Filter
方式一:在
web.xml
注册配置 Filter1
2
3
4
5
6
7
8
9
10<!-- Filter注册 -->
<filter>
<filter-name>custom_filter</filter-name>
<filter-class>pers.afelixliu.javaweb.jsp.CustomFilter</filter-class>
</filter>
<!-- Filter配置 -->
<filter-mapping>
<filter-name>custom_filter</filter-name>
<url-pattern>/filter-demo/*</url-pattern>
</filter-mapping>方式二:在 Filter 类上使用
@WebFilter
注解进行配置1
2
3
4
public class CustomFilter implements Filter {
// 过滤器的实现
}
在 Filter 类的
doFilter()
方法中,开发者需要显式调用FilterChain
的doFilter()
方法,以便请求能够继续传递到下一个过滤器。如果在某个过滤器的doFilter()
方法中没有调用FilterChain
的doFilter()
方法,请求将被阻塞,不会继续传递到后续的 Filter 或 Servlet。
Filter 拦截路径配置
拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter 注解进行配置。如:@WebFilter("拦截路径")
拦截路径有如下四种配置方式:
- 拦截具体的资源:
/index.jsp
,只有访问index.jsp
时才会被拦截 - 目录拦截:
/user/*
,访问/user
下的所有资源,都会被拦截 - 后缀名拦截:
*.jsp
,访问后缀名为 jsp 的资源,都会被拦截 - 拦截所有:
/*
,访问所有资源,都会被拦截
过滤器链
过滤器链是指在一个 Web 应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
过滤器链中的过滤器的优先级根据过滤器类名字母序列进行字典排列。
上图中的过滤器链执行是按照以下流程执行:
- 执行 Filter1 的放行前逻辑代码
- 执行 Filter1 的放行代码
- 执行 Filter2 的放行前逻辑代码
- 执行 Filter2 的放行代码
- 访问到资源
- 执行 Filter2 的放行后逻辑代码
- 执行 Filter1 的放行后逻辑代码
以上流程串起来就像一条链子,故称之为过滤器链。
13. Listener
Listener 提供了一种机制,使开发者能够编写 Listener 类来监听容器事件,并在事件发生时执行相应的逻辑。这样的机制使得开发者能够在不修改源代码的情况下,通过监听器对现有应用程序进行扩展或增强。
Listener 类型
JavaWeb 提供了多种类型的 Listener,其中最常见的有以下三种:
- ServletContextListener(上下文监听器):用于监听 Web 应用程序的启动和关闭事件
- HttpSessionListener(会话监听器):用于监听会话的创建和销毁事件
- ServletRequestListener(请求监听器):用于监听请求的创建和销毁事件
Listener 的生命周期
Listener 的生命周期由容器管理,容器会在适当的时机调用监听器的方法。
-
ServletContextListener 生命周期
ServletContextListener 接口定义了两个方法:
- contextInitialized(ServletContextEvent sce):在 Web 应用程序初始化时被调用。
- contextDestroyed(ServletContextEvent sce):在 Web 应用程序销毁时被调用。
-
HttpSessionListener 生命周期
HttpSessionListener 接口也定义了两个方法:
-
sessionCreated(HttpSessionEvent se):在会话创建时被调用。
-
sessionDestroyed(HttpSessionEvent se):在会话销毁时被调用。
-
-
ServletRequestListener 生命周期
ServletRequestListener 接口同样定义了两个方法:
-
requestInitialized(ServletRequestEvent sre):在请求创建时被调用。
-
requestDestroyed(ServletRequestEvent sre):在请求销毁时被调用。
-
监听器的使用:
-
实现相应的接口(以 HttpSessionListener 为例)
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 pers.afelixliu.javaweb.jsp;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionListenerDemo implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer num = (Integer) context.getAttribute("OnlineUsers");
if (num == null) {
num = 1;
} else {
num = num + 1;
}
context.setAttribute("OnlineUsers", num);
}
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer num = (Integer) context.getAttribute("OnlineUsers");
if (num == null) {
num = 1;
} else {
num = num - 1;
}
context.setAttribute("OnlineUsers", num);
}
} -
注册 Listener
方式一:在
web.xml
注册配置 Listener1
2
3
4<!-- Listener注册 -->
<listener>
<listener-class>pers.afelixliu.javaweb.jsp.SessionListenerDemo</listener-class>
</listener>方式二:在 Listener 类上使用
@WebListener
注解进行配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SessionListenerDemo implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent se) {
// 在会话创建时执行初始化操作
System.out.println("会话创建:" + se.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent se) {
// 在会话销毁时执行清理操作
System.out.println("会话销毁:" + se.getSession().getId());
}
}
JavaWeb 三大组件:Servlet、Filter、Listener
14. JUnit 单元测试使用
配置依赖:
1 | <dependency> |
简单使用:
使用 @Test
注解,该注解只有在方法上有效,只要加了这个注解的方法,就可以直接运行,而不用运行 main()
方法。
1 | package pers.afelixliu.javaweb.logindemo; |