1、Servlet简介

Servlet是Sun公司开发动态Web的一门技术。Sun公司在这些API中提供了一个Servlet接口,如果你想开发一个Servlet程序只需要完成如下两个步骤:

  • 编写一个Java类实现Servlet接口。
  • 把开发好的Java类部署到Web服务器中。

我们把实现了Servlet接口的Java程序叫做Servlet

注:

  • 一个Servlet就是一个实现了Servlet接口的Java类。
  • 一个Servlet对应一个URL。

2、父项目与子项目

关于父子项目的理解:

(1)父项目的pom.xml配置文件中存在<modules>……</modules>标签,记录了子项目的信息:

1
2
3
<modules>
<module>servlet-01</module>
</modules>

(2)相对应的,子项目(即:Module模块)中存在<parent>……</parent>标签,记录了父项目的信息:

1
2
3
4
5
<parent>
<artifactId>javaWeb-Servlet</artifactId>
<groupId>com.atangbiji</groupId>
<version>1.0-SNAPSHOT</version>
</parent>

(3)父项目中的依赖包,子项目可以直接使用。(类似于Java中的继承)

这样我们把所有项目的依赖都添加到父项目的pom.xml配置文件中后,那么所有的子项目就不用每次再重复导入依赖包了。

(4)Servlet接口在子项目中实现。

3、Hello Servlet

3.1、创建父项目

(1)创建一个普通的Maven项目(具体步骤参考《Maven详解》),项目名称为javaWeb-Servlet

(2)删除项目中的src目录,所得到的空项目就是我们的Maven父项目,我们可以在其中创建多个子项目(即:Module)。

主项目

(3)在父项目的pom.xml文件中导入父/子工程需要的依赖,根据需求查询有哪些依赖需要导入。

Servlet项目为例,需要在父项目的pom.xml文件中导入如下依赖:

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>

注:若在父项目的pom.xml文件中已经添加了某个依赖后,则子项目的pom.xml文件中不要再重复添加该依赖了,否则Maven项目编译会出错。

至此,父项目创建完毕。

3.2、创建子项目

(1)在父项目中新建一个模块(子项目)。

新建模块

(2)使用Maven模板创建一个MavenWeb子项目。

新建MavenWeb子项目

(3)填写子项目名称和Maven项目GAV。

填写子项目信息

(4)配置子项目Maven。点击Finish按钮完成子项目创建,等待Maven依赖包导入完毕。

配置Maven

(5)由于使用Maven模板创建的MavenWeb项目的版本较低(“web-app_2_3”)。为了避免以后出现兼容性问题,建议我们先将该子项目中的web.xml文件替换成“web-app_4_0”版本。具体可以参考Tomcat安装目录下的(D:\Environment\apache-tomcat-9.0.65\webapps\examples\WEB-INFweb.xml文件。

web.xml文件:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">

</web-app>

注:Web.xmlJava Web项目的一个核心配置文件,主要用于配置首页、ServletFilterListener等。

(6)在子项目的main文件夹下新建一个java文件夹和一个resource文件夹,并将它们分别标记为“源码根目录”和“资源根目录”。(具体步骤参考《Maven详解》)

(7)至此,子项目创建完毕。编译父项目或子项目,此时父项目与子项目的目录结构如下图所示。

项目结构

3.3、编写一个Java类实现Servlet接口(重点)

(1)在子项目的java目录下新建一个com.atangbiji.servlet包,并在该包下新建一个HelloServlet类,用于实现Servlet接口。

(2)让HelloServlet类继承(extends)HttpServlet类。(此时,Maven会通过父项目中的依赖自动导入依赖包。)

HelloServlet.java文件:

1
2
3
public class HelloServlet extends HttpServlet {

}

注:

  • Servlet是一个接口程序,也就是一个java程序,其存放于main-java文件夹中。
  • 由于sun公司为我们提供了两个默认的Servlet接口实现类:①HttpServlet,②GenericServlet。因此,我们只需继承HttpServlet类便可实现Servlet接口。

附:Servlet接口、GenericServlet类和HttpServlet类详解

a、Servlet接口源码

1
2
3
4
5
6
7
public interface Servlet {
void init(ServletConfig var1);
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2);//核心接口
String getServletInfo();
void destroy();
}

b、GenericServlet类源码(部分)

1
2
3
4
5
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
……
public abstract void service(ServletRequest var1, ServletResponse var2);//直接继承,未做任何处理
……
}

c、HttpServlet类源码(部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public abstract class HttpServlet extends GenericServlet {
……
//重写service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}

//重载service方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
//若请求方式为get,则调用doGet方法
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp); //若请求方式为post,则调用doPost方法
} 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 {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}

}

protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
……
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp){
……
}
……
}

分析源码,我们可以发现:

  • 我们自定义的Servlet类与Servlet接口、GenericServlet类和HttpServlet类的关系如下图所示。
Servlet接口的实现
  • HttpServlet类重写的service(ServletRequest,ServletResponse)方法中会
    ServletRequestServletResponse强转成HttpServletRequestHttpServletResponse
    然后再调用service(HttpServletRequest,HttpServletResponse)方法。
  • HttpServlet类重载了service(HttpServletRequest,HttpServletResponse)方法,该方法是HttpServlet
    自己重载的方法,而不是从Servlet继承来的。

结论:HttpServlet类在重载的service(HttpServletRequest,HttpServletResponse)方法中,会根据不同的Http请求方式,调用不同的处理方法。因此,我们需要在我们自己编写的Servlet类中重写相应的Http请求处理方法(重点掌握doGet和doPost方法)。

**注:**我们可以通过IDEA为我们提供的Structure窗口来查看HttpServlet类的成员变量和成员函数。

查看Servlet类

(3)在HelloServlet类中重写doGetdoPost方法。

重写doGet和doPost方法

HelloServlet.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了doGet方法");//在控制台打印调试信息
PrintWriter writer = resp.getWriter();//响应流
writer.print("Hello Servlet!");//在浏览器显示Hello Servlet!
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

**注:**因为getpost只是不同的Http请求方式,所以它们之间可以相互调用。

3.4、编写Servlet的映射(重点)

**注:**因为我们写的是一个Java程序,但是如果我们要通过浏览器去访问它,就需要连接web服务器。因此,我们需要在Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为其提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">

<!-- 注册Servlet -->
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>com.atangbiji.servlet.HelloServlet</servlet-class>
</servlet>
<!-- Servlet的访问路径 -->
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

</web-app>

3.5、配置Tomcat服务器

(1)配置Tomcat服务器。(具体步骤参考《Tomcat详解》)

配置Tomcat服务器

(2)deploy(发布)servlet-01:war包,并为该子项目添加虚拟路径映射:/s1

发布项目并添加虚拟路径映射

3.6、启动测试

在IDEA中启动Tomcat。

(1)在浏览器中输入http://localhost:8080/s1/,访问结果如下:

访问结果1

**注:**此时访问的是该Web项目的index.jsp文件。

index.jsp文件:

1
2
3
4
5
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>

(2)在浏览器中输入http://localhost:8080/s1/hello,访问结果如下:

访问结果2

**注:**此时访问的是我们自己编写的Servlet类。

4、Servlet与Servlet容器

Servlet是平台独立的Java类。编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。

Servlet容器是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务。

注:

  • Servlet不能独立运行,必须部署在Servlet容器中,由容器来实例化和调用Servlet的方法。
  • 在JSP技术推出后,管理和运行Servlet/JSP的容器也称“Web容器”。
  • Servlet容器、JSP容器以及Web容器基本上是同义的。

5、Servlet的运行原理

Servlet程序是由Web服务器调用的,Servlet程序的执行过程如下图所示。

Servlet运行原理

(1)用户在浏览器中输入URL,向Web服务器(Tomcat)发送Http请求。

(2)Web服务器接收到请求后,先将请求交给Servlet容器。

(3)Servlet容器判断是否需要创建Servlet实例。

注:

  • 当一个Servlet第一次接收请求时Servlet容器才会创建该Servlet的实例。然后调用其init方法进行初始化,接着通过service方法调用相应处理方法(如:doGetdoPost等),进而产生一个响应。
  • 当一个Servlet在第二次及以后接收请求时,Servlet容器会直接通过**service**方法调用相应处理方法(如:doGetdoPost等),进而产生一个响应。

(4)Servlet实例使用请求对象(HttpServletRequest)得到浏览器的请求信息,然后进行相应的处理。

(5)Servlet实例将处理的结果通过响应对象(HttpServletResponse)返回给Web服务器。

(6)Web服务器将响应包装后,以Http响应的形式返回给浏览器。

(7)在Servlet容器正常关闭或在Servlet实例超时时,Servlet容器则调用Servlet实例的destroy方法销毁Servlet

6、Servlet的映射(访问)路径

Servlet的映射(访问)路径是在Web配置文件(web.xml)中编写的。如:

1
2
3
4
5
<!--  Servlet的访问路径  -->
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

6.1、servlet映射(访问)路径的方式

映射路径方式 url-pattern示例 浏览器访问路径
1、精确匹配 /first http://localhost:8080/项目路径/first
/test/demo1 http://localhost:8080/项目路径/test/demo1
2、模糊匹配 /* http://localhost:8080/项目路径/任意字符或字符串(含/等)
/test/* http://localhost:8080/项目路径/test/任意字符或字符串(含/等)
*.后缀名 http://localhost:8080/项目路径/任意字符或字符串.后缀名(含/等)
*.do http://localhost:8080/项目路径/任意字符或字符串.do(含/等)
*.action http://localhost:8080/项目路径/任意字符或字符串.action(含/等)
*.html(伪静态) http://localhost:8080/项目路径/任意字符或字符串.html(含/等)
3、默认路径 / http://localhost:8080/项目路径/

注:

  • 一个Servlet程序可以指定一个或者多个映射路径
  • url-pattern要么以/开头,要么以*开头。( 例如, test为非法路径。)
  • 不能同时使用两种模糊匹配。(例如 : /*.do/test/*.do均为非法路径。)

6.2、优先级

  • servlet映射(访问)路径方式之间的优先级为:精确匹配 > 模糊匹配

7、Servlet上下文

7.1、ServletContext类详解

Web容器在启动时,会为每一个Web项目创建一个对应的ServletContext对象。它是当前项目中所有Servlet实例之间信息交互的“中间商”。(参见“Servlet的运行原理”)

注:一个Web项目对应一个ServletContext(即:同一个Web项目中的所有servlet实例共用同一个ServletContext对象。)

7.2、常见应用

ServletContext的常见应用场景主要包括:

  • **数据共享:**在一个Servlet中保存的数据,可以在另一个Servlet中使用。
  • 获取初始化参数
  • 请求转发
  • 读取资源文件

7.3、使用ServletContext实现:数据共享

(1)在上述父项目下再使用Maven模板新建一个MavenWeb子项目:servlet-02

(2)同样地,为了避免以后出现兼容性问题,建议我们先将该子项目中的web.xml文件替换成“web-app_4_0”版本。

(3)同样地,在该子项目的main文件夹下新建一个java文件夹和一个resource文件夹,并将它们分别标记为“源码根目录”和“资源根目录”。

(4)编译父项目或子项目,此时父项目与子项目的目录结构如下图所示。

父子项目结构2

(5)配置Tomcat服务器,deploy(发布)servlet-02:war包,并为该子项目添加虚拟路径映射:/s2

发布子项目2并添加虚拟路径映射

**注:**如果只发布某一子项目,那么其它子项目的war包和虚拟路径映射可以删除。若不删除,则一起发布。

(6)在该子项目的java目录下新建一个com.atangbiji.servlet包,并在该包下新建一个Servlet1类,用于将数据以“键-值“对的形式保存在ServlContex对象中。同样地,让Servlet1类继承(extends)HttpServlet类,以实现Servlet接口。然后,在Servlet1类中重写doGetdoPost方法。

Servlet1.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取ServlContex对象
String usernameVal = "阿汤笔迹";
servletContext.setAttribute("username",usernameVal);//将数据以“键-值“对的形式保存在ServlContex对象中
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(7)同样地,com.atangbiji.servlet包下再新建一个Servlet2类,用于获取ServlContex对象中保存的“键-值“对

Servlet2.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取ServlContex对象
String usernameVal = (String)servletContext.getAttribute("username");//从ServletContext中获取username的值

resp.setHeader("Content-Type", "text/html; charset=utf-8");
resp.getWriter().print("Servlet2从ServletContext中获取到username的值为:" + usernameVal);//在页面上输出响应
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(8)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的两个Servlet,并为它们分别提供一个浏览器可以访问的路径。

web.xml文件:

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
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<servlet>
<servlet-name>SetServletContext</servlet-name>
<servlet-class>com.atangbiji.servlet.Servlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SetServletContext</servlet-name>
<url-pattern>/setVal</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>GetServletContext</servlet-name>
<servlet-class>com.atangbiji.servlet.Servlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetServletContext</servlet-name>
<url-pattern>/getVal</url-pattern>
</servlet-mapping>

</web-app>

(9)在IDEA中启动Tomcat。在浏览器中先输入http://localhost:8080/s2/setVal加载Servlet1,然后再输入http://localhost:8080/s2/getVal,访问结果如下:

ServletContext访问结果1

**注:**我们可以通过IDEA为我们提供的Web窗口来查看Web项目的结构。

使用Web窗口查看项目

7.4、使用ServletContext实现:获取初始化参数

(1)在Web.xml文件中配置Web应用的初始化参数(如:连接mybatisurl)。

Web.xml文件:

1
2
3
4
5
<!--  配置Web应用初始化参数(连接mybatis的url) -->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
</context-param>

(2)在该子项目com.atangbiji.servlet包下新建一个Servlet3类,用于获取Web应用的初始化参数。同样地,让Servlet3类继承HttpServlet类,以实现Servlet接口。然后,在Servlet3类中重写doGetdoPost方法。

Servlet3.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Servlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取ServlContex对象

String url = servletContext.getInitParameter("url");//获取连接mybatis的url
resp.getWriter().print(url);//在页面上输出响应
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(3)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>GetInitParameter</servlet-name>
<servlet-class>com.atangbiji.servlet.Servlet3</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetInitParameter</servlet-name>
<url-pattern>/getInitParameter</url-pattern>
</servlet-mapping>

(4)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/s2/getInitParameter,访问结果如下:

获取初始化参数

7.5、使用ServletContext实现:请求转发

7.5.1、什么是请求转发

请求转发(RequestDispatcher)指的是:一个web资源(Servlet1)接受到客户端的请求后,通知服务器去调用另外一个web资源(Servlet2)进行处理。

7.5.2、请求转发的工作原理

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

请求转发的工作原理

7.5.3、请求转发的特点

  • 请求转发是服务器端的行为,浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
  • 请求转发之后,浏览器地址栏中的 URL 不会发生变化
  • 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
  • 参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象。
  • 只有转发到最后一个 Web资源时,生成的响应才会被发送到客户端。

7.5.4、请求转发的实现

(1)在该子项目com.atangbiji.servlet包下新建一个Servlet4类,用于将访问Servlet4的请求转发到Servlet3的请求路径上来。同样地,让Servlet4类继承HttpServlet类,以实现Servlet接口。然后,在Servlet4类中重写doGetdoPost方法。

Servlet4.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Servlet4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取ServlContex对象
//获取访问该Servlet的请求将要转发的路径
RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/getInitParameter");
//调用forward方法实现请求转发
requestDispatcher.forward(req,resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(2)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>RequestDispatcher</servlet-name>
<servlet-class>com.atangbiji.servlet.Servlet4</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RequestDispatcher</servlet-name>
<url-pattern>/requestDispatcher</url-pattern>
</servlet-mapping>

(3)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/s2/requestDispatcher,此时服务器会将我们的请求转发到/getInitParameter路径上来。访问结果如下:

请求转发

注:请注意请求转发与请求重定向的区别,我们将在介绍重定向是对比说明。

7.6、ServletContext应用示例:读取资源文件

(1)在该子项目的resources文件夹下新建一个db.properties文件。

db.properties文件:

1
2
username = root
password = 123456789

(2)在该子项目com.atangbiji.servlet包下新建一个propertiesServlet类,用于读取db.properties资源文件。同样地,让propertiesServlet类继承HttpServlet类,以实现Servlet接口。然后,在propertiesServlet类中重写doGetdoPost方法。

propertiesServlet.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class propertiesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = this.getServletContext();//获取ServlContex对象
//以数据流的方式获取资源
InputStream stream = servletContext.getResourceAsStream("/WEB-INF/classes/db.properties");//项目打包后的相对路径

Properties prop = new Properties();
//加载数据流
prop.load(stream);
//获取属性值
String username = prop.getProperty("username");
String password = prop.getProperty("password");
//在页面上输出响应
resp.getWriter().print(username + ":" + password);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(3)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>PropertiesServlet</servlet-name>
<servlet-class>com.atangbiji.servlet.propertiesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PropertiesServlet</servlet-name>
<url-pattern>/propertiesServlet</url-pattern>
</servlet-mapping>

(4)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/s2/propertiesServlet,访问结果如下:

读取资源文件

**注:**由于Maven的“约定大于配置”思想,我们以后可能会遇到我们自己写的配置文件(如:我们配置文件*.xml写在了项目中的Java目录下)无法被导出或者无法生效的问题。该问题的解决方法是:在pom.xml<build>……</build>中配置resources,修改Maven约定的过滤条件,来防止我们的资源导出失败。

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
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<!--选择需要导出资源的路径,可以选择多个-->
<resource>
<directory>src/main/resources</directory>
<!--写入需要导出的什么类型的文件-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--不去过滤这些文件-->
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--不去过滤这些文件-->
<filtering>false</filtering>
</resource>
</resources>
</build>

8、HttpServletResponse响应

Web服务器接收到客户端的http请求后,会针对这个请求分别创建一个代表请求的HttpServletRequest对象和一个代表响应的HttpServletResponse对象。

  • 若我们需要获取客户端发送的请求参数,则使用HttpServletRequest对象。
  • 若我们需要给客户端响应一些信息,则使用HttpServletResponse对象。

8.1、响应方法简单分类

查看源码,可以将HttpServletResponse类及其父类(ServletResponse类)中常用的响应方法简单分为如下几类:

(1)负责向浏览器发送数据的方法

1
2
3
ServletOutputStream getOutputStream() throws IOException;//在响应中写入二进制数据流

PrintWriter getWriter() throws IOException;//发送字符文本(如:中文等)到客户端

(2)负责设置响应头的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void setCharacterEncoding(String var1);

void setContentLength(int var1);

void setContentLengthLong(long var1);

void setContentType(String var1);

void setBufferSize(int var1);

void setDateHeader(String var1, long var2);

void addDateHeader(String var1, long var2);

void setHeader(String var1, String var2);

void addHeader(String var1, String var2);

void setIntHeader(String var1, int var2);

void addIntHeader(String var1, int var2);

此外,我们还可以在HttpServletResponse类的源码中查看JavaWeb基于HTTP协议定义的响应状态码:

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
int 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;

8.2、HttpServletResponse的常见应用

HttpServletResponse的常见应用场景主要包括:

  • 向浏览器输出信息(不再赘述)
  • 文件下载
  • 生成网页验证码图片
  • 网页重定向

8.3、使用HttpServletResponse实现:文件下载

(1)在上述父项目下再使用Maven模板新建一个MavenWeb子项目:response

(2)同样地,为了避免以后出现兼容性问题,建议我们先将该子项目中的web.xml文件替换成“web-app_4_0”版本。

(3)同样地,在该子项目的main文件夹下新建一个java文件夹和一个resource文件夹,并将它们分别标记为“源码根目录”和“资源根目录”。

(4)在resource目录下放置一个供下载的图片文件测试图片.png

(5)同样地,配置Tomcat服务器,deploy(发布)response:war包,并为该子项目添加虚拟路径映射:/resp

(6)同样地,在该子项目的java目录下新建一个com.atangbiji.servlet包,并在该包下新建一个FileServlet类,用于实现文件下载。同样地,让FileServlet类继承(extends)HttpServlet类,以实现Servlet接口。然后,在FileServlet类中重写doGetdoPost方法。

FileServlet.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、获取下载文件的路径
String realPath = this.getServletContext().getRealPath("/WEB-INF/classes/测试图片.png");
System.out.println(realPath);
//2、获取下载文件的文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
//3、设置浏览器使其支持下载文件
resp.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName,"UTF-8"));
//4、获取下载文件的输入流
FileInputStream fileInputStream = new FileInputStream(realPath);//把文件变为流
// 5、创建缓冲区
int length = 0;
byte[] buffer = new byte[1024];
//6、获取OutputStream对象
ServletOutputStream outputStream = resp.getOutputStream();
//7、将FileOutPutStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
while ((length = fileInputStream.read(buffer)) > 0){
outputStream.write(buffer,0,length);
}
//8、关闭文件流
outputStream.close();
fileInputStream.close();
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(7)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>downFileServlet</servlet-name>
<servlet-class>com.atangbiji.servlet.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>downFileServlet</servlet-name>
<url-pattern>/download</url-pattern>
</servlet-mapping>

(8)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/resp/download,便可实现测试图片.png文件的下载。

8.4、使用HttpServletResponse实现:生成验证码图片

网页验证码既可以在前端实现,也可以在后端实现。我们这里介绍如何使用java的图片类在后端生成验证码。

(1)同样地,在com.atangbiji.servlet包下新建一个ImageServlet类,用于生成验证码图片。同样地,让ImageServlet类继承(extends)HttpServlet类,以实现Servlet接口。然后,在ImageServlet类中重写doGetdoPost方法。

ImageServlet.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//让浏览器每3秒刷新一次
resp.setHeader("refresh","3");
//在内存中创建一个图片
BufferedImage bufferedImage = new BufferedImage(100,20,BufferedImage.TYPE_INT_RGB);
//得到验证码图片
Graphics2D graphics = (Graphics2D)bufferedImage.getGraphics();//获取画笔
//设置图片的背景颜色
graphics.setColor(Color.white);
graphics.fillRect(0,0,100,40);
//给图片写数据
graphics.setColor(Color.red);
graphics.setFont(new Font(null,Font.BOLD,20));
graphics.drawString(SetNum(),20,20);
//让浏览器以图片的方式打开该请求
resp.setContentType("image/png");
//让浏览器不要缓存
resp.setDateHeader("expires",-1);
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("program", "no-cache");
// 把图片写给浏览器
ImageIO.write(bufferedImage,"png",resp.getOutputStream());
}

//生成5位随机数字
private String SetNum(){
Random random = new Random();
String strNum = random.nextInt(99999) + "";
StringBuffer stringBuffer = new StringBuffer();
//若随机数不足5位,则用"0"填充
for (int i = 0; i < 5 - strNum.length(); i++) {
stringBuffer.append("0");
}
strNum = stringBuffer.toString() + strNum;
return strNum;
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(2)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>ImageServlet</servlet-name>
<servlet-class>com.atangbiji.servlet.ImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ImageServlet</servlet-name>
<url-pattern>/img</url-pattern>
</servlet-mapping>

(3)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/resp/img,访问结果如下:

生成验证码图片

8.5、使用HttpServletResponse实现:网页重定向(重点)

8.5.1、什么是请求重定向

重定向(Redirect)指的是:一个web资源(Servlet1)接受到客户端的请求后,通知客户端去调用另外一个web资源(Servlet2)进行处理。

当浏览器向Tomcat服务器发送请求时,由于一个Servlet1类无法完成所有响应工作,此时Servlet1类会通知浏览器重新定向到另一个Servlet2类。浏览器则再向Servlet2发送一个请求,来获取Servlet2的响应。

8.5.2、重定向的工作原理

重定向的工作原理如下图所示:

请求重定向的原理

8.5.3、重定向的特点

  • 请求重定向发是客户端的行为
  • 请求重定向之后,浏览器地址栏中的 URL 会发生变化
  • 请求重定向支持跨域访问
  • 参与请求重定向的 Web 资源使用的是不同的 request 对象和 response 对象。

8.5.4、结论:“请求转发” VS ”请求重定向“(重点)

区别 请求转发 请求重定向
浏览器地址栏中的 URL 是否发生变化
是否支持跨域跳转
请求和响应的次数 1次请求和1次响应 2次请求和2次响应
是否共享 request 对象和 response 对象
是否通过request 域对象传递数据
速度 相对较快 相对较慢
行为类型 服务端行为 客户端行为
HTTP响应状态码 302 307
默认映射地址 localhost:8080/当前项目路径 localhost:8080

8.5.5、重定向的实现

(1)同样地,在com.atangbiji.servlet包下新建一个RedirectServlet类,用于实现重定向。同样地,让RedirectServlet类继承(extends)HttpServlet类,以实现Servlet接口。然后,在RedirectServlet类中重写doGetdoPost方法。

RedirectServlet.java文件:

1
2
3
4
5
6
7
8
9
10
11
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("/resp/img");//重定向为:访问"/resp/img"路径
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(2)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>RedirectServlet</servlet-name>
<servlet-class>com.atangbiji.servlet.RedirectServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RedirectServlet</servlet-name>
<url-pattern>/redirect</url-pattern>
</servlet-mapping>

(3)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/resp/redirect,此时浏览器地址栏中的 URL 会重定向为:http://localhost:8080/resp/img。访问结果如下:

重定向

9、HttpServletRequest请求

HttpServletRequest代表客户端的请求,通过HttpServletRequest类及其父类(ServletRequest类)的方法,可以获得客户端的所有信息。

9.1、获取前端传递的参数

  • HttpServletRequest常用于获取用户的输入,即拿到前端传递的参数。常用成员函数主要包括:
1
2
3
String getParameter(String var1);//获取单个参数的值

String[] getParameterValues(String var1);//获取多个参数的值
  • 接下来,我们将通过实现登录功能,使用HttpServletRequest获取前端传递的参数。具体实现步骤如下:

(1)在上述父项目下再使用Maven模板新建一个MavenWeb子项目:request

(2)同样地,为了避免以后出现兼容性问题,建议我们先将该子项目中的web.xml文件替换成“web-app_4_0”版本。

(3)同样地,在该子项目的main文件夹下新建一个java文件夹和一个resource文件夹,并将它们分别标记为“源码根目录”和“资源根目录”。

(4)同样地,配置Tomcat服务器,deploy(发布)request:war包,并为该子项目添加虚拟路径映射:/req

(5)在父项目的pom.xml文件中导入jsp需要的依赖,那么所有的子项目就可以重复使用该依赖包了。

父项目的pom.xml文件:

1
2
3
4
5
6
7
8
9
10
<!--项目依赖-->
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
</dependencies>

(6)修改该子项目下的index.jsp文件,在其中编写登录页面。

index.jsp文件:

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
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
<div style="text-align: center">
<%--
(1)${pageContext.request.contextPath}表示当前项目路径。
(2)请求方式:post。
--%>
<form action="${pageContext.request.contextPath}/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
爱好:
<input type="checkbox" name="hobbies" value="唱歌">唱歌
<input type="checkbox" name="hobbies" value="跳舞">跳舞
<input type="checkbox" name="hobbies" value="乒乓球">乒乓球
<input type="checkbox" name="hobbies" value="羽毛球">羽毛球

<br>
<input type="submit" value="登录">
</form>
</div>
</body>
</html>

注:

  • ${pageContext.request.contextPath}表示当前项目地址
  • 使用post请求方式不会在浏览器的URL地址栏显示数据内容;安全,但效率相对较低。

(7)同样地,在该子项目的java目录下新建一个com.atangbiji.servlet包,并在该包下新建一个LoginServlet类,用于在服务端获取登录信息。同样地,让LoginServlet类继承(extends)HttpServlet类,以实现Servlet接口。然后,在LoginServlet类中重写doGetdoPost方法。

LoginServlet.java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求和响应的编码集
resp.setCharacterEncoding("utf-8");
req.setCharacterEncoding("utf-8");
//使用HttpServletResponse获取前端传递的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobbies = req.getParameterValues("hobbies");
//在服务端打印从前端获取到的参数
System.out.println("======================================");
System.out.println(username);
System.out.println(password);
System.out.println(Arrays.toString(hobbies));
System.out.println("======================================");
//在登录成功后,服务端通过"请求转发"的方式将登录页面跳转到登录成功页面上来。(注:"/"表示当前项目路径。)
req.getRequestDispatcher("/success.jsp").forward(req,resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp); //因为`get`和`post`只是不同的Http请求方式,所以它们之间可以相互调用。
}
}

(8)同样地,在该子项目的Web配置文件(web.xml)中注册(映射)我们自己编写的Servlet,并为它提供一个浏览器可以访问的路径。

web.xml文件:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.atangbiji.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>

(9)在该子项目的webapp目录下新建一个success.jsp文件,作为登录成功界面。在登录成功后,服务端通过请求转发的方式将页面跳转到该页面上来。

success.jsp文件:

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录成功页面</title>
</head>
<body>
<h1>恭喜你,登录成功!</h1>
</body>
</html>

(10)在IDEA中启动Tomcat。在浏览器中输入http://localhost:8080/req进入登录页面,并在页面中输入用户名、密码和爱好。

登录页面

(11)点击“登录”按钮,此时服务端通过请求转发的方式将页面跳转到登录成功的页面上来。

登录成功

与此同时,服务端输出从前端获取到的信息:

登录成功后台输出

9.2、请求转发(RequestDispatcher)

Servlet上下文中,我们已经介绍了如何使用ServletContext实现请求转发。此外,在HttpServletRequest获取前端传递参数的示例中,我们已经介绍了如何使用HttpServletRequest实现请求转发。

具体实现代码如下:

1
req.getRequestDispatcher("/success.jsp").forward(req,resp);

**注:**请求转发的默认映射地址为: localhost:8080/当前项目路径 ;重定向的默认映射地址为:localhost:8080

(本讲完,系列博文持续更新中…… )

阿汤笔迹微信公众平台

关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2022/10/04/ServletInDetail
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。