【Java学习路线之JavaWeb】Servlet教程_javaweb servlet项目教程(1)

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doGet(req, resp);
}
@Override
public void destroy() {
    System.out.println(this.getServletName() + ":销毁");
}
@Override
public void init() throws ServletException {
    System.out.println(this.getServletName() + ":初始化完成");
}

}


web.xml 的配置如下。



<?xml version="1.0" encoding="UTF-8"?>



MyServlet1
net.biancheng.www.MyServlet1

0


MyServlet2
net.biancheng.www.MyServlet1

1


MyServlet3
net.biancheng.www.MyServlet1

2


MyServlet4
net.biancheng.www.MyServlet1

-1



MyServlet5
net.biancheng.www.MyServlet1


MyServlet1
/MyServlet1


MyServlet2
/MyServlet2


MyServlet3
/MyServlet3


MyServlet4
/MyServlet4


MyServlet5
/MyServlet5


启动 Tomcat 服务器,控制台输出如下图所示。


![load-on-start 1](https://img-blog.csdnimg.cn/img_convert/d57203623689438387afc937e737abde.png)


在地址栏输入“http://localhost:8080/servletDemo/MyServlet1”,访问 MyServlet1,结果如下图。


![load-on-start 1](https://img-blog.csdnimg.cn/img_convert/d57203623689438387afc937e737abde.png)


在地址栏输入“http://localhost:8080/servletDemo/MyServlet2”,访问 MyServlet2,结果如下图。


![load-on-start 1](https://img-blog.csdnimg.cn/img_convert/d57203623689438387afc937e737abde.png)


在地址栏输入“http://localhost:8080/servletDemo/MyServlet3”,访问 MyServlet3,结果如下图。


![load-on-start 1](https://img-blog.csdnimg.cn/img_convert/d57203623689438387afc937e737abde.png)


在地址栏输入“http://localhost:8080/servletDemo/MyServlet4”,访问 MyServlet4,结果如下图。


![load-on-start servlet 4](https://img-blog.csdnimg.cn/img_convert/98f7904988796fe8905d195a4dc8933d.png)


在地址栏输入“http://localhost:8080/servletDemo/MyServlet5”,访问 MyServlet5,结果如下图。


![load-on-start 5](https://img-blog.csdnimg.cn/img_convert/7909b2425d604067b5f55b1afe578ebc.png)


由示例可知:


* 由于 MyServlet1、MyServlet2 和 MyServlet3 的 load-on-startup 元素取值都大于等于 0,因此当 Tomcat 启动时,就对它们进行了初始化。
* 由于在 Servlet 的生命周期内,init() 方法(初始化方法)只能被调用一次,因此当 Tomcat 启动完成后,第一次访问 MyServlet1、MyServlet2 和 MyServlet3 时,它们不会再次被初始化。
* 由于 MyServlet4 的 load-on-startup 元素取值为负数,因此只有当第一次请求它时,才会进行初始化。
* 由于 MyServlet5 没有指定 load-on-startup 元素取值,因此只有当第一次请求它时,才会进行初始化。


## Servlet虚拟路径映射


客户端通过 URL 地址来访问 Web 服务器中的资源,Servlet 程序若想被外界访问,就必须被映射到一个 URL 地址上。很多时候,该 URL 地址和 Servlet 程序的物理路径(在硬盘上的存储位置)并不一致,因此它被称为虚拟路径。Servlet 与虚拟路径的对应关系就叫做 Servlet 虚拟路径映射。


Servlet 虚拟路径映射可以被分为 2 类:


1. 单一映射
2. 多重映射


下面介绍如何实现单一映射和多重映射。


### Servlet 单一映射


Servelt 单一映射是指一个 Servlet 只被映射到一个虚拟路径上。


Servlet 单一映射的实现方式有 2 种:


1. 使用 web.xml 实现单一映射;
2. 使用 @WebServlet 实现单一映射。


##### 1. web.xml 实现单一映射


在 web.xml 文件中,使用 和 元素实现 Servlet 单一映射,代码如下。



<?xml version="1.0" encoding="UTF-8"?>



MyServlet
net.biancheng.www.MyServlet


MyServlet
/myServlet


对以上标签说明如下:


* 元素用于注册 Servlet,即给 Servlet 起一个独一无二的名字。
* 包含两个主要的子元素 和 ,分别用于指定 Servlet 的名称和 Servlet 的完整限定名(包名+类名)。
* 元素用于定义 Servlet 与虚拟路径之间的映射。
* 包含两个子元素 和 ,分别用于指定 Servlet 的名称和虚拟路径。


##### 2. @WebServlet 实现单一映射


在 @WebServlet 注解中,一般使用 value 属性实现 Servlet 单一映射,代码如下。



package net.biancheng.www;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(“/MyServlet”)
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}


也可以使用 urlPatterns 属性实现 Servlet 单一映射,代码如下。



package net.biancheng.www;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = “/myServlet”)
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}


### Servlet 多重映射


Servlet 的多重映射是指一个 Servlet 可以被映射到多个虚拟路径上。此时,客户端可以通过多个路径实现对同一个 Servlet 的访问。


Servlet 多重映射的实现方式有 3 种:


1. 配置多个 元素。
2. 配置多个 子元素。
3. 在 @WebServlet 的 urlPatterns 属性中使用字符串数组


##### 1. 配置多个 元素


Servlet 2.5 规范之前, 元素只允许包含一个 子元素,若要实现 Servet 的多重映射,只能通过配置多个 元素实现。


以 serveltDemo 为例,在 web.xml 中配置两个 元素,代码如下所示。



<?xml version="1.0" encoding="UTF-8"?>



MyServlet
net.biancheng.www.MyServlet
1


MyServlet
/myServlet


MyServlet
/myServlet2


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/servletDemo/myServlet”,访问 MyServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/7f20ef268becaf35a590e99861892f90.png)


在地址栏中输入“http://localhost:8080/servletDemo/myServlet2”,也可以访问 MyServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/15eb4e3f7368b7aa93a728bb302e3950.png)


##### 2. 配置多个 子元素


从 Servlet 2.5 开始, 元素可以包含多个 子元素,每个 代表一个虚拟路径的映射规则。因此,通过在一个 元素中配置多个 子元素,也可以实现 Servlet 的多重映射。


以 servletDemo 为例, 元素下添加两个 子元素,代码如下。



<?xml version="1.0" encoding="UTF-8"?>



MyServlet
net.biancheng.www.MyServlet
1


MyServlet
/myServlet
/myServlet3


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/servletDemo/myServlet”,访问 MyServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/3cdcbee66c03049d26b939bd16d82405.png)


在地址栏中输入“http://localhost:8080/servletDemo/myServlet3”,也可以访问 MyServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/74d2daad65711d971461b3c0885eed77.png)


##### 3. @WebServlet 实现多重映射


Servlet 3.0 增加了对 @WebServlet 注解的支持,我们可以在 urlPatterns 属性中,以字符串数组的形式指定一组映射规则来实现 Servlet 的多重映射。


以 servletDemo 为例,在 @WebServlet 注解的 urlPatterns 属性中添加一组虚拟路径,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
urlPatterns = { “/myServlet”, “/myServlet4” })
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private int initCount = 0;
private int httpCount = 0;
private int destoryCount = 0;
@Override
public void destroy() {
destoryCount++;
super.destroy();
// 向控制台输出destory方法被调用次数
System.out.println(
“**********************************destroy方法:” + destoryCount + “*******************************”);
}
@Override
public void init() throws ServletException {
initCount++;
super.init();
// 向控制台输出init方法被调用次数
System.out.println(“init方法:” + initCount);
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
httpCount++;
// 控制台输出doGet方法次数
System.out.println(“doGet方法:” + httpCount);
// 设置返回页面格式与字符集
resp.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = resp.getWriter();
// 向页面输出
writer.write(“初始化次数:” + initCount + “
” + “处理请求次数:” + httpCount + “
” + “销毁次数:” + destoryCount);
writer.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/servletDemo/myServlet”,访问 MyServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/3cdcbee66c03049d26b939bd16d82405.png)


在地址栏中输入“http://localhost:8080/servletDemo/myServlet4”,也可以访问 MyServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/c8be98b26dd5729695d3936c02b96bec.png)


## Servlet虚拟路径匹配规则


当 Servlet 容器接收到请求后,容器会将请求的 URL 减去当前应用的上下文路径,使用剩余的字符串作为映射 URL 与 Servelt 虚拟路径进行匹配,匹配成功后将请求交给相应的 Servlet 进行处理。


以 servletDemo 为例,若 URL 为“http://localhost:8080/servletDemo/myServlet”,其应用上下文是 servletDemo,容器会将“http://localhost:8080/servletDemo”去掉,使用剩余的“/myServlet”与 Servlet 虚拟路径进行匹配。


### 匹配规则


Servlet 虚拟路径匹配规则有以下 4 种:


1. 完全路径匹配
2. 目录匹配
3. 扩展名匹配
4. 缺省匹配(默认匹配)


下面我们以 servletDemo 为例,分别介绍 4 种规则。




| 匹配规则 | 使用规则 | 虚拟路径 | 可访问的URL |
| --- | --- | --- | --- |
| 完全路径匹配 (精确匹配) | 以`/`开始,不能包含通配符`*`。 必须完全匹配 | /myServlet /user/myServlet /product/index.action | http://localhost:8080/servletDemo/myServlet http://localhost:8080/servletDemo/user/myServlet http://localhost:8080/servletDemo/product/index.action |
| 目录匹配 | 以`/`字符开头,并以`/*`结尾的字符串。 用于路径匹配 | /user/\* /\* | http://localhost:8080/servletDemo/user/aaa http://localhost:8080/servletDemo/aa |
| 扩展名匹配 | 以通配符`*.`开头的字符串。 用于扩展名匹配 | \*.do \*.action \*.jsp | http://localhost:8080/servletDemo/user.do http://localhost:8080/servletDemo/myServlet.action http://localhost:8080/servletDemo/bb.jsp |
| 缺省匹配(默认匹配) | 映射路径为`/`,表示这个 Servlet 为当前应用的缺省 Servlet 或默认 Servlet,默认处理无法匹配到虚拟路径的请求。 | / | 可以匹配任意请求 URL |


注意:目录匹配和扩展名匹配无法混合使用,即`/rest/*.do`这种写法是不正确的。


### 匹配优先级


Servlet 虚拟路径的匹配优先级顺序为:全路径匹配(精确匹配)> 目录匹配 > 扩展名匹配 > 缺省匹配(默认匹配)。


Servlet 容器会从优先级高的虚拟路径开始匹配,匹配成功后就会立刻将请求交给相应的 Servlet 进行处理,不会再关注其他虚拟路径是否匹配成功。


##### 示例 1


下面我们通过一个实例加深对 Servlet 虚拟路径匹配的理解。


在 servletDemo 的 net.biancheng.www 包下,创建名称为 VirtualPathServlet 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class VirtualPathServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = resp.getWriter();
// 向页面输出
writer.write(“本次访问的Servlet是:” + this.getServletName());
writer.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}


web.xml 的配置如下。



<?xml version="1.0" encoding="UTF-8"?>



MyServlet1
net.biancheng.www.VirtualPathServlet


MyServlet2
net.biancheng.www.VirtualPathServlet


MyServlet3
net.biancheng.www.VirtualPathServlet


MyServlet4
net.biancheng.www.VirtualPathServlet


MyServlet5
net.biancheng.www.VirtualPathServlet



MyServlet1
/hello



MyServlet2
/abc/my/



MyServlet3
/abc/



MyServlet4
*.do



MyServlet5
/


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/servletDemo/hello”,结果如下图。


![/hello](https://img-blog.csdnimg.cn/img_convert/a058c2b89e6943897acb43a4e85447e2.png)


在地址栏中输入“http://localhost:8080/servletDemo/abc/my/login”,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/b653eb015de797bf2e92c3481032e1f1.png)


在地址栏中输入“http://localhost:8080/servletDemo/abc/index.do”,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/063ef4f8a050870594683340a74e13ab.png)


在地址栏中输入“http://localhost:8080/servletDemo/hello/index.do”,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/cf6ed97c1511117533c49009c8a1dea5.png)


在地址栏中输入“http://localhost:8080/servletDemo/hello/pay.html”,结果如下图。


![/abc/index.do](https://img-blog.csdnimg.cn/img_convert/d5482ca54444808a7868f0cca34ea452.png)


### Tomcat 中的缺省 Servlet


在 Tomcat 安装目录的 \conf\web.xml 文件中,注册了一个名称为 org.apache.catalina.servlets.DefaultServlet 的 Servlet,并将它设置为缺省 Servlet。



<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Tomcat 服务器中的 Web 应用没有缺省 Servlet 时,会将 DefaultServlet 作为其缺省 Servlet。当客户端访问 Tomcat 服务器中某个静态 HTML 文件或者图片时,DefaultServlet 会判断该 HTML 或图片是否存在,若存在,则将数据以流的形式返回客户端,否则会报告 404 错误。


##### 示例 2


将 servletDemo 应用的缺省匹配删除,并在 servletDemo 应用里添加一张图片 a.jpg。



<?xml version="1.0" encoding="UTF-8"?>





启动 Tomcat,在地址栏输入“http://localhost:8080/servletDemo/a.jpg”,结果如下图。


![缺省模式](https://img-blog.csdnimg.cn/img_convert/d2de704428cd46d2a348928383a58bdf.png)


## ServletConfig接口详解


Servlet 容器初始化 Servlet 时,会为这个 Servlet 创建一个 ServletConfig 对象,并将 ServletConfig 对象作为参数传递给 Servlet 。通过 ServletConfig 对象即可获得当前 Servlet 的初始化参数信息。


一个 Web 应用中可以存在多个 ServletConfig 对象,一个 Servlet 只能对应一个 ServletConfig 对象,即 Servlet 的初始化参数仅对当前 Servlet 有效。


### 获得 ServletConfig 对象


获得 ServletConfig 对象一般有 2 种方式:


##### 1. 直接从带参的 init() 方法中提取



public class ServletConfigDemo extends HttpServlet {
private ServletConfig servletConfig;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取Servlet得名字
this.servletConfig.getServletName();
}
@Override
public void init(ServletConfig config) throws ServletException {
//从带参init方法中,提取ServletConfig对象
this.servletConfig = config;
}
}


##### 2. 调用 GenericServlet 提供的 getServletConfig() 方法获得



//调用 GenericServlet 提供的 getServletConfig 方法获得 ServletConfig 对象ServletConfig servletConfig = this.getServletConfig();


### ServletConfig 接口


javax.servlet 包提供了一个 ServletConfig 接口,该接口中提供了以下方法。




| 返回值类型 | 方法 | 功能描述 |
| --- | --- | --- |
| String | getInitParameter(String name) | 根据初始化参数名 name,返回对应的初始化参数值。 |
| Enumeration | getInitParameterNames() | 返回 Servlet 所有的初始化参数名的枚举集合,如果该 Servlet 没有初始化参数,则返回一个空的集合。 |
| ServletContext | getServletContext() | 返回一个代表当前 Web 应用的 ServletContext 对象。 |
| String | getServletName() | 返回 Servlet 的名字,即 web.xml 中 元素的值。 |


### 配置 Servlet 初始化参数


配置 Servlet 的初始化参数有 2 种方式:


1. 使用 web.xml 配置初始化参数;
2. 使用 @WebServlet 配置初始化参数。


##### 1.使用 web.xml 配置初始化参数


在 web.xml 中可以使用一个或多个 元素为 Servlet 配置初始化参数,代码如下。



<?xml version="1.0" encoding="UTF-8"?>



MyServlet
net.biancheng.www.MyServlet


name
编程帮



URL
www.biancheng.net



以上配置说明如下:


* 元素是 的子元素, 需要在 元素内使用,表示只对当前 Servlet 有效 。
* 子元素表示参数的名称。
* 子元素表示参数的值。


##### 2.使用 @WebServlet 配置初始化参数


通过 @WebServlet 的 initParams 属性也可以为 Servlet 设置初始化参数,代码如下。



package net.biancheng.www;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = {“/MyServlet”}, initParams = {@WebInitParam(name = “name”, value = “编程帮”),
@WebInitParam(name = “URL”, value = “www.biancheng.net”)})
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}


### 获取 Servlet 初始化参数


下面我们通过一个例子演示如何通过 ServletConfig 对象读取 Servlet 的初始化参数。


以 servletDemo 工程为例,在 net.biancheng.www 包下,创建名称为 ReadConfigServlet 的类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 获取Servlet的初始化参数
*
*/
@WebServlet(urlPatterns = { “/ReadConfigServlet” }, initParams = { @WebInitParam(name = “name”, value = “编程帮”),
@WebInitParam(name = “URL”, value = “www.biancheng.net”) })
public class ReadConfigServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
// 获取ServletConfig对象
ServletConfig config = getServletConfig();
// 获取servletName
String servletName = config.getServletName();
// 返回 servlet 的初始化参数的名称的集合
Enumeration initParameterNames = config.getInitParameterNames();
// 遍历集合获取初始化参数名称
while (initParameterNames.hasMoreElements()) {
// 获取初始化参数名称
String initParamName = initParameterNames.nextElement();
// 获取相应的初始参数的值
String initParamValue = config.getInitParameter(initParamName);
// 向页面输出
writer.write(initParamName + " : " + initParamValue + “
”);
}
// 关闭流
writer.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


启动 Tomcat 服务器,在地址栏输入“http://localhost:8080/servletDemo/ReadConfigServlet”,访问 ReadConfigServlet,结果如下图。


![ServletConfig](https://img-blog.csdnimg.cn/img_convert/6f27d21a1f466a1b62c9a6004adc06fd.png)


## ServletContext接口详解


Servlet 容器启动时,会为每个 Web 应用(webapps 下的每个目录都是一个 Web 应用)创建一个唯一的 ServletContext 对象,该对象一般被称为“Servlet 上下文”。


ServletContext 对象的生命周期从 Servlet 容器启动时开始,到容器关闭或应用被卸载时结束。


Web 应用中的所有 Servlet 共享同一个 ServletContext 对象,不同 Servlet 之间可以通过 ServletContext 对象实现数据通讯,因此 ServletContext 对象也被称为 Context 域对象。



> 
> 域对象是服务器在内存上创建的存储空间,该空间用于不同动态资源(例如 Servlet、JSP)之间传递与共享数据。
> 
> 
> 


### 获得 ServletContext 对象


获得 ServletContext 对象有以下 4 种方式:


##### 1. 通过 GenericServlet 提供的 getServletContext() 方法



//通过 GenericServlet的getServletContext方法获取ServletContext对象ServletContext servletContext = this.getServletContext();


##### 2. 通过 ServletConfig 提供的 getServletContext() 方法



//通过 ServletConfig的 getServletContext方法获取ServletContext对象ServletContext servletContext = this.getServletConfig().getServletContext();


##### 3. 通过 HttpSession 提供的 getServletContext() 方法



//通过 HttpSession的 getServletContext方法获取ServletContext对象ServletContext servletContext = req.getSession().getServletContext();


##### 4. 通过 HttpServletRequest 提供的 getServletContext() 方法



//通过 HttpServletRequest的 getServletContext方法获取ServletContext对象ServletContext servletContext = req.getServletContext();


注意:以上最后两种方法了解即可,后面我们会详细讲解。


### ServletContext 的应用


javax.servlet 包提供了一个 ServletContext 接口,该接口定义了一组方法,Servlet 可以使用这些方法与容器进行通信。


ServletContext 的应用主要有以下 3 个:


1. 获取上下文初始化参数
2. 实现 Servlet 之间的数据通讯
3. 读取 Web 应用下的资源文件


#### 1. 获取上下文初始化参数


使用 ServletContext 对象获取 Web 应用的上下文初始化参数,分为 2 步:


1. 设置上下文初始化参数
2. 调用接口中方法获取初始化参数


##### 1) 设置上下文初始化参数


通过 web.xml 中的 元素可以为 Web 应用设置一些全局的初始化参数,这些参数被称为上下文初始化参数。


与 Servlet 的初始化参数不同,应用中的所有 Servlet 都共享同一个上下文初始化参数。在 Web 应用的整个生命周期中,上下文初始化参数会一直存在,并且可以随时被任意一个 Servlet 访问。


在 web.xml 文件中配置上下文初始化参数,代码如下所示。



<?xml version="1.0" encoding="UTF-8"?>




name
编程帮


url
www.biancheng.net


对以上标签说明如下:


* 元素用来声明上下文初始化参数,必须在根元素 内使用。
* 子元素表示参数名,参数名在整个 Web 应用中必须是唯一的。
* 子元素表示参数值。


##### 2) 调用接口中方法获取初始化参数


Servlet 容器启动时,会为容器内每个 Web 应用创建一个 ServletContext 对象,并将 元素中的上下文初始化参数以键值对的形式存入该对象中,因此我们可以通过 ServletContext 的相关方法获取到这些初始化参数。


下表列举了 ServletContext 接口中用于获取上下文初始化参数的相关方法。




| 返回值类型 | 方法 | 描述 |
| --- | --- | --- |
| String | getInitParameter(String name) | 根据初始化参数名 name,返回对应的初始化参数值。 |
| Enumeration | getInitParameterNames() | 返回 Web 应用所有上下文初始化参数名的枚举集合,如果该 Web 应用没有上下文初始化参数,则返回一个空的枚举集合。 |


##### 例 1


以 servletDemo 为例,在 net.biancheng.www 包下创建一个名称为 ReadContextServlet 的类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(“/ReadContextServlet”)
public class ReadContextServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
// 调用httpServlet父类GenericServlet的getServletContext方法获取ServletContext对象
ServletContext context = super.getServletContext();
// 返回 context 上下文初始化参数的名称
Enumeration initParameterNames = context.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
// 获取初始化参数名称
String initParamName = initParameterNames.nextElement();
// 获取相应的初始参数的值
String initParamValue = context.getInitParameter(initParamName);
// 向页面输出
writer.write(initParamName + " : " + initParamValue + “
”);
}
// 关闭流
writer.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


启动 Tomcat 服务器,在地址栏输入“http://localhost:8080/servletDemo/ReadContextServlet”,访问 ReadContextServlet,结果如下图。


![ServletContext 1](https://img-blog.csdnimg.cn/img_convert/fb04a8900bb4be546de71ad4cf931645.png)


#### 2. 实现数据通讯


在 Servlet 中,调用 ServletContext 接口的 setAttribute() 方法可以创建一些属性,这些属性被存放在 ServletContext 对象中。应用中所有 Servlet 都可以对这些属性进行访问和操作,通过它们可以实现应用内不同 Servlet 之间的数据通讯。


##### 数据通讯的相关方法


下表列举了 ServletContext 接口实现数据通讯的相关方法。




| 返回值类型 | 方法 | 描述 |
| --- | --- | --- |
| void | setAttribute(String name, Object object) | 把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 ServletContext 中。 参数 name 为属性名,参数 object 为属性值。 |
| void | removeAttribute(String name) | 从 ServletContext 中移除属性名为 name 的属性。 |
| Object | getAttribute(String name) | 根据指定的属性名 name,返回 ServletContext 中对应的属性值。 |


##### ServletContext 属性与上下文初始化参数对比


虽然 ServletContext 的属性与上下文初始化参数都是存放在 ServletContext 对象中,但它们是不同的。




| 不同点 | ServletContext 的属性 | 上下文初始化参数 |
| --- | --- | --- |
| 创建方式 | ServletContext 的属性通过调用 ServletContext 接口的 setAttribute() 方法创建 | 上下文初始化参数通过 web.xml 使用 元素配置 |
| 可进行的操作 | ServletContext 的属性可以通过 ServletContext 接口的方法进行读取、新增、修改、移除等操作 | 上下文初始化参数在容器启动后只能被读取,不能进行新增、修改和移除操作 |
| 生命周期 | ServletContext 中属性的生命周期从创建开始,到该属性被移除(remove)或者容器关闭结束 | 上下文初始化参数的生命周期,从容器启动开始,到 Web 应用被卸载或容器关闭结束 |
| 作用 | 使用 ServletContext 中的属性可以实现 Servlet 之间的数据通讯 | 使用上下文初始化参数无法实现数据通讯 |


##### 例 2


我们通过编写一个统计页面访问量的案例,来演示如何通过 ServletContext 对象实现数据通讯。


在 servletDemo 的 net.biancheng.www 包下,创建一个名称为 CountServlet 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 使用ServletContext 统计访问次数
*
*/
@WebServlet(“/CountServlet”)
public class CountServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void init() throws ServletException {
// 获取ServletContext对象
ServletContext context = getServletContext();
// 初始化时,向ServletContext中设置count属性,初始值为0
context.setAttribute(“count”, 0);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 调用httpServlet父类GenericServlet的getServletContext方法获取ServletContext对象
ServletContext context = super.getServletContext();
// 获取count的值,自增
Integer count = (Integer) context.getAttribute(“count”);
// 存入到域对象中
context.setAttribute(“count”, ++count);
// 向页面输出内容
response.setContentType(“text/html;charset=UTF-8”);
response.getWriter().write(“

编程帮 www.biancheng.net 欢迎您

”);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


然后再创建一个名称为 ShowServlet 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 使用ServletContext展示网站的访问次数
*
*/
@WebServlet(“/ShowServlet”)
public class ShowServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取ServletContext中存放的count属性(即页面的访问次数)
Integer count = (Integer) getServletContext().getAttribute(“count”);
// 向页面输出
response.setContentType(“text/html;charset=UTF-8”);
// 若CountServlet已被访问
if (count != null) {
response.getWriter().write(“

该网站一共被访问了” + count + “次

”);
} else {
// 若CountServlet未被访问,提示先访问CountServlet
response.getWriter().write(“

请先访问 CountServlet

”);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


重启 Tomcat 浏览器,在地址栏输入“http://localhost:8080/servletDemo/CountServlet”,多次访问 CountServlet 次,结果如下图。


![ServletContext 2](https://img-blog.csdnimg.cn/img_convert/44a271c817c49ff0b5d0a2b0c1af071f.png)


然后再输入“http://localhost:8080/servletDemo/ShowServlet”,访问 ShowServlet,结果如下图。


![ServletContext 3](https://img-blog.csdnimg.cn/img_convert/872be9f498ee0ddaa07a92698548d39e.png)


#### 3. 读取 Web 应用下的资源文件


在实际开发中,有时会需要读取 Web 应用中的一些资源文件,如配置文件和日志文件等。为此,ServletContext 接口定义了一些读取 Web 资源的方法 ,如下表。




| 返回值类型 | 方法 | 方法描述 |
| --- | --- | --- |
| Set | getResourcePaths(String path) | 返回一个 Set 集合,该集合中包含资源目录中的子目录和文件的名称。 |
| String | getRealPath(String path) | 返回资源文件的真实路径(文件的绝对路径)。 |
| URL | getResource(String path) | 返回映射到资源文件的 URL 对象。 |
| InputStream | getResourceAsStream(String path) | 返回映射到资源文件的 InputStream 输入流对象。 |


注:上表中参数 path 代表资源文件的虚拟路径,它以正斜线`/`开始,`/`表示当前 Web 应用的根目录。


##### 例 3


下面我们通过一个例子演示如何使用 ServletContext 对象读取资源文件。


在 servletDemo 的 src 目录中,创建一个名称为 db.properties 的文件,文件中输入如下所示的配置信息。



name=编程帮
url=www.biancheng.net
desc=编程帮,欢迎你


在 net.biancheng.www 包中,创建一个名称为 ReadServlet 的 Servlet 类,代码如下所示。



package net.biancheng.www;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 使用ServletContext肚读取资源文件
*
*/
@WebServlet(“/ReadServlet”)
public class ReadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
// 获取相对路径中的输入流对象
InputStream ins = getServletContext().getResourceAsStream(“/WEB-INF/classes/db.properties”);
// 获取输入流
Properties pro = new Properties();
// 加载
pro.load(ins);
// 获取文件中的内容
String name = pro.getProperty(“name”);
String url = pro.getProperty(“url”);
String desc = pro.getProperty(“desc”);
writer.write(“用户名:” + name + “
” + “地址:” + url + “
” + “描述:” + desc + “
”);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/servletDemo/ReadServlet”,访问 ReadServlet,结果如下图。


![ServletContext read](https://img-blog.csdnimg.cn/img_convert/05b1f97b0ba4c7596ac259974d7b3f6a.png)


## HttpServletRequest接口详解


一般情况下,浏览器(客户端)通过 HTTP 协议来访问服务器的资源,Servlet 主要用来处理 HTTP 请求。


Servlet 处理 HTTP 请求的流程如下:


![Servlet 处理HTTP请求](https://img-blog.csdnimg.cn/img_convert/f48f6d3ac87b6294679274a18713681b.png)


1. Servlet 容器接收到来自客户端的 HTTP 请求后,容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletReponse 对象。
2. 容器将 HttpServletRequest 对象和 HttpServletReponse 对象以参数的形式传入 service() 方法内,并调用该方法。
3. 在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息。
4. 对 HTTP 请求进行处理。
5. 请求处理完成后,将响应信息封装到 HttpServletReponse 对象中。
6. Servlet 容器将响应信息返回给客户端。
7. 当 Servlet 容器将响应信息返回给客户端后,HttpServletRequest 对象和 HttpServletReponse 对象被销毁。


通过以上流程可以看出, HttpServletRequest 和 HttpServletReponse 是 Servlet 处理 HTTP 请求流程中最重要的两个对象。HttpServletRequest 对象用于封装 HTTP 请求信息,HttpServletReponse 对象用于封装 HTTP 响应信息。


下面我们将对 HttpServletRequest 进行详细介绍。


### HttpServletRequest 接口


在 Servlet API 中,定义了一个 HttpServletRequest 接口,它继承自 ServletRequest 接口。HttpServletRequest 对象专门用于封装 HTTP 请求消息,简称 request 对象。


HTTP 请求消息分为请求行、请求消息头和请求消息体三部分,所以 HttpServletRequest 接口中定义了获取请求行、请求头和请求消息体的相关方法。


##### 获取请求行信息


HTTP 请求的请求行中包含请求方法、请求资源名、请求路径等信息,HttpServletRequest 接口定义了一系列获取请求行信息的方法,如下表。




| 返回值类型 | 方法声明 | 描述 |
| --- | --- | --- |
| String | getMethod() | 该方法用于获取 HTTP 请求方式(如 GET、POST 等)。 |
| String | getRequestURI() | 该方法用于获取请求行中的资源名称部分,即位于 URL 的主机和端口之后,参数部分之前的部分。 |
| String | getQueryString() | 该方法用于获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。 |
| String | getContextPath() | 返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""。 |
| String | getServletPath() | 该方法用于获取 Servlet 所映射的路径。 |
| String | getRemoteAddr() | 该方法用于获取客户端的 IP 地址。 |
| String | getRemoteHost() | 该方法用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址。 |


##### 示例 1


为了更好地理解这些方法,下面通过案例演示这些方法的使用。


创建一个名称为 httpServletRequestDemo 的项目,在 net.biancheng.www 包中创建一个名为 RequestLine 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(“/RequestLine”)
public class RequestLine extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
writer.println(“请求方式:” + request.getMethod() + “
” +
“客户端的 IP 地址:” + request.getRemoteAddr() + “
” +
“应用名字(上下文):” + request.getContextPath() + “
” +
“URI:” + request.getRequestURI() + “
” +
“请求字符串:” + request.getQueryString() + “
” +
“Servlet所映射的路径:” + request.getServletPath() + “
” +
“客户端的完整主机名:” + request.getRemoteHost() + “

);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/httpServletRequestDemo/RequestLine?a=1&b=2”,访问 RequestLine,结果如下图。


![requestLine](https://img-blog.csdnimg.cn/img_convert/5f5ce9f3c75663be4d8738bf59ef2fe2.png)


##### 获取请求头信息


当浏览器发送请求时,需要通过请求头向服务器传递一些附加信息,例如客户端可以接收的数据类型、压缩方式、语言等。为了获取请求头中的信息, HttpServletRequest 接口定义了一系列用于获取 HTTP 请求头字段的方法,如下表所示。




| 返回值类型 | 方法声明 | 描述 |
| --- | --- | --- |
| String | getHeader(String name) | 该方法用于获取一个指定头字段的值。 如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。 |
| Enumeration | getHeaders(String name) | 该方法返回指定头字段的所有值的枚举集合, 在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。 |
| Enumeration | getHeaderNames() | 该方法返回请求头中所有头字段的枚举集合。 |
| String | getContentType() | 该方法用于获取 Content-Type 头字段的值。 |
| int | getContentLength() | 该方法用于获取 Content-Length 头字段的值 。 |
| String | getCharacterEncoding() | 该方法用于返回请求消息的字符集编码 。 |


##### 示例 2


为了更好地理解这些方法,下面通过案例演示这些方法的使用。


在 net.biancheng.www 包中创建一个名为 RequestHeader 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(“/RequestHeader”)
public class RequestHeader extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
//获得所有请求头字段的枚举集合
Enumeration headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
//获得请求头字段的值
String value = request.getHeader(headers.nextElement());
writer.write(headers.nextElement() + “:” + value + “
”);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/httpServletRequestDemo/RequestHeader”,访问 RequestHeader ,结果如下图。


![requestHeader](https://img-blog.csdnimg.cn/img_convert/26fb67c5443df9ecf65045b3c129c1ec.png)


##### 获取 form 表单的数据


在实际开发中,我们经常需要获取用户提交的表单数据,例如用户名和密码等。为了方便获取表单中的请求参数,ServletRequest 定义了一系列获取请求参数的方法,如下表所示。




| 返回值类型 | 方法声明 | 功能描述 |
| --- | --- | --- |
| String | getParameter(String name) | 返回指定参数名的参数值。 |
| String [ ] | getParameterValues (String name) | 以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。 |
| Enumeration | getParameterNames() | 以枚举集合的形式返回请求中所有参数名。 |
| Map | getParameterMap() | 用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。 |


##### 示例 3


为了更好地理解这些方法,下面通过案例演示这些方法的使用。


在 httpServletRequestDemo 的 WebContent 目录下,创建 form.html,代码如下。



Insert title here
编程帮wwww.biancheng.net
输入姓名
输入密码
选择性别男 女
选择使用的语言JAVA C语言 PHP Python
选择城市 --请选择-- 北京 上海 广州

在 net.biancheng.www 包中,创建一个名为 RequestParam 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(“/RequestParam”)
public class RequestParam extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
// 获取内容,做其他操作
// 获取姓名
String username = request.getParameter(“username”);
// 获取密码
String password = request.getParameter(“password”);
// 获取性别
String sex = request.getParameter(“sex”);
// 获取城市
String city = request.getParameter(“city”);
// 获取语言
String[] languages = request.getParameterValues(“language”);
writer.write(“用户名:” + username + “
” + “密码:” + password + “
” + “性别:” + sex + “
” + “城市:” + city
+ “
” + “使用过的语言:” + Arrays.toString(languages) + “
”);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/httpServletRequestDemo/form.html”,即可访问 form.html,结果如下图。


![form 图](https://img-blog.csdnimg.cn/img_convert/70d7bf6f387c93dcc1d96142e8fa2d2f.png)


在表单中填写信息后,点击提交,结果如下图。


![表单参数](https://img-blog.csdnimg.cn/img_convert/6936eb6242f25d28fc99d09a1cffa51b.png)


### 中文乱码问题


如果在 form 表单输入框中输入中文,例如在输入姓名时,填写“编程帮默认用户”,点击提交后就会出现乱码问题,如下图所示。


![img](https://img-blog.csdnimg.cn/img_convert/5dc36d26ed52c151f115ef1ebc523b21.png)


根据请求方式的不同,请求一般可以被分为两种:GET 请求和 POST 请求。这两种请求方式都可能会产生中文乱码问题,下面我们分别对它们产生乱码的原因及其解决方案进行介绍。


##### POST 请求


乱码的原因:POST 提交的数据在请求体中,其所使用的编码格式时页面一致(即 utf-8)。request 对象接收到数据之后,会将数据放到 request 缓冲区,缓冲区的默认字符集是 ISO-8859-1(该字符集不支持中文),两者使用的字符集不一致导致乱码。


解决方案:在获取请求参数之前设置 request 缓冲区字符集为 utf-8 ,代码如下。



//修改request缓冲区的字符集为UTF-8
request.setCharacterEncoding(“utf-8”);
// 获取用户名
String username = request.getParameter(“username”);


##### GET 请求


乱码的原因:Get 请求将请求数据附加到 URL 后面作为参数,浏览器发送文字时采用的编码格式与页面编码保持一致(utf-8)。如果 Tomcat 没有设置字符集,接收 URL 时默认使用 ISO-8859-1 进行解码,ISO-8859-1 不兼容中文,无法正确解码,导致出现乱码。



> 
> 需要注意的是,在 Tomcat 8 中已解决了 get 方式提交请求中文乱码的问题,使用 Tomcat 8 及以上版本的同学不必再考虑此问题了,如果您使用的是 Tomcat 7 或更早的版本,出现乱码问题可以使用如下的方案解决。
> 
> 
> 


解决方案:解决 GET 请求中文乱码问题,有以下 3 种解决方案。


\1. 修改 tomcat/conf/server.xml 中的配置,代码如下。




\2. 使用 URLEncoder 和 URLDecoder 进行编码和解码的操作(逆向编解码)。



//得到TOMCAT通过ISO8859-1解码的字符串
String username = request.getParameter(“username”);
//对字符串使用ISO8859-1进行编码,得到最初浏览器使用UTF-8编码的字符串
username = URLEncoder.encode(username, “ISO8859-1”);
//将使用UTF-8编码的字符串使用UTF-8进行解码,得到正确的字符串
username = URLDecoder.decode(username, “UTF-8”);


\3. 使用 String 的构造方法:String(byte[] bytes, String charset) ,对字节数组(bytes)按照指定的字符集(charset)进行解码,返回解码后的字符串,解决乱码问题(推荐使用)。



//获取姓名
String username = request.getParameter(“username”);
//使用String的构造方法解决乱码的问题
username = new String(username.getBytes(“ISO-8859-1”),“UTF-8”);


## Servlet请求转发


Web 应用在处理客户端的请求时,经常需要多个 Web 资源共同协作才能生成响应结果。但由于 Serlvet 对象无法直接调用其他 Servlet 的 service() 方法,所以 Servlet 规范提供了 2 种解决方案:


1. 请求转发
2. 请求包含(了解即可)


下面我们主要对请求转发进行介绍。


### 请求转发


请求转发属于服务器行为。容器接收请求后,Servlet 会先对请求做一些预处理,然后将请求传递给其他 Web 资源,来完成包括生成响应在内的后续工作。


##### RequestDispatcher 接口


javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。


Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:


1. 调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
2. 调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。



> 
> 绝对路径是指以符号“/”开头的路径,“/”表示当前 Web 应用的根目录。相对路径是指相对当前 Web 资源的路径,不以符号“/”开头。
> 
> 
> 


RequestDispatcher 接口中提供了以下方法。




| 返回值类型 | 方法 | 功能描述 |
| --- | --- | --- |
| void | forward(ServletRequest request,ServletResponse response) | 用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常 |
| void | include(ServletRequest request,ServletResponse response) | 用于将其他的资源作为当前响应内容包含进来 |


##### 请求转发的工作原理


在 Servlet 中,通常使用 forward() 方法将当前请求转发给其他的 Web 资源进行处理。请求转发的工作原理如下图所示。


![请求转发流程图](https://img-blog.csdnimg.cn/img_convert/4fdcb6df267a4a711f59c9fba778d06e.png)


##### 请求转发的特点


请求转发具有以下特点:


1. 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
2. 请求转发之后,浏览器地址栏中的 URL 不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
3. 参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象。
4. 由于 forward() 方法会先清空 response 缓冲区,因此只有转发到最后一个 Web 资源时,生成的响应才会被发送到客户端。


### request 域对象


request 是 Servlet 的三大域对象之一,它需要与请求转发配合使用,才可以实现动态资源间的数据传递。


在 ServletRequest 接口中定义了一系列操作属性的方法,如下表。




| 返回值类型 | 方法 | 描述 |
| --- | --- | --- |
| void | setAttribute(String name, Object o) | 将 Java 对象与属性名绑定,并将它作为一个属性存放到 request 对象中。参数 name 为属性名,参数 object 为属性值。 |
| Object | getAttribute(String name) | 根据属性名 name,返回 request 中对应的属性值。 |
| void | removeAttribute(String name) | 用于移除 request 对象中指定的属性。 |
| Enumeration | getAttributeNames() | 用于返回 request 对象中的所有属性名的枚举集合。 |


Context 域对象和 request 域对象对比,具有以下 4 点差异:


##### 1) 生命周期不同


Context 域对象的生命周期从容器启动开始,到容器关闭或者 Web 应用被移除时结束;


request 域对象的生命周期从客户端向容器发送请求开始,到对这次请求做出响应后结束。


##### 2) 作用域不同


Context 域对象对整个 Web 应用内的所有Servlet都有效;


request 域对象只对本次请求涉及的 Servlet 有效。


##### 3) Web 应用中数量不同


整个 Web 应用中只有一个 Context 域对象;


由于 Servlet 能处理多个请求,因此 Web 应用中的每个 Servlet 实例都可以有多个 request 域对象。


##### 4) 实现数据共享的方式不同


Context 域对象可以独立完成动态资源之间的数据共享;


Request 域对象需要与请求转发配合使用才能实现动态资源之间的数据共享。


### 示例


下面我们通过一个案例加深大家对转发和 request 域对象的理解。


在 httpServletRequestDemo 的 net.biancheng.www 包中,创建一个名为 DispatcherServlet 的类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
*请求转发
*/
@WebServlet(“/DispatcherServlet”)
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置向页面输出内容格式
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
// 尝试在请求转发前向response缓冲区写入内容,最后在页面查看是否展示
writer.write(“

这是转发前在响应信息内的内容!

”);
// 向reuqest域对象中添加属性,传递给下一个web资源
request.setAttribute(“webName”, “C语言中文网”);
request.setAttribute(“url”, “www.biancheng.net”);
request.setAttribute(“welcome”, “C语言中文网,欢迎你”);
// 转发
request.getRequestDispatcher(“/DoServlet”).forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


然后,再创建一个名称为 DoServlet 的类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net
* 请求转发
*
*/
@WebServlet(“/DoServlet”)
public class DoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置向页面输出内容格式
response.setContentType(“text/html;charset=UTF-8”);
PrintWriter writer = response.getWriter();
String webName = (String) request.getAttribute(“webName”);
String url = (String) request.getAttribute(“url”);
String welcome = (String) request.getAttribute(“welcome”);
if (webName != null) {
writer.write(“

” + webName + “

”);
}
if (url != null) {
writer.write(“

” + url + “

”);
}
if (welcome != null) {
writer.write(“

” + welcome + “

”);
}
String username = request.getParameter(“username”);
// 获取密码
String password = request.getParameter(“password”);
// 获取性别
String sex = request.getParameter(“sex”);
// 获取城市
String city = request.getParameter(“city”);
// 获取使用语言返回是String数组
String[] languages = request.getParameterValues(“language”);
writer.write(“用户名:” + username + “
” + “密码:” + password + “
” + “性别:” + sex + “
” + “城市:” + city
+ “
” + “使用过的语言:” + Arrays.toString(languages) + “

);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}


在 WebContent 根目录下,创建 login.html,代码如下。



Insert title here
编程帮wwww.biancheng.net
输入姓名
输入密码
选择性别男 女
选择使用的语言JAVA C语言 PHP Python
选择城市 --请选择-- 北京 上海 广州

启动 Tomcat 服务器,在地址栏输入“http://localhost:8080/httpServletRequestDemo/login.html”,访问 login.html,结果如下图。


![请求转发login](https://img-blog.csdnimg.cn/img_convert/06f22b21f5b92704e42d6f87b3a10c1b.png)


填写表单信息,点击提交,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/f3ed6e4d302619866bf61d85a47cf0a4.png)


## HttpServletResponse接口详解


在 Servlet API 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应消息,简称 response 对象。


Servlet 容器会针对每次请求创建一个 response 对象,并把它作为参数传递给 Servlet 的 service 方法。Servlet 处理请求后,会将响应信息封装到 response 对象中,并由容器解析后返回给客户端。


由于 HTTP 响应消息由响应行、响应头、消息体三部分组成,所以 HttpServletResponse 接口中定义了向客户端发送响应状态码、响应头、响应体的方法,下面我们将针对这些方法进行介绍。


### 响应行相关的方法


当 Servlet 返回响应消息时,需要在响应消息中设置状态码。因此,HttpServletResponse 接口定义了发送状态码的方法,如下表。




| 返回值类型 | 方法 | 描述 |
| --- | --- | --- |
| void | setStatus(int status) | 用于设置 HTTP 响应消息的状态码,并生成响应状态行。 |
| void | sendError(int sc) | 用于发送表示错误信息的状态码。 |


### 响应头相关的方法


HttpServletResponse 接口中定义了一系列设置 HTTP 响应头字段的方法,如下表所示。




| 返回值类型 | 方法 | 描述 |
| --- | --- | --- |
| void | addHeader(String name,String value) | 用于增加响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。 |
| void | setHeader (String name,String value) | 用于设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。 |
| void | addIntHeader(String name,int value) | 用于增加值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。 |
| void | setIntHeader(String name, int value) | 用于设置值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。 |
| void | setContentType(String type) | 用于设置 Servlet 输出内容的 MIME 类型以及编码格式。 |
| void | setCharacterEncoding(String charset) | 用于设置输出内容使用的字符编码。 |


### 响应体相关的方法


由于在 HTTP 响应消息中,大量的数据都是通过响应消息体传递的。因此 ServletResponse 遵循以 I/O 流传递大量数据的设计理念,在发送响应消息体时,定义了两个与输出流相关的方法。




| 返回值类型 | 方法 | 描述 |
| --- | --- | --- |
| ServletOutputStream | getOutputStream() | 用于获取字节输出流对象。 |
| PrintWriter | getWriter() | 用于获取字符输出流对象。 |



> 
> 注意:getOutputStream() 和 getWriter() 方法互相排斥,不可同时使用,否则会发生 IllegalStateException 异常。
> 
> 
> 


### 示例


创建一个名称为 responseDemo 的项目,并在 net.biancheng.www 包下创建名称为 OutServlet 的 Servlet 类,代码如下。



package net.biancheng.www;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author 编程帮 www.biancheng.net 使用response对象向页面输出内容
*
*/
@WebServlet(“/OutServlet”)
public class OutServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
run1(response);
// run2(response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
// 使用字符流向页面输出
public void run2(HttpServletResponse response) throws IOException {
response.getWriter().write(“编程帮 www.biancheng.net”);
}
// 使用字节流向页面输出
// 使用字节流向页面输出
public void run1(HttpServletResponse response) throws IOException {
// 设置浏览器打开文件时编码
response.setHeader(“Content-Type”, “text/html;charset=UTF-8”);
// 获取字节输出流
OutputStream os = response.getOutputStream();
byte[] str = “编程帮 www.biancheng.net”.getBytes(“UTF-8”);
// 输出中文
os.write(str);
}
}


启动 Tomat 服务器,在客户端浏览器地址栏输入“http://localhost:8080/responseDemo/OutServlet”访问 OutServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/45875e73960864697ab7bb237815f039.png)


在 doGet() 方法中调用 run2() 方法,使用字符流输出响应内容到浏览器,再次访问 OutServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/c6a5f564ded6b8b24f741c8d504a9e48.png)


### response 中文乱码问题


response 对象向页面输出时有两种方式:字节流、字符流,这两种方式输出中文时都有可能出现乱码。下面我们针对这两种方式出现乱码的原因以及解决方案进行介绍。


#### 使用字节流输出中文



ServletOutputStream outptuStream = response.getOutputStream();
outputStream.write(“编程帮 www.biancheng.net”.getBytes());


使用字节流向页面输出中文是否会出现乱码问题?不一定。


##### 乱码原因:


字节流输出中文是否出现乱码,取决于中文转成字节数组时与浏览器打开时采用的字符集是否一致。若两者保持一致,则不会出现乱码问题,若不一致就会出现乱码问题。


##### 解决方案:


将中文转成字节数组时和浏览器默认采用的字符集保持一致即可,代码如下。



// 设置response缓冲区的编码
response.setCharacterEncoding(“UTF-8”);
// 设置浏览器打开文件所采用的编码
response.setHeader(“Content-Type”, “text/html;charset=UTF-8”);
// 输出中文
response.getWriter().write(“编程帮 www.biancheng.net”);


#### 使用字符流输出中文


使用字符流向页面输出中文是否会出现乱码问题?一定乱码。


##### 乱码原因:


通过字符流输出的内容是存放在 response 缓冲区的,response 缓冲区的默认字符集是 ISO-8859-1,该字符集不支持中文。


##### 解决方案:


将 response 缓冲区和浏览器采用的字符集保持一致即可,有如下 2 种的方式。


第一种方式:



// 设置response缓冲区的编码
response.setCharacterEncoding(“UTF-8”);

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

ttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
// 使用字符流向页面输出
public void run2(HttpServletResponse response) throws IOException {
response.getWriter().write(“编程帮 www.biancheng.net”);
}
// 使用字节流向页面输出
// 使用字节流向页面输出
public void run1(HttpServletResponse response) throws IOException {
// 设置浏览器打开文件时编码
response.setHeader(“Content-Type”, “text/html;charset=UTF-8”);
// 获取字节输出流
OutputStream os = response.getOutputStream();
byte[] str = “编程帮 www.biancheng.net”.getBytes(“UTF-8”);
// 输出中文
os.write(str);
}
}


启动 Tomat 服务器,在客户端浏览器地址栏输入“http://localhost:8080/responseDemo/OutServlet”访问 OutServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/45875e73960864697ab7bb237815f039.png)


在 doGet() 方法中调用 run2() 方法,使用字符流输出响应内容到浏览器,再次访问 OutServlet,结果如下图。


![img](https://img-blog.csdnimg.cn/img_convert/c6a5f564ded6b8b24f741c8d504a9e48.png)


### response 中文乱码问题


response 对象向页面输出时有两种方式:字节流、字符流,这两种方式输出中文时都有可能出现乱码。下面我们针对这两种方式出现乱码的原因以及解决方案进行介绍。


#### 使用字节流输出中文



ServletOutputStream outptuStream = response.getOutputStream();
outputStream.write(“编程帮 www.biancheng.net”.getBytes());


使用字节流向页面输出中文是否会出现乱码问题?不一定。


##### 乱码原因:


字节流输出中文是否出现乱码,取决于中文转成字节数组时与浏览器打开时采用的字符集是否一致。若两者保持一致,则不会出现乱码问题,若不一致就会出现乱码问题。


##### 解决方案:


将中文转成字节数组时和浏览器默认采用的字符集保持一致即可,代码如下。



// 设置response缓冲区的编码
response.setCharacterEncoding(“UTF-8”);
// 设置浏览器打开文件所采用的编码
response.setHeader(“Content-Type”, “text/html;charset=UTF-8”);
// 输出中文
response.getWriter().write(“编程帮 www.biancheng.net”);


#### 使用字符流输出中文


使用字符流向页面输出中文是否会出现乱码问题?一定乱码。


##### 乱码原因:


通过字符流输出的内容是存放在 response 缓冲区的,response 缓冲区的默认字符集是 ISO-8859-1,该字符集不支持中文。


##### 解决方案:


将 response 缓冲区和浏览器采用的字符集保持一致即可,有如下 2 种的方式。


第一种方式:



// 设置response缓冲区的编码
response.setCharacterEncoding(“UTF-8”);

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

[外链图片转存中…(img-3OHB936e-1714508664840)]

[外链图片转存中…(img-flvOzhUA-1714508664841)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值