JavaWeb开发(一)

Tomcat简介

Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全局管理和Tomcat阀等。[1]

Jakarta EE平台是Java EE平台的演进。Tomcat 10及以后的版本实现了作为Jakarta EE一部分开发的规范。Tomcat 9和更早版本的实现规范是作为Java EE的一部分开发的。

Apache Tomcat本身完全开源免费,这也是它受到热烈欢迎的原因之一。Tomcat的作用是自己开发的APP,它的运行需要jre。jre是针对Java SE的,如果需要运行Java EE的程序就提供更多的支持,Tomcat的作用就是这个。

Web项目的标准结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Web Root
|
├─── WEB-INF
│ ├─── web.xml
│ ├─── classes
│ └─── lib

├─── Static Resources
│ ├─── HTML Files
│ ├─── CSS Files
│ └─── JavaScript Files

├─── Dynamic Resources
│ ├─── JSP Files
│ └─── Servlet Files

└─── Configuration Files
├─── web.xml
├─── context.xml
└─── log4j.properties

[2] 其中WEB-INF目录下的资源是被保护的资源,不可以被浏览器访问。用来放第三方jar包,classes,web.xml等。

JSP 文件:JavaServer Pages(JSP)文件是用于创建动态 Web 内容的文件。它们通常位于 Web 根目录下的某个位置。JSP 文件包含 HTML 代码和 Java 代码,用于生成动态内容。当 JSP 文件被访问时,它们会被服务器编译成 Servlet 类,并由服务器进行处理和执行。

Servlet 文件:Servlet 是 Java Web 应用程序的核心组件之一,用于处理客户端请求并生成动态内容。Servlet 文件通常位于 WEB-INF 目录下的某个位置,可以通过映射 URL 来访问。Servlet 文件是 Java 类,实现了 Servlet 接口或扩展了 HttpServlet 类,通过重写相应的方法来处理请求和生成响应。

Servlet

简介

Servlet 是 Java EE(现在称为 Jakarta EE)平台中用于处理 Web 请求并生成响应的一种 Java 编程技术。不是所有的Java类都能处理客户端的请求,能处理客户端请求并做出响应的一套技术标准就是 Servlet。Servlet 是运行在服务器端的,所以必须在 Web 项目中开发并且在 Tomcat 这样的服务容器中运行。

  • 静态资源:在程序运行之前就写好了的资源,比如hexo静态博客中的js,css,html文件
  • 动态资源:程序运行时通过代码运行生成的资源,指的不是js创建的动画效果

Servlet用于接收、处理客户端请求、响应给浏览器的动态资源。是运行在Tomcat的Java小程序,是sum公司提供的一套自定义动态资源规范,从代码层面上讲 Servlet 就是一个接口。

工作流程

  1. Tomcat 接收到请求以后,会把请求的信息转换成一个HttpServletRequest对象,包含了请求消息中的所有信息。
  2. Tomcat 同时创建了一个HttpServletResponse对象,用来装要响应给客户端的信息,未来这个对象会被转换成响应消息。
  3. Tomcat 根据提供的路径找到对应的 Servlet,实例化,调用service方法,同时传入两个对象:HttpServletRequestHttpServletResponse。从而获取请求消息;然后根据请求消息生成要响应给客户端的数据,将相应的数据放入HttpServletResponse对象。
  4. 最后HttpServletResponse被转换成响应消息发回客户端。

我们需要自己定义一个 Servlet 类,实现接口Servlet,重写service方法。


举个例子,客户端提交一个用户名,如果用户名已经存在,那么返回No

  1. 创建一个Java项目,同时把 Tomacat 添加为依赖
  2. 重写protected void service(HttpServletRequest req, HttpServletResponse resp)
  3. service方法中定义业务处理代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 配置Servlet类,起一个别名-->
<!-- servlet-class 用于告知tomcat要实例化的类
name 用于关联请求的路径-->
<!--用于告诉容器这个 Servlet 存在,以及它的类在哪。-->
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>servlet.UserServlet</servlet-class>
</servlet>

<!--这是 Servlet 的 URL 映射部分,用于告诉服务器:当访问某个 URL 时,要调用哪个 Servlet。-->
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/absolute</url-pattern>
</servlet-mapping>

一个servlet-name可以对应多个url-pattern,但是一个url-pattern只能和一个servlet-name对应。

使用注解的方式可以完全替代上面复杂的web.xml代码,只需要在自定义的public class UserServlet extends HttpServlet类前加上注解@WebServlet("/自定义")(也可以定义多个url)。这两种方式二选其一,不能重复。

插曲

servlet-api.jar 编码的时候需要,但是服务器内置,所以不参与打包,作用范围是provided

响应头中的 Content-Type 显示的是MIME类型,MIME 类型用于告诉客户端响应的数据是什么类型的数据。什么是MIME类型?

MIME 类型(Multipurpose Internet Mail Extensions) 是一种在 HTTP 通信或邮件传输中标识文件内容类型的标准方式。它告诉浏览器或其他客户端:服务器发送的内容属于哪种格式,应该如何处理。

MIME 格式:主类型/子类型,例如text/html指的是HTML文件。

conf/web.xml中记录了几乎所有文件类型对应的MIME类型,例如:

1
2
3
4
5
6
7
8
 <mime-mapping>
<extension>htm</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html</mime-type>
</mime-mapping>

extension指的是文件拓展名。Tomcat 通过这里记录的关系对 Content-Type 赋值。将相应的数据放入响应体中时可以调用对应的方法手动指定。

Servlet 生命周期

普通 Servlet

  1. 实例化——构造器,第一次请求//服务启动(这个执行阶段可自己指定,关键词loadOnStartUp
  2. 初始化——init,构造完毕执行一次
  3. 接收请求,处理请求 服务——service,每次请求都执行
  4. 销毁——destroy,关闭服务时请求

Servlet在 Tomcat 中是单例的。其中的成员变量在多个线程中是共享的,所以不建议在service方法中修改成员变量,否则会引发线程安全问题。

DefaultServlet

它被定义在 Tomcat 安装目录下的\conf\web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<!-- debug=0 调试信息等级0,表示不输出debug信息 -->
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<!-- listings=false 禁止目录列表浏览功能-->
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<!-- 优先级1,表示容器启动时立即加载此 Servlet -->
<load-on-startup>1</load-on-startup>
</servlet>

目录列表浏览功能”:当你在浏览器中访问一个 目录路径,如果这个目录下没有默认的首页文件(如index.htmlindex.jsp),Tomcat 会尝试列出这个目录中的所有文件,这个功能就是所谓的目录列表浏览(Directory Listing)。

在禁止目录列表浏览功能的情况下,就算找不到默认首页文件,也不会展示目录列表,直接返回403。这样可以防止用户发现敏感文件,不暴露Web应用的内部结构。

它主要处理:

  • 静态文件(如.html.css.js、图片等)请求(最重要)
  • 找不到匹配的servlet或映射时,作为“兜底”处理请求

SpringMVC 会导致 DefaultServlet 失效,无法访问静态资源,这时需要重新配置 DefaultServlet。

Servlet 接口

位于包javax.servlet

1
2
3
4
5
6
7
public interface Servlet {
void init(ServletConfig config) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
  • void init: 初始化 Servlet,只在 Servlet 第一次被加载时调用一次。
  • ServletConfig getServletConfig: 返回 Servlet 的配置对象(ServletConfig),包含初始化参数等信息。
  • void service: 处理客户端请求,是 Servlet 的核心方法。
  • String getServletInfo: 返回一个字符串,用于描述 Servlet 的信息。一般用处不大,可以写作者名、版本号等。
  • void destroy: 在 Servlet 被销毁之前调用,用于释放资源。可以用于关闭数据库连接、清理线程池等。

Servlet 实现类

这个接口有两个实现类:GenericServletHttpServlet

GenericServlet

所在包:javax.servlet。GenericServlet 是一个抽象类,实现了 Servlet 接口的大部分方法,但是没有实现service(),所以还是抽象类。它与协议无关。

使用它的时候只需要重写service(ServletRequest req, ServletResponse res):

1
2
3
4
5
6
7
public class MyServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
res.getWriter().write("Hello from GenericServlet");
}
}

HttpServlet

所在包:javax.servlet.http,是GenericServlet的子抽象类,专为处理 HTTP 请求设计。它为 HTTP 请求方法(GET、POST、PUT 等)提供了对应的回调方法。

1
public abstract class HttpServlet extends GenericServlet{...}

关键源码:这是我们创建自定义类时要重写的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 向下转型
HttpServletRequest request;
HttpServletResponse response;
try { // 捕获强转异常
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用这个类中的另一个service方法
this.service(request, response);
}

这里的形参列表中的ServletRequestServletResponse是实际使用的HttpServletRequestHttpServletResponse的父类,我们使用的时候自动向下转型。

这个方法执行到最后会调用另一个被protected的重载方法:

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
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求方法名
String method = req.getMethod();
// 处理GET请求
if (method.equals("GET")) {
long lastModified = this.getLastModified(req); // 获取资源的最后修改时间
if (lastModified == -1L) { // -1说明没有使用缓存机制,直接调用 doGet() 处理请求
this.doGet(req, resp);
} else { // 如果使用了缓存机制
long ifModifiedSince; // 客户端请求中自带 If-Modified-Since 头字段,表示它缓存的时间。
try { // 获取这个字段
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
// 如果解析错误,设置为-1
ifModifiedSince = -1L;
}
// 缓存的时间 < 最后修改时间(秒),说明缓存太旧还没来得及更新
if (ifModifiedSince < lastModified / 1000L * 1000L) {
// 这时候要通知客户端重新设置缓存
// 设置 Last-Modified 响应头,并调用 doGet() 返回过去新数据。
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
// 304 状态码告诉客户端使用本地缓存
resp.setStatus(304);
}
}
// 处理 HEAD 请求
} else if (method.equals("HEAD")) {
long lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
// 处理其他 HTTP 方法
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
// 如果请求使用了不支持的HTTP方法,返回 501 Not Implemented 错误
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}

}

上面这个方法负责根据客户端请求的 HTTP 方法,自动将请求分发到对应的 doGet, doPost 等方法。下面来看doGet方法是如何实现的:

1
2
3
4
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}

作用是:如果没有重写doGet,当用户发起 GET 请求时,就会用英文提示“GET 不被支持”返回 405 错误。

其他几个请求方法也都是一样的逻辑。只要用户没有重写这些方法,都会返回错误信息。这样保证了需要什么就用什么,用不到的请求方法也不会被意外调用。

不过,话虽如此,在实际使用中直接重写service也是完全可以使用的,这样也比较方便。

后续使用 Spring 框架后,就不用再写这些了...

ServletConfig

ServletConfig 是什么?

ServletConfig是为Servlet提供初始配置参数的一种对象,每个Servlet都有自己独立唯一的ServletConfig对象。

容器会为每个Servlet实例化一个ServletConfig对象,并通过Servlet的生命周期的init方法传入给Servelt作为属性。

一般配置方式

web.xml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>servlet.Servlet1</servlet-class>

<!--配置初始参数-->
<init-param>
<param-name>keya</param-name>
<param-value>valueA</param-value>
</init-param>
<init-param>
<param-name>keyb</param-name>
<param-value>valueB</param-value>
</init-param>

</servlet>

<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>/s1</url-pattern>
</servlet-mapping>

然后就可以在 Servlet 中读取<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
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
/*第一种方法*/
// 获取初始配置信息
// 根据参数名获取参数值
String a_parameter = servletConfig.getInitParameter("keya");
System.out.println("A: " + a_parameter);
String b_parameter = servletConfig.getInitParameter("keyb");
System.out.println("B: " + b_parameter);
System.out.println();

/* 第二种方法 */
// 获取所有的初始参数的名字 迭代器!
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
// 判断有没有下一个参数;
while (initParameterNames.hasMoreElements()) {
// 取出下一个元素 并向下移动游标(迭代器的指针)
String name = initParameterNames.nextElement();
System.out.println(name + " : " + servletConfig.getInitParameter(name));
}
}
}

注解方式配置

使用@WebServlet注解可以更简单地完成上述配置

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
@WebServlet(
urlPatterns = "/s1",
initParams = {@WebInitParam(name="keya", value="valuea")}
)
public class Servlet1 extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
/*第一种方法*/
// 获取初始配置信息
// 根据参数名获取参数值
String a_parameter = servletConfig.getInitParameter("keya");
System.out.println("A: " + a_parameter);
String b_parameter = servletConfig.getInitParameter("keyb");
System.out.println("B: " + b_parameter);
System.out.println();

/* 第二种方法 */
// 获取所有的初始参数的名字 迭代器!
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
// 判断有没有下一个参数;
while (initParameterNames.hasMoreElements()) {
// 取出下一个元素 并向下移动游标(迭代器的指针)
String name = initParameterNames.nextElement();
System.out.println(name + " : " + servletConfig.getInitParameter(name));
}
}
}

打印结果:

1
2
3
4
A: valuea
B: null

keya : valuea

因为这时没有定义keyb参数,所以第一种方法打印出来B为null,使用迭代的方法没有打印B

ServletContext

ServletConfig类似,它也可以提供初始化配置参数,但ServletContext是为所有的Servlet对象提供公共的参数。

ServletContext 是一个全局的储存信息的空间,它被所有客户端共享,因此Servlet对象之间可以通过 ServletContext 对象来实现通讯。

修改 ServeletContext

1
2
3
4
5
6
7
8
<context-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</context-param>

获取 ServletContext 对象

1
2
3
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}

调用servlet对象.getServletContext()来获取 ServletContext 对象。

打印所有ServeletContext的全局参数,也可以使用迭代的方法,同ServletConfig

1
2
3
4
5
6
7
ServletContext servletContext = getServletContext();
Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String initParameterName = initParameterNames.nextElement();
String initParameterValue = servletContext.getInitParameter(initParameterName);
System.out.println(initParameterName + ": " + initParameterValue);
}

但是注意:这里调用getInitParameter()获取的是来自 web.xml 或注解配置的初始化参数,不可变,程序启动后不可改。如果想要获取程序运行时动态修改过的 ServletContext 属性,应当使用getAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
System.out.println("--------- ServletContext init params ---------");
Enumeration<String> initParams = servletContext.getInitParameterNames();
while (initParams.hasMoreElements()) {
String name = initParams.nextElement();
System.out.println("init param: " + name + " = " + servletContext.getInitParameter(name));
}

System.out.println("--------- ServletContext attributes ---------");
Enumeration<String> attrNames = servletContext.getAttributeNames();
while (attrNames.hasMoreElements()) {
String name = attrNames.nextElement();
System.out.println("attribute: " + name + " = " + servletContext.getAttribute(name));
}

获取资源真实路径 API

getServletContext().getRealPath("/目录名")

示例

src/Servlet3.java中写如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@WebServlet(
urlPatterns = "/s3",
initParams = {@WebInitParam(name = "s3", value = "sdeddd")}
)
public class Servlet3 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
// 获取真实路径
String path = servletContext.getRealPath("upload");
System.out.println(path);
}
}

控制台输出:

1
D:\Develop\web-all\out\artifacts\demo03_servletConfig_servletContext_war_exploded\upload

作用

获取项目部署路径,也叫上下文路径。与手动指定的方式不同的是,这个路径不是写死的,随项目部署位置的改变而自动变化。这样一来,不管该项目实在本地运行还是未来放到服务器中运行,都可以成功找到正确的路径。

本例中的项目结构为

web即项目的访问路径,upload开发项目中的的确切位置为web/upload;在部署项目中的位置变成了demo03_servletConfig_servletContext_war_exploded\upload,也就是我们刚才获取的真实路径。

域对象相关 API

域对象:即具有作用域(Scope)的对象。它们用于在 Web 应用中保存和共享数据。说白了,域对象就是一个放数据的对象,可以通过域对象来存值和取值,不同的域对象的范围不一样,也就限制了哪些类可以访问这个对象,进行值的存储,以达到数据交换的目的。

刚才提到的ServletContext在一个Web App中是唯一的,代表了这个应用,也叫应用域,是 Web App 中最大的域。

常见的域对象

域对象名称 接口类型 常用获取方式
request HttpServletRequest Servlet 方法参数或 JSP 中隐含对象
session HttpSession request.getSession()
application(即 ServletContext ServletContext getServletContext()application(JSP)
page(只在 JSP 中) PageContext JSP 中隐含对象 pageContext

域对象常用 API 方法

方法 作用
setAttribute(String name, Object value) 设置(添加或修改)一个属性(键值对)
getAttribute(String name) 获取指定属性的值
removeAttribute(String name) 移除指定属性
getAttributeNames() 返回所有属性名的枚举(Enumeration 类型)
  1. setAttribute
1
void setAttribute(String var1, Object var2);
  • var1: 相当于哈希表中的,不过必须是字符串类型,不可重复
  • var2: 相当于哈希表中的值
  1. getAttribute(String name): Object
  • 获取键对应的值,但是Object类型,类似于Java的下界通配符,写入安全但是读取受限

Tomcat 在启动 Web 应用时会把Tomcat 内部运行所需的组件、路径、缓存等对象自动放入 ServletContext 的 attribute 属性表中,所以如果运行以下代码:

1
2
3
4
5
6
System.out.println("--------- ServletContext attributes ---------");
Enumeration<String> attrNames = servletContext.getAttributeNames();
while (attrNames.hasMoreElements()) {
String name = attrNames.nextElement();
System.out.println("attribute: " + name + " = " + servletContext.getAttribute(name));
}

会得到包含用户自定义属性在内的非常长的一串输出,但是其中系统自带的属性并不是我们所关心的。因此,如果要查看自己定义的属性,最好使用getAttribute(String name)

HttpServletRequest

1
public interface HttpServletRequest extends ServletRequest {...}

它出现在service方法:

1
protected void service(HttpServletRequest req, HttpServletResponse resp) {...}

HttpServletRequest提供了一系列可用于处理 HTTP 请求的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/s4")
public class Servlet4 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getMethod()); // 请求方式
System.out.println(req.getScheme()); // 协议
System.out.println(req.getProtocol()); // 协议和版本号
System.out.println(req.getRequestURI()); // 项目内的资源路径
System.out.println(req.getRequestURL()); // 项目内资源的完整路径
System.out.println(req.getLocalPort()); // 本应用所在容器的端口号 8080
System.out.println(req.getServerName()); // 客户端发请求时用的端口号,因为代理的存在可能和LocalPort不一样
System.out.println(req.getRemoteHost()); // 客户端软件的端口号
}
}

运行一下:

1
2
3
4
5
6
7
8
GET
http
HTTP/1.1
/demo03/s4
http://localhost:8080/demo03/s4
8080
localhost
0:0:0:0:0:0:0:1

URI: 统一资源标识符。用于标识某个资源。只要能唯一地定位资源,就是 URI。
URL: 统一资源定位符。URI 的一种,不仅标识资源,还指定了获取资源的协议和地址
即:URL 是 URI 的一个子集,URI 更宽泛,URL 更具体。

0:0:0:0:0:0:0:1是 IPv6 表示形式的本地回环地址,等价于 IPv4 的 127.0.0.1。

获取头

1
2
3
4
5
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
System.out.println(headerName + ": " + req.getHeader(headerName));
}

获取请求参数

getParameterMap()返回一个Map<String, String[]>的变量,使用forEach遍历所有参数,这种处理方式也适用于多选的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@WebServlet("/s5")
public class Servlet5 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 根据指定的name获取键值对形式的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println(username + " " + password);

// 返回所有 map 的集合(更通用)
Map<String, String[]> parameterMap = req.getParameterMap();
parameterMap.forEach((key, value) -> {
System.out.println(key + " " + Arrays.toString(value));
});
}
}

以上API专门用于键值对形式的参数,无论这些参数是在url中还是请求体中。get请求参数以键值对的形式放在url中,一般约定不放在请求体中。post方式放在请求体中。

以下API用于读取文本/文件/JSON:

1
2
BufferedReader reader = req.getReader(); // 字符流 文本数据
ServletInputStream inputStream = req.getInputStream(); // 字节流 二进制数据

HttpServletResponse

提供了一系列用于处理响应消息的方法:

  • setStatus: 设置状态码
  • setHeader: 设置响应头的字段,传入字段名和字段值
  • setContentType: 设置 MIME 类型,如前所述,作用是告诉客户端响应的数据是什么类型的数据。其实用setHeader也能完成,但是因为太过重要所以单独封装了一个方法
  • setContentLength:设置传输的内容长度,同样用setHeader也能完成,但是因为太过重要所以单独封装了一个方法
  • getWriter: 获取一个字符输出流PrintWriter,用于设置响应体的内容

请求转发和响应重定向

请求转发(Forward) 响应重定向(Redirect)[3]

forward 转发

服务器内部跳转,浏览器地址栏不变。

用法:

1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/s1")
public class servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servlet1 service");
// getRequestDispatcher 获取一个可以将当前请求转发或包含到指定资源的请求分派器对象
RequestDispatcher s2 = req.getRequestDispatcher("s2");
// forward 实现请求转发功能,把请求转发给另一个Servlet
s2.forward(req, resp);
}
}

客户端只产生一次请求,服务端只产生一次req和resp,这一对req和resp可以一直传递下去。

当然,getRequestDispatcher传入的参数不仅仅局限于 Servlet ,也可以是一个文件:

1
2
req.getRequestDispatcher("test.html").forward(req, resp);
// 运行后页面显示为 test.html ,但是地址栏仍然没有发生变化。

就算是 WEB-INF 中的资源也可以通过这种方式被访问,这也是 WEB-INF 访问的唯一方式。

也就是说,目标资源既可以是动态资源,也可以是静态资源,也可以是受保护的资源,但是不能是外部资源。

redirect 重定向

语法:

1
resp.sendRedirect("test.html");
  • 通过HttpServeltResponse对象的sendRedirect方法实现
  • 响应重定向是服务端通过302响应码和location资源路径,告诉客户端让它自己去找其他资源,属于在服务器的提示下的客户端的行为。
  • 客户端至少发送了两次请求,客户端地址栏是要变化的。
  • 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
  • 重定向可以是Servlet动态资源,也可以是其他静态资源
  • 不可以访问WEB-INF下受保护的资源(显然)
  • 可以重定向到本项目以外的资源。

参考链接

  1. https://zh.wikipedia.org/wiki/Apache_Tomcat ↩︎
  2. https://www.cnblogs.com/mamamia/p/17748456.html ↩︎
  3. https://www.cnblogs.com/Qian123/p/5345527.html#!comments ↩︎

JavaWeb开发(一)
https://kznep19.blog/2025/05/11/JavaWeb1/
作者
banyee
发布于
2025年5月11日
许可协议