Servlet类的继承体系以及源码分析

写在前面

Servlet是我开始接触javaweb后的第一个难点,当时课程设置在大二,在Java基础匆忙结课后就开始学习Javaweb,Servlet所涉及的一些方法以及原理完全没有仔细了解过。写这篇笔记的目的就是用现有的知识对servlet加深理解,也是对知识对复习笔记

Servlet的继承体系

首先我在IDEA中新建了一个web项目导入了Servlet的jar包,通过 Ctrl+H 可以得到下图的目录结构:
image.png

可以看到,最外层的目录项为Interface,即Servlet接口。然后二级目录中,GenericServlet实现了Interfa Servlet,这就是我们常在编写类写的implement Servlet。 在日常使用中,我们常用到getMethod()方法,而在继承类中调用该方法是会报错的,因为servlet实现的方法的父类是图中的三级目录:HttpServlet实现的,你无法通过调用抽象类的父类来实现抽象类中的方法。所以就出现了我们日常使用的:

HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

不止如此,GenericService在实现interface时定义了很多空实现,我们可以通过httpServlet继承来实现其中service(),即请求分发。

而通过上述方式来继承HttpServlet就可以来实现我们自己的方法。
所以概括来说,Servlet的继承体系大概如下图所示:
image.png


源码分析

Interface Servlet

我们首先来看Servlet接口:


package javax.servlet;

import java.io.IOException;

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所能实现的操作。

GenericServlet

然后再来看GenericServlet,可以看到都是Servlet接口的实现,返回值都是一些空值或者参数,并且引入了ServletConfig来实现一些方法:


package javax.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

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();
    }
}

其中,可以看到service()方法被定义为抽象方法,作为唯一,且最重要的抽象方法,其存在的意义就是:谁来继承该类,谁就要来实现父类的方法,即实现接口的功能。那么谁来继承GenericServlet?就是刚才结构中所提到的HttpServlet:
image.png

HttpServlet

我们来看继承类后对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 {

它所实现的,就是对请求方式的获取和判断,即根据请求方法来分发。在上面的代码中可以看到在获取到请求后通过字符串匹配来判断,并且通过if语句执行对应的方法,这就是请求分发。

这里我们就以上面的GET和POST请求为例,当判断到请求方式为GET或POST后,执行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);
    }

他们都执行了sendMethodNotAllowed()

private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
        String protocol = req.getProtocol();
        if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

而这个方法,只是抛出了异常来指明是否支持该类型请求。
在我们实际生产中,我们接收请求后需要发给对应的方法来执行特定的业务逻辑,那么我们在自定义类中继承HttpServlet后,只需要通过重写doGet和doPost方法即可。

总述

通过对继承体系以及代码实现的分析,我们可以概括为如下的逻辑图:
image.png

Q.E.D.


二八笙歌云幕下,三千世界雪花中