JavaWeb-HttpServlet源码分析

发布于:2025-03-06 ⋅ 阅读:(11) ⋅ 点赞:(0)

HttpServlet源码分析

在我们之前的创建Servlet对象的过程中, 经历了很多个阶段, 但是今天我们的这个版本是最终的版本了, 这个代表着Servlet的对象的真正的开发流程

已知Servlet规范

截至目前, 学习过的已知的Servlet规范如下

jakarta.servlet.*:

  • Servlet(接口)
  • ServletConfig(接口)
  • ServletContext(接口)
  • ServletRequest(接口)
  • ServletResponse(接口)
  • ServletException(异常类)
  • GenericServlet(抽象类)

本节学习的规划是在jakarta.servlet.http包下的, 也就是专门针对BS架构的开发

  • HttpServlet(抽象类)
  • HttpServletRequest(接口)
  • HttpServletResponse(接口)

实现了下面接口的对象在使用http协议通信的过程中发挥重要作用, 前者是把所有的Http请求的请求的格式已经相关内容都封装起来, 而后者是响应的主要的内容已经相关格式的封装


我们最终的创建类的继承关系是, 继承抽象类HttpServlet因为这个更适合我们做web相关的开发工作


使用Servlet对象生命周期分析源码

上面说了, 我们都是继承HttpServlet来实现Servlet对象的, 那现在我们根据Servlet对象分析生命周期的方式分析一下HttpServlet的部分源码

先前介绍过 Servlet 对象的生命周期, 通过下图回忆一下

在这里插入图片描述

调用无参构造方法构造对象

在这里插入图片描述

我们提供一个无参数的构造方法, tomcat底层new对象之后, 会执行这个无参数构造方法里的内容


使用init(ServletConfig config)方法初始化对象信息

因为现在这个类自身没有这个方法, 就会调用HttpServlet 中的这个方法
如下图所示
在这里插入图片描述
HttpServlet这个类实际上是去调用的其父类GenericServlet中的inti方法

在这里插入图片描述


调用service方法提供服务

通过观察分析可得, HttpServlet这个抽象类中有两个service方法

  • void service(ServletRequest req, ServletResponse res)
  • void service(HttpServletRequest req, HttpServletResponse resp)

其中下面的这个service方法是从父类GenericServlet中继承的抽象方法

在这里插入图片描述

观察上面的源码可以了解到, 调用这个方法的本质, 其实还是调用了另一个参数的service方法

我们tomcat内部一定是调用的这个不带http的参数的service方法

在这里插入图片描述
关于另一个service方法我们没有截完, 但是大致都在这里了
可以看到, 其实另一个service方法其实根据传递过来的请求的类型来调用相关的方法, 比如doGet, doPost, doDelete,

这其实就是一种模板方法设计模式, 在模板类中设计好模板方法, 具体的实现交给子类去进行完成


destroy销毁对象

这个方法没啥可说的, 一般都是调用父类的实现…


模板方法设计模式&do系列方法解析

  • 我们的模板方法设计模式通常有一个模板类, 一个模板方法, 其中模板方法规定了大部分操作实现的系统骨架, 而不给出非常具体的实现, 具体的实现交给子类通过继承父类的相关方法来完成

比如上面的 HttpServlet 这就是一个模板类, service 就是一个模板方法, service方法中给出了大概实现的基本骨架, 具体的do系列 方法, 通过子类进行重写实现具体的操作…

service方法源代码如下

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;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    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);
        }

    }


关于 doGet 方法在 HttpServlet 中的默认实现如下

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

可以发现, 这里的默认实现发送了一条消息
"http.method_get_not_supported"
这其实就是 405错误码(method not allowed)方法不允许

在这里插入图片描述

这个错误码的含义是, 方法不匹配, 假设前端发送了一个 Get 请求, 而我们的继承HttpServletServlet类对象没有实现doGet方法, 就会默认的调用这个doGet 方法, 从而触发一个错误, 也就是方法不允许…

顺便提一下, 目前我们已知前端只有通过form表单发送post只是唯一post请求的方式

可见, 后端如何控制前端发送的请求方式呢, 正是通过重写do系列相关方法实现的, 假设允许前端发送Get请求, 那我们就实现doGet, 假设允许前端发送Post请求, 那我们就重写doPost


为什么不把do系列方法都重写呢?

我们通常不会这样做, 虽然这样做确实避免了报405错误, 但是我们后端通常还是希望制约前端发送的请求的, 而不是单纯的为了不报错而全部修改一通…


构建一个Servlet的完整步骤(终版)

我们的构造一个Servlet对象的终极方案就是
创建一个类继承HttpServlet抽象类, 重写其中的do系列 方法即可…


测试:

测试代码结构如下(静态资源一定在web目录下)
在这里插入图片描述

我们给一个html用于前端向后端提交数据(通过前端的超链接标签发送请求都加上项目名)

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>新增部门</title>
</head>
<body>
<h2>新增部门</h2>
<form action='/servlet08/hello' method='get'>
    部门编号<input type='text' name='deptno'/><br>
    部门名称<input type='text' name='deptname'/><br>
    部门位置<input type='text' name='loc'/><br>
    <input type='submit' value='提交'><br>
</form>
</body>
</html>

给一个Servlet对象(通过注解避开配置web.xml这一块以后再说)

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;
// 通过注解的方式取代web.xml进行开发(以后会仔细分析)
@WebServlet(urlPatterns="/hello")
public class HelloServlet extends HttpServlet {

    // 重写doGet方法
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置返回的格式以及获取输出流
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("<h1>Hello World</h1>");
    }
}

启动tomcat服务器, 打开浏览器输入url

在这里插入图片描述

在这里插入图片描述


假设我们把此时的前端的html页面的Get请求替换为Post请求
因为我们没有重写doPost, 所以就会报405错误

测试结果如下

在这里插入图片描述

此时只有继续重写doPost 方法才不会报405错误

关于站点的欢迎页

什么是欢迎页

我们通过浏览器访问资源的时候

  • 一般通过 http://ip:port/项目/资源路径 的方式获取
  • 我们的欢迎页访问方式是http://ip:port/项目 的方式访问

欢迎页相当于一个站点的门头

如何配置一个静态资源欢迎页

web.xml文件中配置一个欢迎页

在这里插入图片描述

我们配置了两个欢迎页(welcome.html, hello.html)

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>hello欢迎页</title>
</head>
<body>
<h2>hello欢迎页</h2>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta charset='UTF-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>
  <title>welcome欢迎页</title>
</head>
<body>
<h2>welcome欢迎页</h2>
</form>
</body>
</html>

在web.xml中的配置如下

<?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">

    <!--通过 welcome-file-list 标签进行访问-->
    <!--注意, 欢迎页的路径是自动从web的根路径下寻找的, 所以不需要加上/作为开头-->
    <welcome-file-list>
        <welcome-file>welcome.html</welcome-file>
        <welcome-file>hello.html</welcome-file>
    </welcome-file-list>
</web-app>

关于欢迎页的路径配置
不用加上/作为开头, 因为默认的扫描路径是web根节点

欢迎页是可以设置多个的, 优先级从上到下依次扫描, 扫描到哪一个资源存在就执行这个欢迎页面


测试:

输入http://127.0.0.1:8080/servlet08

在这里插入图片描述
因为这一个欢迎页的更靠上所以优先级越高…

配置Servlet作为欢迎页

欢迎页也是一种资源, 我们也可以请求Servlet对象获取资源充当欢迎页

我们构造一个Servlet对象如下

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.*;

// 还是通过注解配置Servlet对象资源
@WebServlet(urlPatterns = "/welcomepage")
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置输出格式, 获取输出流
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        
        // 输出欢迎页的内容
        out.print("<h1>welcome page!</h1>");
    }
}

我们的欢迎页的配置信息如下

路径就是把Servlet对象的路径信息去掉前面的/

<!--通过 welcome-file-list 标签进行访问-->
    <!--注意, 欢迎页的路径是自动从web的根路径下寻找的, 所以不需要加上/作为开头-->
    <welcome-file-list>
        <welcome-file>welcomepage</welcome-file>
        <welcome-file>welcome.html</welcome-file>
        <welcome-file>hello.html</welcome-file>
    </welcome-file-list>

在这里插入图片描述

可以看到, 此时再次访问欢迎页就是使用的Servlet对象提供的资源


关于默认欢迎页

  • 如果我们在web根目录下有一个index.html页面, 即使我们不在 web.xml 中配置欢迎页相关的信息, 仍然将这个页面作为默认的欢迎页, 因为在tomcat中有相关的配置信息

在这里插入图片描述

在tomcat服务器中的相关配置中有关于默认欢迎页的配置

在这里插入图片描述

可以看到这就是默认欢迎页的配置

我们做一下测试(给一个index.html, 但是不给web.xml配置)

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>默认的index - welcome欢迎页</title>
</head>
<body>
<h2>默认的index - welcome欢迎页</h2>
</form>
</body>
</html>

注意, 此时我们并没有配置web.xml文件中的欢迎页

测试结果如下, 可以看到, 默认的欢迎页面起作用了…

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到