JavaWeb | 七个步骤,完成一个servlet的hello world程序

Servlet 是一种实现动态页面的技术,是一组 Tomcat 提供给程序猿的 API,帮助程序猿简单高效的开发一
个 web app

动态页面 vs 静态页面:

  • 静态页面也就是内容始终固定的页面,即使 用户不同/时间不同/输入的参数不同 , 页面内容也不会发生
    变化 (除非网站的开发人员修改源代码,否则页面内容始终不变)
  • 对应的,动态页面指的就是 用户不同/时间不同/输入的参数不同页面内容会发生变化

构建动态页面的技术有很多,每种语言都有一些相关的库 / 框架来做这件事
Servlet 就是 Tomcat 这个 HTTP 服务器提供给 Java 的一组 API,来完成构建动态页面这个任务

Servlet 主要做的工作:

  • 允许程序猿注册一个类,在 Tomcat 收到某个特定的 HTTP 请求的时候,执行这个类中的一些代码
  • 帮助程序猿解析 HTTP 请求,把 HTTP 请求从一个字符串解析成一个 HttpRequest 对象
  • 帮助程序猿构造 HTTP 响应,程序猿只要给指定的 HttpResponse 对象填写一些属性字段,Servlet
  • 就会自动的安装 HTTP 协议的方式构造出一个 HTTP 响应字符串,并通过 Socket 写回给客户端

简而言之,Servlet 是一组 Tomcat 提供的 API, 让程序猿自己写的代码能很好的和 Tomcat 配合起来,从而更简单的实现一个 web app
而不必关注 Socket,HTTP协议格式,多线程并发等技术细节,降低了 web app 的开发门槛,提高了开发效率


Hello World!

1、创建一个 maven 项目

在这里插入图片描述


2、引入依赖

需要在代码中引入 Servlet api,这个 api 不是 JDK 内置的,而是第三方 (Tomcat 提供的)
Tomcat 相对于 Java 官方来说,仍然属于第三方

借助 maven 直接就能引入,搜索 servlet 第一个就是,

在这里插入图片描述

使用此版本:

在这里插入图片描述

JDK,Tomcat,Servlet 版本要配套,如果版本不太配套,可能就存在一些问题~~
(不是完全用不了,大概率是大部分功能都正常,但是少数功能有 bug )

  • JDK 8
  • Tomcat 8.5
  • Servlet 3.1

maven 默认仓库的位置:

在这里插入图片描述

在这里插入图片描述


3、创建目录结构

  • 虽然当下 maven 帮我们创建了一些目录,但是还不够,还不足以支撑咱们写一个 Servlet 项目,
  • 因此需要手动的再创建一些目录和文件出来~~
    也有更简单的办法,但是此处先不介绍,先用最朴素的方式,手动搞好

——在 main 下创建一个目录 webapp 。Tomcat 可以同时加载多个 webapp 的,因此目录是 webapps 的 (每个 webapp 就相当于一个网站了),因为此处咱们写的是一个 webapp 就没有 s

——第二个目录 WEB-INF

——创建文件 web.xml

在这里插入图片描述

接下来,需要给 web.xml 中写点内容 (不能是空着的)

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

4、编写 Servlet 代码

// 如果代码中找不到 HttpServlet 这个类 说明依赖没有被正确引入
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // super.doGet(req, resp);
    }
}

do:处理
Get 对应到 HTTP 的 GET 方法
这个方法就是在 Tomcat 收到了一个 HTTP GET 请求 的时候,会被 Tomcat 调用到 (回调函数)

HttpServletRequest :代表一个 HTTP 请求 (Tomcat 已经收到了,已经解析成对象了)

HttpServletResponse :代表着 HTTP 响应 (当前这里的 resp 是一个空的响应对象,需要在代码中给这个对象设置一些属性的)

doGet 方法要做的工作,就是根据请求,计算生成响应~~

一个服务器的工作流程,就可以分成三个典型的步骤:

  1. 接收请求并解析
  2. 根据请求计算响应
  3. 构造响应数据,并返回给客户端

其中,1 3 这两步,Tomcat 已经帮我们做好了,这个就是咱们程序猿自己要实现的逻辑,也就是 doGet 要实现的内容~~

服务器想象成一个餐馆~~
服务器上运行的代码,就要处理一个一个的请求~~

当老板收到 “来份炸酱面” 这个请求之后,后厨就行动起来了,根据客户提出的要求,来把这个炸酱面给做出来~

后厨这里,有个师傅负责切菜,有个师傅负责拉面,再有个师傅负责炸酱,再来个师傅把这里的结果给进行集成~~

服务员就可以把这份面端给我了~~

这三个师傅并发的工作,相当于三个线程!!

@WebServlet("/hello")
// 如果代码中找不到 HttpServlet 这个类 说明依赖没有被正确引入
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这个代码一定要干掉! 不能调用父类的 doGet!
        // super.doGet(req, resp);

        // 这是让服务器在自己的控制台里打印
        System.out.println("hello world!");

        // 在页面上也能打印,把 hello world! 字符串放到 http 响应的 body 中,浏览器就会把 body 的内容显示到页面上
        resp.getWriter().write("hello word!" + System.currentTimeMillis());
    }
}

resp 是响应对象
getWriter 其实是返回了一个 Writer 对象 (字符流对象)
此处 Writer 对象就不是往文件里写了,而是往 HTTP 响应的 body 中写入数据
write() 是真正的干活,写数据的方法

1、 @WebServlet("/hello")

把当前的这个 HelloServlet 这个类,和 HTTP 请求中的 URL 里面路径带有 /hello 这样的请求,给关联起来了

  • Tomcat 可能会受到很多种形形色色的请求
    /123 http://123.123.123.123:8080/123
    /aaa http://123.123.123.123:8080/aaa
    /test http://123.123.123.123:8080/test
  • 这么多请求,咱们的代码只管其中的一种情况:/hello http://123.123.123.123:8080/hello
    才会让 Tomcat 来调用 HelloServlet 这个类,来进行处理

2、 保证方法统一

  • /hello 是和 HelloServlet 这个类关联起来了
  • 但是要想执行到 doGet,还得保证方法也是 GET 方法~~
  • 如果是 POST,/hello,仍然无法执行到doGet ~~

5、打包

  • 当前的代码,是不能单独运行的 (没有 main 方法)
  • 需要把当前的代码,打包,然后部署到 Tomcat 上,由 Tomcat 来进行调用

准备工作: 修改 pom.xml~~

jarwar 都是 java 发布程序用的压缩包格式
war 算是给 Tomcat 专门搞的,这里不光会包含一些 .class,还可以包含配置文件,以及依赖的第三方的 jar,还有 html css …

双击这个 package 触发打包

在这里插入图片描述

提示 BUILD SUCCESS 说明打包成功!!

在这里插入图片描述


6、部署

war 包 拷贝到 tomcat 的 webapps 目录下,启动 Tomcat

在这里插入图片描述


7、验证程序

咱们访问 Tomcat 的时候,是需要指定两级目录的

  • Context Path:之所以,咱们的第一级路径叫做 hello102,是因为咱们 war 包名字就是 hello102,war 同时解压缩之后得到的路径也就是 hello102

  • Servlet Path@WebServlet("/hello") 这个注解 ,是和第二级路径相匹配的

可以这样认为:
一个 Tomcat 上可以同时部署多个网站,一个网站上又有多个页面~~
一个请求中的第一级路径,就告诉 Tomcat,要访问的网站是哪个,第二级路径,就告诉 Tomcat 要访问的页面是这个网站中的哪个页面

访问:http://127.0.0.1:8080/hello102/hello

没有成功,此时在 tomcat 控制台这里,敲了一下回车就出来了,显示的 hello world 就是返回的 HTTP 响应的 body 部分

在这里插入图片描述

这个情况是属于 cmd 的一个大坑!!!

CMD 有一个 “选择文本” 这样的模式

鼠标选中其中的一段文本,就会触发 CMD 的选中文本模式~~
当 CMD 被选中文本的时候,CMD 就会把里面运行的程序给挂起 (暂停了,不往下走了)

  • 现在这个页面内容是通过 Java 代码生成的了,但是这和你直接创建一个 HTML,里面写个 hello world 有啥区别呢??
  • 页面里的内容是固定死的,是静态的,是不变的~~
  • 这里的内容是可变的!!
  • 根据用户不同的输入,可以得到不同的结果!!!

修改代码:获取时间戳

resp.getWriter().write("hello word!" + System.currentTimeMillis());

上述这七个步骤,这是针对一个新的项目~~
项目创建好之后,后续修改代码,前三个步骤就不必重复了,直接从 4 - 7 进行操作即可~~
重新部署的时候,不一定要重启,tomcat (理论上来说,是不需要重启的,但是 Windows 系统多少有点不顺畅的地方~~ ,一般在 Linux上,这个都是能顺利自动重新加载的~~)

在这里插入图片描述

如果看到形如这样的提示~~ 就已经重新部署了,deploy:部署

显示结果:hello word!1653073391039


smart tomcat

1、介绍

上面介绍的步骤,其实都是最朴素的操作了~~
因此也有一些办法,来提高上述流程的效率,就可以通过一些第三方工具来简化 5 和 6 的操作,毕竟每次修改代码,都需要重新打包,重新部署~~

咱们是通过 IDEA 上的插件,直接把 Tomcat 给集成进来了~~ 做到 “一键式” 完成打包部署了~~

注意: Tomcat 和 IDEA 是两个独立的程序!!! Tomcat 不是 IDEA 功能的一部分!!

后面开发,主要还是通过 IDEA 调用 Tomcat 的方式来进行
用的时间长了之后,同学们就对于 Tomcat 的印象,就开始模糊~~

在这里插入图片描述

smart tomcat 是 idea 的一个插件
idea 本身虽然已经有很多功能了,但是也不能做到,面面俱到!!
因此,idea 还提供了一系列的扩展能力,允许第三方来开发一些程序,交给 idea 运行,相当于对 idea 进行了扩展~~

  • smart tomcat 并不是真的打包了~~

  • 其实相当于把 webapp 目录作为 tomcat 启动的一个参数,给设定进去了,让 tomcat 直接从这个指定目录中加载 webapp

  • Tomcat 默认是从 webapps 中加载 war 包,也不是只有这一条路~~

  • 因此当你去看 Tomcat 的 webapps 目录,就会发现其实没有你的 war~~(不像手动拷贝)


2、安装

在这里插入图片描述


3、使用 smart tomcat

  • 在一个项目中,首次使用,需要先简单配置,后续就不必再配了~~

  • 当使用 Smart Tomcat 之后,Context Path 就不是 war 包名字了,而是在 Smart Tomcat 配置页面中指定的名字

在这里插入图片描述

点击这里的三角号,就会自动完成打包部署重启 tomcat 这一系列操作

在这里插入图片描述

报错信息:Caused by: java.net.BindException: Address already in use: bind

如果一个端口,已经被服务器绑定了,再次启动一个程序绑定同一个端口,就会出现这个错误!!!

当下存在这个问题,是因为已经在命令行里启动了一个 Tomcat 了
如果在 idea 中再启动一个,显然端口是不能重复占用的

此时就没有问题,这些文字,在 cmd 中乱码,但是在 idea 的终端中就不再乱码了

org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom 使用[SHA1PRNG]创建会话ID生成的SecureRandom实例花费了[103]毫秒。
21-May-2022 03:36:58.905 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
21-May-2022 03:36:58.915 信息 [main] org.apache.catalina.startup.Catalina.start Server startup in 370 ms
http://localhost:8080/hello102

常见出错问题汇总

1、404

你要访问的资源在服务器上不存在

错误 1: 你请求的资源路径写的不对

错误 2: 路径虽然对,但是服务器没有正确把资源加载起来~~

少些了第一级路径 Context Path:

在这里插入图片描述

少写了第二级路径 Servlet Path:

在这里插入图片描述

错误 3: Servlet Path 写的和 URL 不匹配

请求的路径和服务器这边的配置不匹配:

在这里插入图片描述

错误 4: web.xml 写错了

当 web.xml 有问题的时候,tomcat 是不能正确加载到 webapp的~~
tomcat 发现这个目录中存在了 web.xml 且内容正确,tomcat 才能加载这个 webapp


2、405 —— Method Not Allowed

指 HTTP 中的方法

错误 1: 将 doGet 改成 doPost:

在这里插入图片描述

这样的用户操作,浏览器是构造了一个 GET 请求,而服务器这里写的是 doPost,不能处理 GET 请求~~

什么时候浏览器发的是 GET 请求?

  1. 直接在地址栏里,输入 URL

  2. 通过 a 标签跳转

  3. 通过 img/link/script…

  4. 通过 form 表单,method 指定为 GET

  5. 通过 ajax,type 指定为 GET

什么时候浏览器发的是POST请求?

  1. 通过form表单, method指定为POST
  2. 通过ajax, type指定为POST

错误 2: 如果是代码中忘记注释掉 super.doGet(req, resp),这样的情况也会出现 405 !!!

进入到 HttpServlet 源码中,就能看到,此处父类的 doGet,干的事就是直接返回一个 405 的响应!!!

在这里插入图片描述


3、500

500 也是一个非常高频的错误,5 开头,是服务器出了问题
一般 500 就意味着服务器代码里抛出异常了,并且咱们的代码没处理,异常抛到 Tomcat 这里了~~

比如:如果代码中出现异常,可以通过 catch 来捕获到
如果 catch 没捕获到(类型不匹配,压根没 catch),异常就会沿着调用栈,向上传递

如果代码出现 500,就偷着乐吧,这种情况是最好解决的了!! 因为它直接告诉你,哪里有问题了!!!

在这里插入图片描述

异常的调用栈 (平时写代码的时候,IDEA 里出现的异常调用栈),告诉了我们出现异常的准确位置


4、空白页面

如果不给响应对象中设置任何内容,就会出现 空白页面


5、无法访问此网站

  • 如果是这个情况,证明,网络就不通 (TCP 的层次上就是不通)
  • 其中一个很大的可能性,就是 Tomcat 没有正确启动 (比如 Tomcat 端口被占用、启动失败……)

在这里插入图片描述


Servlet 运行原理

Servlet 是属于上层建筑,下面的传输层,网络层,数据链路层… 属于经济基础

在这里插入图片描述

  • 当浏览器给服务器发送请求的时候,Tomcat 作为 HTTP 服务器,就可以接收到这个请求

HTTP 协议作为一个应用层协议,需要底层协议栈来支持工作,如下图所示:

Tomcat 其实是一个应用程序,运行在用户态的普通进程 (Tomcat 其实也是一个 Java进程)
用户写的代码 (根据请求计算相应),通过 Servlet 和 Tomcat 进行交互~~
Tomcat 进一步的和浏览器之间的网络传输,仍然是走的网络原理中的那一套:封装和分用

在这里插入图片描述

更详细的交互过程可以参考下图:

在这里插入图片描述

  1. 接收请求:

    • 用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求.
    • 这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit 流, 最终通过物理层的硬件设备转换成光信号/电信号传输出去.
    • 这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达目标主机(这个过程也需要网络层和数据链路层参与).
    • 服务器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成HTTP 请求. 并交给 Tomcat 进程进行处理(根据端口号确定进程)
    • Tomcat 通过 Socket 读取到这个请求(一个字符串), 并按照 HTTP 请求的格式来解析这个请求, 根据
      请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类. 再根据当前请
      求的方法 (GET/POST/…), 决定调用这个类的 doGet 或者 doPost 等方法. 此时我们的代码中的
      doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息
  2. 根据请求计算响应:

    • 在我们的 doGet / doPost 方法中, 就执行到了我们自己的代码. 我们自己的代码会根据请求中的一
      些信息, 来给 HttpServletResponse 对象设置一些属性. 例如状态码, header, body 等.
  3. 返回响应:

    • 我们的 doGet / doPost 执行完毕后, Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串, 通过 Socket 把这个响应发送出去.
    • 此时响应数据在服务器的主机上通过网络协议栈层层 封装, 最终又得到一个二进制的 bit 流, 通过物理层硬件设备转换成光信号/电信号传输出去.
    • 这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达浏览器所在的主机(这个过程也需要网络层和数据链路层参与).
    • 浏览器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成HTTP 响应, 并交给浏览器处理.
    • 浏览器也通过 Socket 读到这个响应(一个字符串), 按照 HTTP 响应的格式来解析这个响应. 并且把body 中的数据按照一定的格式显示在浏览器的界面上.

浏览器和服务器之间交互数据,这个过程中是否会涉及到 TCP 三次握手,确认应答…,是否会涉及到IP的分包组包… 是否会涉及到以太网的MTU 呢…

都是会的!!!


Tomcat 的伪代码

  • 下面的代码通过 “伪代码” 的形式描述了 Tomcat 初始化 / 处理请求 两部分核心逻辑
  • 所谓 “伪代码”,并不是一些语法严谨,功能完备的代码,只是通过这种形式来大概表达某种逻辑

1、Tomcat 初始化流程

  • Tomcat 的代码中内置了 main 方法, 当我们启动 Tomcat 的时候, 就是从 Tomcat 的 main 方法开始执行的,
  • 被 @WebServlet 注解修饰的类会在 Tomcat 启动的时候就被获取到,并集中管理,

1). 让 Tomcat 先从指定的目录中找到所有要加载的 Servlet 类~~

前面部署的时候,是把 Servlet 代码编译成了 .class,然后打了 war 包,然后拷贝到了 webapps 里面。Tomcat 就会从 webapps 里来找到哪些 .class 对应的 Servlet 类,并且需要进行加载 (当然啦,这里的加载不一定是 Tomcat 一启动就立即执行,也可能是 “懒加载”
但是此处伪代码中就假设立即加载了~~)

在这里插入图片描述

2). 根据刚才类加载的结果,给这些类创建 Servlet 实例~~

// 这里要做的的是实例化出所有的 Servlet 对象出来;
for (Class<Servlet> cls : allServletClasses) {
    // 这里是利用 java 中的反射特性做的
    // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
    // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
    // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
    Servlet ins = cls.newInstance(); // 反射
    instanceList.add(ins);
}

3). 实例创建好之后,就可以调用当前 Servlet 实例的 init 方法了

Servlet 自带的方法,默认情况下 init 啥都不干
咱们在继承一个 HttpServlet 的时候,也可以自己重写 init,就可以在这个阶段,帮我们做一些初始化工作了

// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
	ins.init();
}

4). 创建 TCP socket,监听 8080 端口,等待有客户端来连接

Tomcat 为了能同时相应多个 HTTP 请求, 采取了多线程的方式实现, 因此 Servlet 是运行在 多线程环境 下的, Tomcat 内部也是通过 Socket API 进行网络通信,

每次有连接过来了,accept 就会返回,然后就给当前的线程池里,加个任务
doHttpRequest(socket); 这个任务里,就要负责处理这个请求了

此处这个主循环,和咱们之前讲过的 TCP 回显服务器,本质上没啥区别
Tomcat 工作的大部分时间都是在这个循环里完成的~~

// 利用我们之前学过的知识,启动一个 HTTP 服务器
// 并用线程池的方式分别处理每一个 Request
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);

while (true) {
    Socket socket = ServerSocket.accept();
    // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
    pool.execute(new Runnable() {
        doHttpRequest(socket);
    });
}

5). 如果循环退出了,Tomcat 也要结束了,就会依次循环调用每个 Servlet 的 destroy 方法

这个是属于收尾工作 (Tomcat 退出之前的事情)

// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
    ins.destroy();
}

没有 break 为啥还能走到下面的代码?
那是因为咱们当下写的是一个伪代码,只包含了核心逻辑,没有太多的实现细节~~
实际上 Tomcat 里面会有一些条件来退出这个主循环
和 init 类似,这里的 destroy 默认也是啥都不干的,可以在用户代码中重写这个 destroy ~~

虽然这里有 5) 这个调用 destroy 这个环节,但是这个环节不一定可靠~~
如果 Tomcat 是通过 “正常流程" 退出的,才能主动结束循环,调用这里的 destroy。如果是通过 “非正常流程" 退出的,此时就来不及调用 destroy

比如,Tomcat,8005 端口 (管理端口,通过这个端口可以对这个 Tomcat 发号施令),通过这个方式来关闭 Tomcat,就属于 “正常流程"

非正常流程:直接结束进程 (大部分是这种情况)

class Tomcat {
    // 用来存储所有的 Servlet 对象
    private List<Servlet> instanceList = new ArrayList<>()

    public void start() {
        // 根据约定,读取 WEB-INF/web.xml 配置文件;
        // 并解析被 @WebServlet 注解修饰的类

        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
        Class<Servlet>[] allServletClasses = ...;

        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
            // 这里是利用 java 中的反射特性做的
            // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
        }

        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.init();
        }

        // 利用我们之前学过的知识,启动一个 HTTP 服务器
        // 并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);

        while (true) {
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
                doHttpRequest(socket);
            });
        }

        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.destroy();
        }
    }
        
    public static void main(String[] args) {
        new Tomcat().start();
    }
}

2、Tomcat 处理请求流程

class Tomcat {
    void doHttpRequest(Socket socket) {
        // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket); // 1) 
        HttpServletRequest resp = HttpServletRequest.build(socket);
        
        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
        // 直接使用我们学习过的 IO 进行内容输出
        if (file.exists()) { // 2)
            // 返回静态内容
            return;
        }
        
        // 走到这里的逻辑都是动态内容了
        
        // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
        // 最终找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL()); // 3)
        
        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
            ins.service(req, resp); // 4)
        } catch (Exception e) {
            // 返回 500 页面,表示服务器内部错误
        }
    }
}

1). req :是通过读取 socket 中的数据,然后再按照 HTTP 协议的请求格式来解析的,构造成了一个 HttpServletRequest 对象
resp :这里则是相当于 new 了一个空的对象

2). 是判定当前要请求的资源是否是静态文件~~
如果是静态文件,就读取文件内容,把文件内容构造到 resp 对象的 body 中,并且返回这个 resp 对象

例如可以再 webapp 目录中,创建一个静态文件 test.html,启动,访问:http://127.0.0.1:8080/hello102/test.html

3). 根据请求的 URL,来获取到使用哪个类来处理
URL 里要有两级路径:

  • 第一级路径:Context Path,确定一个 webapp

  • 第二级路径:Servlet Path,确定一个 Servlet 类,如果没有找到匹配的 Servlet 类,就会返回 404

4). 根据刚才找到的 Servlet 对象,来调用 service 方法~~ 在 service 方法内部,又会进一步的调用 doGet / doPost


3、Servlet 的 service 方法的实现

如果在执行 Servlet 的 service 方法过程中出现未处理的异常就会返回500

class Servlet {
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        String method = req.getMethod();
        if (method.equals("GET")) {
            doGet(req, resp);
        } else if (method.equals("POST")) {
            doPost(req, resp);
        } else if (method.equals("PUT")) {
            doPut(req, resp);
        } else if (method.equals("DELETE")) {
            doDelete(req, resp);
        }
        ......
    }
}

在讨论到上面这整套流程过程中,涉及到了关于 Servlet 的关键方法,主要有三个:

  • init:初始化阶段,对象创建好了之后,就会执行到,用户可以重写这个方法,来执行一些初始化逻辑
  • service:在处理请求阶段来调用,每次来个请求都要调用一次 service
  • destroy:退出主循环,tomcat 结束之前会调用,用来释放资源

他们的调用时机就称 Servlet 的生命周期,生命周期这个词是计算机里一个挺常见的术语 “啥时候,该做啥事”

Servlet 的 service 方法内部会根据当前请求的方法,决定调用其中的某个 doXXX 方法
在调用 doXXX 方法的时候,就会触发 多态 机制,从而执行到我们自己写的子类中的 doXXX 方法

理解此处的 多态

  • 我们前面自己写的 HelloServlet 类, 继承自 HttpServlet 类. 而 HttpServlet 又继承自 Servlet. 相当于 HelloServlet 就是 Servlet 的子类.

  • 接下来, 在 Tomcat 启动阶段, Tomcat 已经根据注解的描述, 创建了 HelloServlet 的实例, 然后把这个实例放到了 Servlet 数组中.

  • 后面我们根据请求的 URL 从数组中获取到了该 HelloServlet 实例, 但是我们是通过 Servlet ins这样的父类引用来获取到 HelloServlet 实例的.

  • 最后, 我们通过 ins.doGet() 这样的代码调用 doGet 的时候, 正是 “父类引用指向子类对象”, 此时就会触发多态机制, 从而调用到我们之前在 HelloServlet 中所实现的 doGet 方法

  • 等价代码:

    Servlet ins = new HelloServlet();
    ins.doGet(req, resp);
    

评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三春去后诸芳尽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值