我只想卷死各位,或者被各位卷死,在此特别感谢黑马程序员的JavaWeb教程


本文摘要:

  • 了解JavaWeb开发的技术栈
  • 理解HTTP协议和HTTP请求与响应数据的格式
  • 掌握Tomcat的使用
  • 掌握在IDEA中使用Tomcat插件
  • 理解Servlet的执行流程和生命周期
  • 掌握Servlet的使用和相关配置

Web概述

Web和JavaWeb的概念

Web是全球广域网,也称为万维网(www),能够通过浏览器访问的网站。

在我们日常的生活中,经常会使用浏览器去访问百度知乎推特等这些网站,这些网站统称为Web网站。

我们知道了什么是Web,那么JavaWeb又是什么呢?顾名思义JavaWeb就是用Java技术来解决相关Web互联网领域的技术栈。

JavaWeb技术栈

了解JavaWeb技术栈之前,有一个很重要的概念要介绍。

B/S架构

什么是B/S架构?
B/S 架构:Browser/Server,浏览器/服务器 架构模式,它的特点是,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web资源,服务器把Web资源发送给浏览器即可。

  • 打开浏览器访问百度首页,输入要搜索的内容,点击回车或百度一下,就可以获取和搜索相关的内容
  • 思考下搜索的内容并不在我们自己的点上,那么这些内容从何而来?答案很明显是从百度服务器返回给我们的
  • 日常百度的小细节,逢年过节百度的logo会更换不同的图片,服务端发生变化,客户端不需做任务事情就能获取最新内容
  • 所以说B/S架构的好处:易于维护升级:服务器端升级后,客户端无需任何部署就可以使用到新的版本。

了解了什么是B/S架构后,作为后台开发工程师的我们将来主要关注的是服务端的开发和维护工作。在服务端将来会放很多资源,都有哪些资源呢?

静态资源

  • 静态资源主要包含HTML、CSS、JavaScript、图片等,主要负责页面的展示。
  • 我们之前已经学过前端网页制作三剑客(HTML+CSS+JavaScript),使用这些技术我们就可以制作出效果比较丰富的网页,将来展现给用户。但是由于做出来的这些内容都是静态的,这就会导致所有的人看到的内容将是一模一样。
  • 在日常上网的过程中,我们除了看到这些好看的页面以外,还会碰到很多动态内容,比如我们常见的百度登录效果:
    张三登录以后在网页的右上角看到的是 张三,而李四登录以后看到的则是李四。所以不同的用户访问相同的资源看到的内容大多数是不一样的,要想实现这样的效果,光靠静态资源是无法实现的。

动态资源

  • 动态资源主要包含Servlet、JSP等,主要用来负责逻辑处理。
  • 动态资源处理完逻辑后会把得到的结果交给静态资源来进行展示,动态资源和静态资源要结合一起使用。
  • 动态资源虽然可以处理逻辑,但是当用户来登录百度的时候,就需要输入用户名密码,这个时候我们就又需要解决的一个问题是,用户在注册的时候填入的用户名和密码、以及我们经常会访问到一些数据列表的内容展示(如下图所示),这些数据都存储在哪里?我们需要的时候又是从哪里来取呢?

数据库

  • 数据库主要负责存储数据。
  • 整个Web的访问过程就如下图所示:
    web访问流程.png
  1. 浏览器发送一个请求到服务端,去请求所需要的相关资源;
  2. 资源分为动态资源和静态资源,动态资源可以是使用Java代码按照Servlet和JSP的规范编写的内容;
  3. 在Java代码可以进行业务处理也可以从数据库中读取数据;
  4. 拿到数据后,把数据交给HTML页面进行展示,再结合CSS和JavaScript使展示效果更好;
  5. 服务端将静态资源响应给浏览器;
  6. 浏览器将这些资源进行解析;
  7. 解析后将效果展示在浏览器,用户就可以看到最终的结果。

HTTP协议

  • HTTP协议:主要定义通信规则
  • 浏览器发送请求给服务器,服务器响应数据给浏览器,这整个过程都需要遵守一定的规则,之前学习过TCP、UDP,这些都属于规则,这里我们需要使用的是HTTP协议,这也是一种规则。

Web服务器

  • Web服务器:负责解析 HTTP 协议,解析请求数据,并发送响应数据
  • 浏览器按照HTTP协议发送请求和数据,后台就需要一个Web服务器软件来根据HTTP协议解析请求和数据,然后把处理结果再按照HTTP协议发送给浏览器
  • Web服务器软件有很多,本文使用的是最为常用的Tomcat服务器

HTTP

简介

HTTP概念

HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。

  • 数据传输的规则指的是请求数据和响应数据需要按照指定的格式进行传输。
  • 如果想知道具体的格式,可以打开浏览器,点击F12打开开发者工具,点击Network来查看某一次请求的请求数据和响应数据具体的格式内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xc9b693f200183fa5
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Tue, 16 Aug 2022 10:39:20 GMT
Expires: Tue, 16 Aug 2022 10:39:20 GMT
Isprivate: 1
Server: BWS/1.1
Set-Cookie: BDSVRTM=221; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=36560_36981_36955_36917_36776_37126_37135_26350_36863; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1660646360018991617014534967514977812389
X-Frame-Options: sameorigin
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET / HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8
Cache-Control: max-age=0
Connection: keep-alive
Cookie: ...
Host: www.baidu.com
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

学习HTTP主要就是学习请求和响应数据的具体格式内容。

HTTP协议特点

HTTP协议有它自己的一些特点,分别是:

  • 基于TCP协议: 面向连接,安全

    TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在数据传输方面更安全。

  • 基于请求-响应模型的:一次请求对应一次响应

    请求和响应是一一对应关系

  • HTTP协议是无状态协议:对于事物处理没有记忆能力。每次请求-响应都是独立的

    无状态指的是客户端发送HTTP请求给服务端之后,服务端根据请求响应数据,响应完后,不会记录任何信息。这种特性有优点也有缺点,

    • 缺点:多次请求间不能共享数据
    • 优点:速度快

    请求之间无法共享数据会引发的问题,如:

    • 京东购物,加入购物车去购物车结算是两次请求,
    • HTTP协议的无状态特性,加入购物车请求响应结束后,并未记录加入购物车是何商品
    • 发起去购物车结算的请求后,因为无法获取哪些商品加入了购物车,会导致此次请求无法正确展示数据

    但实际使用的时候,我们发现京东是可以正常展示数据的,原因是Java早已考虑到这个问题,并提出了使用会话技术(Cookie、Session)来解决这个问题。具体如何来做,后面会详细讲到。刚才提到HTTP协议是规定了请求和响应数据的格式,那具体的格式是什么呢?

请求格式数据

格式介绍

请求数据总共分为三部分内容,分别是请求行请求头请求体

  • 请求行: HTTP请求中的第一行数据,请求行包含三块内容,分别是 GET[请求方式] /[请求URL路径] HTTP/1.1[HTTP协议及版本]

    请求方式有七种,最常用的是GET和POST

  • 请求头: 第二行开始,格式为key: value形式

    请求头中会包含若干个属性,常见的HTTP请求头有:

    1
    2
    3
    4
    5
    Host: 表示请求的主机名
    User-Agent: 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0 ...Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...)like Gecko;
    Accept:表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有;
    Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
    Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate等。

    这些数据有什么用处?
    举例说明:服务端可以根据请求头中的内容来获取客户端的相关信息,有了这些信息服务端就可以处理不同的业务需求,比如:

    • 不同浏览器解析HTML和CSS标签的结果会有不一致,所以就会导致相同的代码在不同的浏览器会出现不同的效果
    • 服务端根据客户端请求头中的数据获取到客户端的浏览器类型,就可以根据不同的浏览器设置不同的代码来达到一致的效果
    • 这就是我们常说的浏览器兼容问题
  • 请求体: POST请求的最后一部分,存储请求参数
    请求体和请求头之间是有一个空行隔开(不过我现在用Chrome浏览器和Edge浏览器查看的时候,请求体和请求头都是分开的,具体看下面的实例演示)。此时浏览器发送的是POST请求,为什么不能使用GET呢?这时就需要回顾GET和POST两个请求之间的区别了:

  • GET请求请求参数在请求行中,没有请求体,POST请求请求参数在请求体中

  • GET请求请求参数大小有限制,POST没有

实例演示

简单写了两个表单,分别来测试GET请求和POST请求的参数携带方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="#" method="get">
用户名<input type="text" name="username"><br>
密码<input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="#" method="post">
用户名<input type="text" name="username"><br>
密码<input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
</body>
</html>

随便输入用户名和密码,在点击注册之前,用F12打开开发者工具,点击Network来查看请求的请求数据和响应数据具体的格式,得到如下结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /tomcat_demo_war/index.html?username=suger1201&password=dsaasd HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: keep-alive
Cookie: Hm_lvt_f8440b73b5064587c92bcc0ec23ce01c=1653785840,1653981339,1654172967,1654398744; Idea-72a17a30=9bc0eee2-8943-4e0e-b750-ac83c65bb51a; Idea-5587b4ce=bd97b98f-3ea3-4e0c-95e8-12bb1b3b1abb
DNT: 1
Host: localhost:8080
Referer: http://localhost:8080/tomcat_demo_war/index.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.81 Mobile Safari/537.36 Edg/104.0.100.0
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Microsoft Edge";v="104"
sec-ch-ua-mobile: ?1
sec-ch-ua-platform: "Android"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /tomcat_demo_war/a.html HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 37
Content-Type: application/x-www-form-urlencoded
Cookie: Hm_lvt_f8440b73b5064587c92bcc0ec23ce01c=1653785840,1653981339,1654172967,1654398744; Idea-72a17a30=9bc0eee2-8943-4e0e-b750-ac83c65bb51a; Idea-5587b4ce=bd97b98f-3ea3-4e0c-95e8-12bb1b3b1abb
DNT: 1
Host: localhost:8080
Origin: http://localhost:8080
Referer: http://localhost:8080/tomcat_demo_war/a.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.81 Mobile Safari/537.36 Edg/104.0.100.0
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Microsoft Edge";v="104"
sec-ch-ua-mobile: ?1
sec-ch-ua-platform: "Android"

username=Cyderpunk2077%40gmail.com&password=asdasdasd

GET请求请求参数在请求行(注意看第一行)中,没有请求体,POST请求请求参数在请求体中(注意看最后一行,其实这行是在payload中找到的,Chrome浏览器把请求参数和请求体分开了)

响应格式数据

格式介绍

响应数据总共分为三部分内容,分别是响应行响应头响应体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"323-1660647748000"
Last-Modified: Tue, 16 Aug 2022 11:02:28 GMT
Content-Type: text/html
Content-Length: 323S
Date: Tue, 16 Aug 2022 11:18:20 GMT

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="#" method="post">
用户名<input type="text" name="username"><br>
密码<input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
</body>
</html>
  • 响应行:响应数据的第一行,响应行包含三块内容,分别是 HTTP/1.1[HTTP协议及版本] 200[响应状态码] ok[状态码的描述]

  • 响应头:第二行开始,格式为key:value形式

    响应头中会包含若干个属性,常见的HTTP响应头有:

    1
    2
    3
    4
    Content-Type:表示该响应内容的类型,例如text/html,image/jpeg;
    Content-Length:表示该响应内容的长度(字节数);
    Content-Encoding:表示该响应压缩算法,例如gzip;
    Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300
  • 响应体: 最后一部分。存放响应数据

上面的代码中<html>...</html>这部分内容就是响应体,它和响应头之间有一个空行隔开(在Chrome浏览器中响应头和响应体也是分开的)。

响应状态码

状态码大类

状态码分类 说明
1xx 响应中——临时状态码,表示请求已经接受,告诉客户端应该继续请求或者如果它已经完成则忽略它
2xx 成功——表示请求已经被成功接收,处理已完成
3xx 重定向——重定向到其它地方:它让客户端再发起一个请求以完成整个处理。
4xx 客户端错误——处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等
5xx 服务器端错误——处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等

状态码大全:https://cloud.tencent.com/developer/chapter/13553

常见的响应状态码

状态码 英文描述 解释
200 OK 客户端请求成功,即处理成功,这是我们最想看到的状态码
302 Found 指示所请求的资源已移动到由Location响应头给定的 URL,浏览器会自动重新访问到这个页面
304 Not Modified 告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向
400 Bad Request 客户端请求有语法错误,不能被服务器所理解
403 Forbidden 服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
404 Not Found 请求资源不存在,一般是URL输入有误,或者网站资源被删除了
428 Precondition Required 服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
429 Too Many Requests 太多请求,可以限制客户端请求某个资源的数量,配合 Retry-After(多长时间后可以请求)响应头一起使用
431 Request Header Fields Too Large 请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。
405 Method Not Allowed 请求方式有误,比如应该用GET请求方式的资源,用了POST
500 Internal Server Error 服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
503 Service Unavailable 服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好
511 Network Authentication Required 客户端需要进行身份验证才能获得网络访问权限

Tomcat

简介

什么是Web服务器?
Web服务器是一个应用程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是"提供网上信息浏览服务"。

我们可以把自己写的Web项目部署到Web Tomcat服务器软件中,当Web服务器软件启动后,部署在Web服务器软件中的页面就可以直接通过浏览器来访问了。

Web服务器软件使用步骤

  • 准备静态资源
  • 下载安装Web服务器软件
  • 将静态资源部署到Web服务器上
  • 启动Web服务器使用浏览器访问对应的资源

Tomcat的相关概念:

  • Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。

  • 概念中提到了JavaEE规范,那什么又是JavaEE规范呢?

    JavaEE: Java Enterprise Edition,Java企业版。指Java企业级开发的技术规范总和。包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF。

  • 因为Tomcat支持Servlet/JSP规范,所以Tomcat也被称为Web容器、Servlet容器。Servlet需要依赖Tomcat才能运行。

  • Tomcat的官网: https://tomcat.apache.org/ 从官网上可以下载对应的版本进行使用。

关于IDEA创建Maven Web项目和在Tomcat的使用这里就不过多介绍了。

Servlet

简介

  • Servlet是JavaWeb最为核心的内容,它是Java提供的一门动态web资源开发技术。

  • 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。

  • Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet

快速入门

需求分析: 编写一个Servlet类,并使用IDEA中Tomcat插件进行部署,最终通过浏览器访问所编写的Servlet程序。

具体的实现步骤为:

  1. 创建Web项目web-demo,导入Servlet依赖坐标
1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
此处为什么需要添加该标签?
provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
-->
<scope>provided</scope>
</dependency>
  1. 创建:定义一个类,实现Servlet接口,并重写接口中所有方法,并在service方法中输入一句话
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
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

public class ServletDemo implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello servlet");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}
  1. 配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet("/demo1") //加上这个东东
public class ServletDemo implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello servlet");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}
  1. 访问:启动Tomcat,浏览器中输入URL地址访问该Servlet
    http://localhost:8080/web-demo/demo1

  2. 访问后,在控制台会打印hello servlet 说明servlet程序已经成功运行。

至此,Servlet的入门案例就已经完成

执行流程

Servlet程序已经能正常运行,但是我们需要思考个问题: 我们并没有创建ServletDemo类的对象,也没有调用service中的方法,为什么控制台就输出了hello servlet这句话呢?

要解答上述问题,我们就需要对Servlet的执行流程进行一个学习。

  • 浏览器发出http://localhost:8080/web-demo/demo1请求,从请求中可以解析出三部分内容,分别是localhost:8080web-demodemo1
    • 根据localhost:8080可以找到要访问的Tomcat Web服务器
    • 根据web-demo可以找到部署在Tomcat服务器上的web-demo项目
    • 根据demo1可以找到要访问的是项目中的哪个Servlet类,根据@WebServlet后面的值进行匹配
  • 找到ServletDemo这个类后,Tomcat Web服务器就会为ServletDemo这个类创建一个对象,然后调用对象中的service方法
    • ServletDemo实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用
    • service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互

小结

介绍完Servlet的执行流程,需要大家掌握两个问题:

  1. Servlet由谁创建?Servlet方法由谁调用?

Servlet由web服务器创建,Servlet方法由web服务器调用

  1. 服务器怎么知道Servlet中一定有service方法?

因为我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法

生命周期

介绍完Servlet的执行流程后,我们知道Servlet是由Tomcat Web服务器帮我们创建的。

接下来咱们再来思考一个问题:Tomcat什么时候创建的Servlet对象?

要想回答上述问题,我们就需要对Servlet的生命周期进行一个学习。

  • 生命周期: 对象的生命周期指一个对象从被创建到被销毁的整个过程。

  • Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:

    1. 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
      默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的体验就比较差,那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置?
    1
    @WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)

    loadOnstartup的取值有两类情况
    (1)负整数:第一次访问时创建Servlet对象
    (2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
    2. 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次
    3. 请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理
    4. 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被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
45
46
47
48
49
50
51
52
53
54
55
56
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

//设置为1之后,在我们访问http://localhost:8080/web-demo/demo之前,就会进行初始化操作了,第一个人访问的时候就不会等那么久了
@WebServlet(urlPatterns = "/demo",loadOnStartup = 1)
public class ServletDemo implements Servlet {
/**
* 初始化方法
* 1.调用时机:默认情况下,Servlet被第一次访问时,调用
* * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用
* 2.调用次数: 1次
*
* @param servletConfig
* @throws ServletException
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init..");
}

/**
* 提供服务
* 1.调用时机:每一次Servlet被访问时,调用
* 2.调用次数: 多次
*
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello servlet");
}

/**
* 销毁方法
* 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
* 2.调用次数: 1次
*/
@Override
public void destroy() {
System.out.println("destroy...");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public ServletConfig getServletConfig() {
return null;
}
}

小结

  1. Servlet对象在什么时候被创建的?

默认是第一次访问的时候被创建,可以使用@WebServlet(urlPatterns = "/demo2",loadOnStartup = 1)修改成在服务器启动的时候创建。

  1. Servlet生命周期中涉及到的三个方法,这三个方法是什么?什么时候被调用?调用几次?

涉及到三个方法,分别是 init()、service()、destroy()
init方法在Servlet对象被创建的时候执行,只执行1次
service方法在Servlet被访问的时候调用,每访问1次就调用1次(每刷新一次界面,控制台就会输出一次)
destroy方法在Servlet对象被销毁的时候调用,只执行1次

方法介绍

Servlet中总共有5个方法,我们已经介绍过其中的三个,剩下的两个方法是:

  • 获取Servlet信息
1
2
3
4
5
String getServletInfo() 
//该方法用来返回Servlet的相关信息,没有什么太大的用处,一般我们返回一个空字符串即可
public String getServletInfo() {
return "";
}
  • 获取ServletConfig对象
1
ServletConfig getServletConfig()

ServletConfig对象,在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可。具体如何操作?

创建一个私有的ServletConfig对象,当我们调用init方法的时候,将传入的ServletConfig对象赋给私有的ServletConfig对象,代码如下

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
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet(urlPatterns = "/demo", loadOnStartup = 1)
public class ServletDemo implements Servlet {
//创建ServletConfig对象
private ServletConfig servletConfig;

@Override
public void init(ServletConfig servletConfig) throws ServletException {
//将init方法传入的ServletConfig对象赋给我们创建的ServletConfig
this.servletConfig = servletConfig;
System.out.println("init..");
}

@Override
public ServletConfig getServletConfig() {
//返回ServletConfig对象
return servletConfig;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello servlet");
}

@Override
public void destroy() {
System.out.println("destroy...");
}

@Override
public String getServletInfo() {
return null;
}
}

getServletInfo()和getServletConfig()这两个方法使用的不是很多,了解一下就行。

体系结构

通过上面的学习,我们知道要想编写一个Servlet就必须要实现Servlet接口,重写接口中的5个方法,虽然已经能完成要求,但是编写起来还是比较麻烦的,因为我们更关注的其实只有service方法,那有没有更简单方式来创建Servlet呢?

要想解决上面的问题,我们需要先对Servlet的体系结构进行下了解:

servlet体系结构.png

因为我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会通过继承HttpServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.servlet.*;
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 = "/demo", loadOnStartup = 1)
public class ServletDemo extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get...");
}
}
  • 要想发送一个GET请求,请求该Servlet,只需要通过浏览器发送http://localhost:8080/web-demo/demo4,就能看到doGet方法被执行了
  • 要想发送一个POST请求,请求该Servlet,单单通过浏览器是无法实现的,这个时候就需要编写一个form表单来发送请求,在webapp下创建一个index.html页面,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/web_demo_war_exploded/demo" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>

启动测试,在控制台可看到doPost方法被执行了。

Servlet的简化编写就介绍完了,接着需要思考两个问题:

  1. HttpServlet中为什么要根据请求方式的不同,调用不同的方法?
  2. 如何调用?

针对问题一,我们需要回顾之前的知识点前端发送GET和POST请求的时候,参数的位置不一致,GET请求参数在请求行中,POST请求参数在请求体中,为了能处理不同的请求方式,我们得在service方法中进行判断,然后写不同的业务处理,这样能实现,但是每个Servlet类中都将有相似的代码,针对这个问题,有什么可以优化的策略吗?

可以把这些相似的代码写入一个类当中,HttpServlet已经帮我们解决了,下面来看看HttpServlet中的service方法,首先先获取请求方法,随后根据不同的请求方法来进行不同的操作。
前面说过一共有7种请求方法,HttpServlet类中已经包含了这7种,不仅可以处理GET和POST还可以处理其他五种请求方式。

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
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
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);
} 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);
}

}

小结

  1. HttpServlet的使用步骤

继承HttpServlet
重写doGet和doPost方法

  1. HttpServlet原理

获取请求方式,并根据不同的请求方式,调用不同的doXxx方法

urlPattern配置

Servlet类编写好后,要想被访问到,就需要配置其访问路径(==urlPattern==)

  • 一个Servlet,可以配置多个urlPattern,例如
1
@WebServlet(urlPatterns = {"/demo1","/demo2"})

在浏览器上输入http://localhost:8080/web-demo/demo1,http://localhost:8080/web-demo/demo2这两个地址都能访问到ServletDemo的doGet方法。

  • urlPattern配置规则
    • 精确匹配
    1
    @WebServlet(urlPatterns = "/user/select")
    访问路径http://localhost:8080/web-demo/user/select
    • 目录匹配
    1
    @WebServlet(urlPatterns = "/user/*")
    访问路径http://localhost:8080/web-demo/user/任意
    • 扩展名匹配
    1
    @WebServlet(urlPatterns = "*.do")   //注意这里没加斜杠
    访问路径http://localhost:8080/web-demo/任意.do
    • 任意匹配
    1
    2
    3
    @WebServlet(urlPatterns = "/")  
    //或
    @WebServlet(urlPatterns = "/*")
    访问路径http://localhost:8080/demo-web/任意

注意://*的区别?

  1. 当我们的项目中的Servlet配置了 “/”,会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时都会走这个Servlet

  2. 当我们的项目中配置了"/*",意味着匹配任意访问路径

  3. DefaultServlet是用来处理静态资源,如果配置了"/"会把默认的覆盖掉,就会引发请求静态资源的时候没有走默认的而是走了自定义的Servlet类,最终导致静态资源不能被访问(不必太过担心这种问题,因为我们几乎不会配置这种匹配方式)

小结

  1. urlPattern总共有四种配置方式,分别是精确匹配、目录匹配、扩展名匹配、任意匹配

  2. 五种配置的优先级为 精确匹配 > 目录匹配> 扩展名匹配 > /* > / ,无需记,以最终运行结果为准。

XML配置

前面对应Servlet的配置,我们都使用的是@WebServlet,这个是Servlet从3.0版本后开始支持注解配置,3.0版本前只支持XML配置文件的配置方法。

对于XML的配置步骤有两步:

  • 编写Servlet类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.itheima.web;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;

public class ServletDemo extends MyHttpServlet {

@Override
protected void doGet(ServletRequest req, ServletResponse res) {
System.out.println("demo get...");
}
@Override
protected void doPost(ServletRequest req, ServletResponse res) {
}
}
  • 在web.xml中配置该Servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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">



<!--
Servlet 全类名
-->
<servlet>
<!-- servlet的名称,名字任意-->
<servlet-name>demo13</servlet-name>
<!--servlet的类全名-->
<servlet-class>com.itheima.web.ServletDemo13</servlet-class>
</servlet>

<!--
Servlet 访问路径
-->
<servlet-mapping>
<!-- servlet的名称,要和上面的名称一致-->
<servlet-name>demo13</servlet-name>
<!-- servlet的访问路径-->
<url-pattern>/demo13</url-pattern>
</servlet-mapping>
</web-app>

这种配置方式和注解比起来,确认麻烦很多,所以建议使用注解来开发。但是还是要认识上面这种配置方式,因为并不是所有的项目都是基于注解开发的。