【JavaWeb】Servlet(一)——原理篇

发布于:2025-06-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

参考笔记:

JavaWeb——Servlet(全网最详细教程包括Servlet源码分析)-CSDN博客

JavaWeb 速通Servlet(Servlet和HttpServlet)-CSDN博客

PS:本文不涉及太多 Servlet 开发案例,主要是讲解 Servlet 的底层原理


一、Servlet快速入门

        1. Servlet的工作机制GIF演示

        此 GIFCSDN 博主:"刘扬俊",做的非常好。动画中左下角为客户端(浏览器),其发送给服务器的是 HTTP 请求报文

         

        2. 为什么需要Servlet

        当浏览器向 Web 服务器请求静态资源,例如 HTML、CSS、JS、图片等时,Web 服务器可以对请求进行解析后,拿到资源,以响应的方式直接返回给浏览器

        但是,当浏览器发出动态地与用户进行交互的请求时,比如用户留言,用户评论等直接与数据库挂钩的需求,就需要 Servlet 去调用其他 Java 程序来实现,Java 程序通常会分层(Service,DAO等)。此时 Tomcat 作为 Servlet 容器,能够完成对 Servlet 的解析

        3. 什么是Servlet

        Servlet  是运行在服务端的 Java 小程序,是 SpringMVC、SpringBoot 的底层基础。特点如下——

        在整个 Web 应用中,Servlet 主要负责接收处理请求、协同调度功能以及响应数据给浏览器,可以把 Servlet 称为 Servlet Servlet 应用中的控制器

        ② 由 Servlet 容器调用和执行(Tomcat 就是 Servlet 容器的一种)

         Servlet 由 Java 语言编写,本质就是 Java 类。但他不像普通的 .java 文件可以直接运行,Servlet 必须在 Web 项目中开发且在例如像 Tomcat 这样的 Servlet 容器中才能运行

        4. 什么是Servlet容器

        关于什么是 Servlet  容器,我觉得是非常有必要搞清楚的,这样对整个 Servlet 技术的理解更加透彻。可以看我的另一篇博客:

Servlet容器(Web容器)简介-CSDN博客文章浏览阅读676次,点赞19次,收藏8次。(本文的内容基本转载自该文章,写的很好,非常推荐大家看一下)JavaWeb项目中,我们编写的Servlet类没有main()函数,不能独立运行,那我们如何启动一个Servlet?如何结束一个Servlet?如何寻找一个Servlet?这一切都受控于另一个Java应用,这个应用我们就称之为Servlet容器,也称为Web容器。Servlet容器帮助我们管理着Servlet,使我们只需要将重心专注于业务逻辑在介绍Servlet。 https://blog.csdn.net/m0_55908255/article/details/148797641?spm=1011.2415.3001.5331

        5. Servlet的工作模式 

  • 客户端发送请求至服务器

  • 服务器启动并调用 Servlet ,Servlet  根据客户端请求生成响应内容并将其传给服务器

  • 服务器将响应返回客户端

        6. 快速入门案例

package com.atguigu.servlet;

import javax.servlet.ServletException;
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("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletB正在提供服务");
        //..业务逻辑
    }
}

        请求结果: 

         可以看到,我们编写的 Servlet 类需要去 extends HttpServlet,并且重写该方法:

protected void service(HttpServletRequest request, HttpServletResponse response)

        但是为什么要  extends HttpServlet 呢?为什么又要重写上面这个 service() 方法呢?这就要好好捋一捋整个 Servlet 技术了

Servlet的主要类型


Servlet的使用方法


Servlet 技术的核心是 Servlet  接口,它是所有 Servlet  类必须直接或者间接实现的一个接口

Servlet的工作原理


        Servlet 接口定义了 Servlet 与 Servlet 容器之间的规则。这个规则是:Servlet 容器将 Servlet 类从磁盘载入内存,并产生 Servlet 实例和调用它具体的方法。但是要注意的是,在一个应用程序中,每个 Servlet 只会有一个实例,遵循单例设计模式

        用户请求致使 Servlet 容器调用 Servlet service()方法,并传入一个 ServletRequest 对象和一个 ServletResponse 对象。ServletRequest 对象和 ServletResponse 对象都是由 Servlet 容器封装好的,并不需要程序员去实现,程序员可以直接使用这两个对象

        ServletRequest 中封装了当前的请求信息,ServletResponse 中封装了返回给客户端的响应信息,程序员只需直接操作 ServletResponse 对象就能把响应轻松的发回给客户端

        对于每一个 JavaWeb 应用程序,Servlet 容器还会创建一个 ServletContext 对象。  ServletContext 中封装了项目上下文路径等信息,每个 JavaWeb 应用程序只有一个 ServletContext 

        对于每一个 Servlet 对象都有一个对应的 ServletConfig 对象,ServletConfig 中封装了该 Servlet 的一些配置信息

Servlet接口中定义的方法


        前文说到,"Servlet 技术的核心是 Servlet 接口,它是所有 Servlet 类必须直接或者间接实现的一个接口"

        让我们来看一看 Servlet 接口中定义了哪些方法

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

Servlet的生命周期


        Servlet 的生命周期,就是 Servlet 从创建对象到销毁的过程。其中:init()、service() 、destroy()Servlet 生命周期的方法,代表 Servlet 从 "出生" 到 "工作" 再到 "死亡" 的过程。 Servlet 容器会根据如下规则来调用这 3 个方法:

        init( ):当该 Servlet 第一次被请求时,Servlet 容器就会先调用 Servlet 的构造器创建 Servlet 对象,再调用 init() 对 Servlet 对象作初始化工作。init () 在后续请求中不会再被 Servlet 容器调用,就像人只能 "出生"一次一样。调用 init() 时,Servlet 容器还会传入一个 ServletConfig 对象

        service( ):每当请求 Servlet 时,Servlet 容器就会 service() 。就像人一样,需要不停的接受老板的指令并且 "工作" 。第一次请求时,Servlet 容器会先调用构造器 + init( ) 创建并初始化一个 Servlet 对象,然后调用它的 service( ) 方法进行工作,但在后续的请求中,Servlet 容器只会调用 service() 了

        destory( ):当要销毁 Servlet 时,Servlet 容器就会调用 destory(),就如人一样,到时期了就得死亡。在卸载应用程序或者关闭 Servlet 容器时,就会发生这种情况,一般在这个方法中会写一些清除代码

生命周期 对应方法 执行时机 执行次数
构造对象 构造器

第一次请求或者容器启动时

(如果该Servlet配置了<load-on-startup>整数n</load-on-startup>,且n>0,则容器启动时就会调用构造器+init())

1
初始化 init() 构造完毕后(构造函数执行结束后) 1
处理服务 service(ServletRequest req,ServletResponse resp) 每次请求 多次
销毁 destory() 容器关闭 1

生命周期测试

        可以用以下代码简单测试一下 Servlet 的生命周期:

@WebServlet("/servletLifeCycle")
public class ServletLifeCycle implements Servlet {

    public ServletLifeCycle() {
        System.out.println("构造器");
    }
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("Servlet正在初始化");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Servlet正在提供服务");
    }

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

    @Override
    public void destroy() {
        System.out.println("Servlet正在销毁");
    }
}

        第一次访问该 Servlet 时,控制台输出以下结果:

        再连续访问该 Servlet ,控制台输出如下:

        可以看到,每个 Servlet 确实只有一个实例

        接着,我们关闭 Servlet 容器,控制输出:

Servlet接口的其他两个方法


getServletInfo():这个方法几乎用不到,不用管它

getServletConfig():这个方法在后续实现了 Servlet 接口的 GenericServlet 抽象类中会有具体实现,可以返回该 Servlet 对应的 ServletConfig 对象

ServletRequest接口


        Servlet 容器对于接受到的每一个请求,都会创建一个 ServletRequest 对象,并把这个对象传递给 Servlet 的 service() 方法。其中,ServletRequest 对象内封装了关于这个请求的许多详细信息

void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

        下面是 ServletRequest 接口的部分方法:

public interface ServletRequest {
   
    String getParameter(String var1);//获取请求参数
    void setCharacterEncoding(String var1) throws UnsupportedEncodingException;//设置请求的编码方式
    String getScheme();//获取请求协议
}

ServletResponse接口


        ServletResponse 接口表示一个 Servlet 响应,在调用 Servlet 的 service() 方法前,Servlet 容器在创建 ServletRequest 对象的同时也会创建一个 ServletResponse 对象,把它作为 Service()的第 2 个参数

void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

        ServletResponse 隐藏了向浏览器发送响应的复杂过程。下面是 ServletResponse 接口的部分方法:

public interface ServletResponse {
    
    void setContentLength(int var1);//设置响应体的字节数
    
    void setContentType(String var1);//设置响应体类型和字符编码
    
    ...
}

ServletConfig接口


        当 Servlet容器初始化 Servlet 时,Servlet容器会给 Servlet 的 init( ) 方式传入一个ServletConfig 对象

    void init(ServletConfig var1) throws ServletException;

        ServletConfig 接口的几个方法如下:

public interface ServletConfig {
    String getServletName();//获得 Servlet 在 web.xml 中配置的 name

    ServletContext getServletContext();//获取ServletContext对象

    String getInitParameter(String var1);//获取 Servlet 在 web.xml 中配置的init param(初始化参数)

    Enumeration<String> getInitParameterNames();//获取所有初始化参数名组成的Enumeration对象
}

         在 web.xml  给单个 Servlet 配置初始参数的方法:

<?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-name>ServletA</servlet-name>
        <servlet-class>com.atguigu.servlet.ServletA</servlet-class>
        <!--配置ServletA的初始参数-->
        <init-param>
            <param-name>param1</param-name>
            <param-value>value1</param-value>
        </init-param>
        <init-param>
            <param-name>param2</param-name>
            <param-value>value2</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletA</servlet-name>
        <url-pattern>/servletA</url-pattern>
    </servlet-mapping>

</web-app>

ServletContext接口


        每个 JavaWeb 应用程序都只有一个 ServletContext 对象,通过在 ServletConfig 中调用  ServletContext 方法,也可以获得 ServletContext 对象

        为什么要存在一个 ServletContext 对象呢?因为 ServletContext 对象是所有 Servlet 共享的,可以为所有的 Servlet 提供初始配置参数,并且可以在程序运行期间动态添加新的 Servlet 

        ServletContext 接口的部分方法如下:

public interface ServletContext {

    String getContextPath();//获取项目的上下文路径

    String getInitParameter(String var1);//获取为所有Servlet配置的初始参数,var1为<param-name>,返回值是<param-value>

    Enumeration<String> getInitParameterNames();//获取所有<param-name>组成的Enumeration对象(类似一个迭代器)
	
    //ServletContext也称为应用域,getAttribute、setAttribute、removeAttribute是域相关的3个API
    Object getAttribute(String var1);

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);

    ServletRegistration.Dynamic addServlet(String var1, String var2);//动态添加Servlet
    
    ....
}

        在 web.xml 中为所有 Servlet 配置初始参数的方法如下:

<?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配置初始参数-->
    <context-param>
        <param-name>paramA</param-name>
        <param-value>valueA</param-value>
    </context-param>
    <context-param>
        <param-name>paramB</param-name>
        <param-value>valueB</param-value>
    </context-param>
</web-app>

GenericServlet抽象类


        前面我们在编写 Servlet 时一直是通过实现 Servlet 接口来编写的,但是使用这种方法,则必须要实现 Servlet 接口中定义的所有方法,即使有一些方法中没有任何东西也要去实现,并且还需要自己手动维护 ServletConfig 这个对象的引用,因为 Servlet 接口中有一个返回 ServletConfig 对象的方法:

         因此,这样去编写 Servlet 其实是相当麻烦的,对程序员也不友好

        后来 GenericServlet 抽象类的出现很好的解决了这个问题。本着尽可能使代码简洁的原则,GenericServlet 实现了 Servlet 和 ServletConfig  接口:

        下面是 GenericServlet 抽象类的具体代码:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String message) {
        this.getServletContext().log(this.getServletName() + ": " + message);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

         在前文我们提到,"Servlet技术的核心是Servlet接口,它是所有Servlet类必须直接或者间接实现的一个接口"

        所以相比于去实现 Servlet 接口,我们编写的 Servlet 类去 extends GenericServlet 抽象类有以下几个好处:

        ① GenericServlet 抽象类中为 Servlet 接口中的所有方法提供了默认的实现,程序员需要什么就直接改什么,不再需要把所有的方法都自己实现了

        ②GenericServlet 抽象类完成对 ServletConfig 对象的引用,不再需要程序员自己去维护 ServletConfig

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private transient ServletConfig config;
	....
	
	//获取Servlet的ServletConfig对象
    public ServletConfig getServletConfig() {
        return this.config;
    }
    
    //Servlet的初始化方法
    public void init(ServletConfig config) throws ServletException {
        this.config = config;//保存ServeltConfig
        this.init();
    }
    
	public void init() throws ServletException {
    
    }
    ....
}

         另外,在这里可以发现,在 GenericServlet 抽象类中有一个没有任何参数、没有任何实现的 init() 方法。该方法的被调用的时机是在 Servlet 的初始化方法 init(ServletConfig config) 中最后一行调用的,很多人可能会觉得很奇怪,设计一个无参的 init() 有意义吗?

        有的兄弟,有的。我们编写的 Servlet 类 ServletA 会 extends GenericServlet,所以  GenericServlet 抽象类中的 init(ServletConfig config) 自然会被继承到 ServletA 中。如果程序员觉得在 ServletA 初始化时有其他功能要添加,不需要去重写 init(ServletConfig config),只需要重写 init() 即可。这样又能保证程序员只需要专注于业务需求,也能使得代码更加简洁

        另外,需要注意的是,我们编写的 Servlet 类 去 extends GenericServlet 时,必须要重写 service(ServletRequest var1,ServletResponse var2) 方法,因为在 GenericServlet  抽象类中并没有实现,只是被定义为一个抽象方法,如下:

public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

        说了这么多,我们来写个具体的案例吧

package com.atguigu.servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet(value = "/servletB",//访问路径
        initParams = {@WebInitParam(name = "encoding",value = "UTF-8"),//配置初始参数
                      @WebInitParam(name = "hobby",value = "ball")})
public class ServletB extends GenericServlet {

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("ServletB正在提供服务");

        //获取ServletConfig对象
        ServletConfig servletConfig = this.getServletConfig();

        //获取ServletContext对象
        ServletContext servletContext = servletConfig.getServletContext();


        String servletName = servletConfig.getServletName();//获取Servlet名称

        String encoding = servletConfig.getInitParameter("encoding");//获取Servlet的初始参数
        String hobby = servletConfig.getInitParameter("hobby");//获取Servlet的初始参数

        String contextPath = servletContext.getContextPath();//获取项目的上下文路径

        System.out.println("servletName:" + servletName);
        System.out.println("encoding:" + encoding);
        System.out.println("hobby:" + hobby);
        System.out.println("contextPath:" + contextPath);

    }
}

        结果: 

        虽然 GenericServlet 是对 Servlet 一个很好的加强,但是也不经常用,因为它不像  HttpServlet 那么高级。HttpServlet 才是 JavaWeb 中的主角,在现实的应用程序中被广泛使用。接下来就看看传说中的 HttpServlet 到底厉害在哪里

javax.servlet.http包内容


        HttpServlet 要比 GenericServlet 功能强大的多得多,它是在 GenericServlet 抽象类扩展而来的,HttpServlet 抽象类的声明如下:

public abstract class HttpServlet extends GenericServlet

        而 GenericServlet 抽象类的声明如下: 

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable

        所以 HttpServlet 也是间接实现了 Servlet、ServletConfig、Serializable接口

         HttpServlet 之所以运用广泛的另一个原因是现在大部分的应用程序都要与 HTTP 协议结合起来使用,而 HttpServlet 是针对 HTTP 协议定制的 servlet 实现,能够处理 GET、POST、PUT、DELETEHTTP 请求,与之配套使用的还有 HttpServletRequest、HttpServletResponse、HttpSession、Cookie

        HttpServlet 位于 javax.servlet.http 包中,该包中的很多类型都覆盖了 javax.servlet 包中的类型

        所以,有了 HttpServlet 抽象类,我们编写 Servlet 类要 extends HttpServlet

HttpServlet抽象类


        HttpServlet 抽象类是继承于 GenericServlet 抽象类而来的。使用 HttpServlet 抽象类时,还需要借助分别代表 HTTP 请求和 HTTP 响应的 HttpServletRequestHttpServletResponse 对象

        HttpServletRequest 接口继承 javax.servlet.ServletRequest 接口,HttpServletResponse 接口继承 javax.servlet.servletResponse 接口

public interface HttpServletRequest extends ServletRequest
public interface HttpServletResponse extends ServletResponse

        HttpServlet 抽象类中重写了 GenericServlet 抽象类中的 service(ServletRequest var1, ServletResponse var2) 方法,并且添加了一个自己独有的 service (HttpServletRequest request,HttpServletResponse) 方法

        首先来看 GenericServlet 抽象类中定义的 service(ServletRequest var1, ServletResponse var2) :

public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

        是一个抽象方法,我们前面已经说过了,必须在子类中重写该方法 

        在看看 HttpServlet 是怎么重写这个 Service 方法的:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;//向下转型
        response = (HttpServletResponse)res;//向下转型
    } catch (ClassCastException var6) {
        throw new ServletException("non-HTTP request or response");
    }
 
    this.service(request, response);
}

        可以看到,HttpServlet 中的 service(ServletRequest req ServletResponse res) 把接收到的 ServletRequsest 类型对象转换成了 HttpServletRequest 类型的对象,把 ServletResponse 类型的对象转换成了 HttpServletResponse 类型的对象

        其实就是向下转型,之所以能够向下转型,是因为在Servlet容器在调用 Servlet 的 service(ServletRequest req, ServletResponse res) 时,传入的是 HttpServletRequest 对象和 HttpServletResponse 对象(利用了多态的思想:父类的引用接受了子类对象,即相当于 ServletRequest req  = HttpServletRequest),预备使用 HTTP 

        转换之后,service 方法把两个转换后的对象再传入了另一个 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 = 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);
    }
 
}

        我们会发现该 service 方法中还是没有任何的业务逻辑,只是在解析 HttpServletRequest ,判断该 HTTP 请求是 Get、Post、Put、Delete、Options、Trace、Head 中的哪一种,再调用响应的 doXXX 方法。其中 doGet、doPost 是最经常使用的,两个方法的源码如下:

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

        可以看到,doGet、doPost 都在故意响应错误信息,所以这就导致我们编写的 Servlet 类在 extends HttpServlet 时,有 2 种选择来处理客户端的请求:

         只重写 service(HttpServletRequest req, HttpServletResponse res)。Get、Post、Put、Delete、Options、Trace、Head 请求最终都在该 service 方法内处理,不会执行相应的 doXXX 方法。底层方法执行流是 service(ServletRequest req, ServletResponse res) ——> service(HttpServletRequest req, HttpServletResponse res)

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("ServletB正在提供服务");
        //业务逻辑...
    }
}

         只重写 doXXX 方法。如果是 Get 请求,则由 doGet 方法处理;如果是 Post  请求,则由 doPost 处理;底层方法执行流是  service(ServletRequest req, ServletResponse res) ——> service(HttpServletRequest req, HttpServletResponse res) ——> doXXX

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet方法处理Get请求");
        //业务逻辑...
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost方法处理Post请求");
        //业务逻辑...
    }
}

        当然,按 ② 这样写的话可能会造成代码冗余,所以有的程序员还会这么写:

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);//Get请求也由doPost处理
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //业务逻辑...
    }
}

注意:我们编写的 Servlet 类在 extends HttpServlet 时,处理客户端请求的方法必须在①、②中任选其一,否则访问该 Servlet 时就会报错

我们可以作一下验证,先准备一个Servlet ,①、②都不选,如下:

package com.atguigu.servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;

@WebServlet("/servletB")
public class ServletB extends HttpServlet {

}

Get 方式访问该 Servlet 时候,浏览器会显示如下错误:

这是因为底层方法的执行流是 service(ServletRequest req, ServletResponse res) ——> service(HttpServletRequest req, HttpServletResponse res) ——> doGet。如下所示:

HttpServletRequest接口、HttpServletResponse接口、封装的内容


        Servlet 的工作全流程如下图所示:

         HttpServletRequest 表示使用 HTTP 协议的 Servlet 请求。它继承了 javax.servlet.ServletRequest 接口,HttpServletRequest 接口方法更多,更全面

        Tomcat(Servlet容器)会将浏览器发送的 HTTP 请求封装在 HttpServletRequest 对象中,并在调用 Servlet 的 service(ServletRequest req,ServletResponse res) 时传入

        HTTP 请求的所有信息都是可以通过 HttpServletRequest 的 API 获取的。关于 HTTP 请求的结构,如下图所示:

        HttpServletResponse 继承了 javax.servlet.ServletResponse 接口,同样也是方法更多,更全面

        Tomcat(Servlet容器)是在创建 HttpServletRequest 对象的同时创建 HttpServletResponse 对象,也会在调用 Servlet 的 service(ServletRequest req,ServletResponse res) 时传入

        HttpServletResponse 用来封装给客户端响应的内容,在 service 方法执行结束后,Tomcat 内核会将 HttpServletResponse 转换成 HTTP 响应发送到客户端

        关于 HTTP 响应的结构,如下图所示:

关于 HTTP 协议的详细解释,可以阅读我的另一篇博文:

【JavaWeb】速通HTTP协议-CSDN博客文章浏览阅读688次,点赞23次,收藏11次。1. 简述HTTP全称:超文本传输协议。是一个应用层协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统HTTP规定了客户端与服务器之间进行数据传输的规则。客户端与服务端通信时传输的内容称之为报文。而HTTP就规定了客户端发送给服务器的报文格式,也规定了服务器发送给客户端的报文格式实际上要学习的就是这两种报文:①客户端发送给服务器的称为"请求报文②服务器发送给客户端的称为"响应报文"正式由于HTTP。 https://blog.csdn.net/m0_55908255/article/details/148384856?spm=1011.2415.3001.5331


Servlet的工作全流程

注:上图中我们省略了 Servlet 容器查找 Servlet 的详细过程 ,这里我们解释一下。Tomcat 维护了至少 2 个非常牛逼的 HashMap 容器

        其中一个 HashMap 容器 map1key 用来存放 web.xml 配置文件中的 <servlet-mapping> 中的<url-pattern>;value 则存放对应的 <servlet-name>

        另外一个是 HashMap<id,Servlet>,称之为 map2id 以<servlet-name>(不可重复)来存放,Servlet 则存放 Servlet 的实例。Tomcat 会通过 id 查询该 HashMap,确认该 Servlet 实例是否已经存在 

        🆗,解释完后,Servlet 容器查找 Servlet 的过程如下: 

 Tomcat 受到 HTTP 请求后(假设请求的是动态资源 Servlet ),解析出资源路径

利用资源路径到 map1 中查询是否有该 <url-pattern> :

        无:直接给浏览器返回一个 404 页面(找不到资源)。执行到此结束了,下面的 ③ 不会执行

        有:利用 <url-pattern>map1 得到对应的 value,即 <servlet-name>,继续往下执行

利用 <servlet-name>map2 中查询该 Servlet 实例是否存在:

        存在:说明该 Servlet 已经不是第一次被请求了,无需再创建实例了,直接调用该 Servletservice 方法,处理客户端请求

        未存在:说明该 Servlet第一次被请求,根据 <servlet-name> 拿到 web.xml 中的 <servlet-class> 中的全类名,然后通过反射技术实例化该 Servlet ,即执行 构造器方法+  init() ,接着将  Servlet 实例放入到维护的 map2 中。最终调用该 Servletservice 方法,处理客户端请求。整个流程结束

🆗,以上就是本文的所有内容 


网站公告

今日签到

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