【JavaWeb】Servlet继承结构

Servlet接口下有一个GenericServlet抽象类。在GenericServlet抽象类下有一个子类HttpServlet,它是基于http协议。

javax.servlet.Servlet接口

​		javax.GenericServlet抽象类

​			javax.servlet.http.HttpServlet

一、Servlet 接口

1)介绍

Servlet是我们Servlet的顶级接口,我们自己定义Servlet时重写Servlet接口也是可以的,只不过我们要是实现这个Servlet接口的话,里面所有的抽象方法我们都需要重写,这样就会给我们造成除处理业务以外的其他代码的压力,所以在整个Tomcat的环境里,它给我们提供了一个HttpServlet和GenericServlet帮助我们处理一些基础的接口要求。

public interface Servlet {
    // 初始化方法,构造完毕后,由tomcat自动调用完成初始化功能的方法
    void init(ServletConfig var1) throws ServletException;

    // 获取ServletConfig对象的方法
    ServletConfig getServletConfig();

    // 接收用户请求,向用户响应信息的方法,说白了就是处理请求的方法
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    // 返回Servlet字符串申明信息描述信息的方法
    String getServletInfo();

    // Servlet在回收前,由tomcat调用的方法,往往用于做资源的释放工作
    void destroy();
}

2)接口及方法说明

Servlet 规范接口,要求所有的Servlet必须实现

  • public void init(ServletConfig config) throws ServletException;
    • 初始化方法,容器在构造servlet对象后,自动调用的方法,容器负责实例化一个ServletConfig对象,并在调用该方法时传入
    • ServletConfig对象可以为Servlet 提供初始化参数
  • public ServletConfig getServletConfig();
    • 获取ServletConfig对象的方法,后续可以通过该对象获取Servlet初始化参数
  • public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    • 处理请求并做出响应的服务方法,每次请求产生时由容器调用
    • 容器创建一个ServletRequest对象和ServletResponse对象,容器在调用service方法时,传入这两个对象
  • public String getServletInfo();
    • 获取ServletInfo信息的方法
  • public void destroy();
    • Servlet实例在销毁之前调用的方法

二、GenericServlet 抽象类

这个类侧重除了service方法以外的其他方法的基础处理。

1)源码重点方法解释

这是一个抽象类,这个类实现了Servlet接口,因此需要重写Servlet中所有的抽象方法

public abstract class GenericServlet implements Servlet {
    private transient ServletConfig config;
    
    // 如果一个类被其他类继承,并且子类没有显式调用父类的构造器,那么子类会默认调用父类的无参构造器。如果父类没有无参构造器,编译时会报错。因此,为了避免这种情况,父类通常会提供一个无参构造器。
    public GenericServlet() {
    }

    public void destroy() {
        // 将抽象方法,重写为普通方法,在方法内没有任何的实现代码
        // 这种实现叫平庸实现
        // 这样的好处是,destroy已经是一个非抽象方法了,你之后想重写就重写,不想重写就不重写
    }

    // 这个init方法是对Servlet接口中init方法的实现
    // tomcat在调用init方法时,会读取配置信息注入一个ServletConfig对象并将该对象传入init方法
    public void init(ServletConfig config) throws ServletException {
        // 将config对象存储为当前的属性,这样谁继承GenericServlet,谁就可以通过这个属性拿到config初始配置信息
        this.config = config;
        // 调用子重载的无参的init
        // 为什么要这么设计呢?上节我们在讲Servlet生命周期的时候,重写的init方法一定要是无参的init方法,如果你重写的是这个带参的init方法,那你就需要自己为config对象赋值了,并且如果有一个API跟这里获取config对象有关呢,那么跟它有关的API也需要我们自己去处理,例如下面的getServletConfig(),这样的话就意味着如果你重写这个带参的init方法,那么你要处理的代码就比较多、比较麻烦。但我们既不想处理ServletConfig参数,又想让自己的代码参与到它的初始化生命周期里,那么我们重写无参的init方法就行了。Tomcat调用的是有参的init方法,但是有参的init方法中又调用了无参的init方法,这样我们重写的init方法特别干净,而且又不干扰重写它的时候会造成这个config属性无法赋值的情况。
        this.init();
    }

    // 重载的初始化方法,我们重写初始化方法时的方法
    public void init() throws ServletException {
    }

    // 返回ServletConfig配置对象的方法
    public ServletConfig getServletConfig() {
        // Tomcat在初始化这个Servlet的时候,由Tomcat给我们传进来了一个ServletConfig对象
        return this.config;
    }

    // 再次抽象声明service方法,并没有实现
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}

3)源码中的全部方法解释

GenericServlet 抽象类是对Servlet接口一些固定功能的粗糙实现,以及对service方法的再次抽象声明,并定义了一些其他相关功能方法

  • private transient ServletConfig config;
    • 初始化配置对象作为属性
  • public GenericServlet() { }
    • 构造器,为了满足继承而准备
  • public void destroy() { }
    • 销毁方法的平庸实现
  • public String getInitParameter(String name)
    • 获取初始参数的快捷方法
  • public Enumeration<String> getInitParameterNames()
    • 返回所有初始化参数名的方法
  • public ServletConfig getServletConfig()
    • 获取初始Servlet初始配置对象ServletConfig的方法
  • public ServletContext getServletContext()
    • 获取上下文对象ServletContext的方法
  • public String getServletInfo()
    • 获取Servlet信息的平庸实现
  • public void init(ServletConfig config) throws ServletException()
    • 初始化方法的实现,并在此调用了init的重载方法
  • public void init() throws ServletException
    • 重载init方法,为了让我们自己定义初始化功能的方法
  • public void log(String msg)
  • public void log(String message, Throwable t)
    • 打印日志的方法及重载
  • public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    • 服务方法再次声明
  • public String getServletName()
    • 获取ServletName的方法

三、HttpServlet 抽象类

它也是一个抽象类,目前这个类侧重service方法,因为其他方法都已经在GenericServlet抽象类进行了实现。

1)源码

首先发现GenericServlet抽象类和Servlet抽象类中参数都是 ServletRequest、ServletResponse,而这里却是 HttpServletRequest、 HttpServletResponse

通过查看源码我们能发现,ServletRequestHttpServletRequest 的父接口,ServletResponseHttpServletResponse 的父接口。

如果我们重写的时候,如果是将父接口作为参数传入,所调用的API不多,子接口所声明的那些API我们就用不到了

public abstract class HttpServlet extends GenericServlet {
    // 参数的父类字、调用重载service方法
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // 参数的父转子,转换的原因就是因为子类中的API更多更丰富。而ServletRequest、ServletResponse接口在设计的时候为什么不直接设计为子接口的参数呢?目的就是因为需要考虑到其他情况。我们在HTTP协议下用的就是HttpServletRequest、HttpServletResponse,我们写的Java代码也一定是遵循HTTP协议的,因此就算我们写的是父接口ServletRequest、ServletResponse,但实际上Tomcat给我们传入的也一定是HttpServletRequest、HttpServletResponse
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        // 调用重载的service
        this.service(request, response);
    }
    
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的方式
        String method = req.getMethod(); // GET    POST    PUT    DELETE    OPTIONS    ... ...
        long lastModified;
        // 各种if判断,根据请求方式不同,决定去调用不同的do方法
        // 在HttpServlet中这些do方法默认都是405的实现风格-要我们子类去实现对应的方法,否则默认会报405错误
        // 因此,我们在新建Servlet时,我们才会去考虑请求方法,从而决定重写哪个do方法
        if (method.equals("GET")) { // 如果发过来的是GET请求
            this.doGet(req, resp);
        } 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);
        }
    }
    
    // 故意响应405
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 它会根据http.method_post_not_supported字符串去找另一个字符串,所对应的value值就是消息
        String msg = lStrings.getString("http.method_get_not_supported");
        // 故意响应 405 请求方式不 允许的信息
        this.sendMethodNotAllowed(req, resp, msg);
    }

    // 故意响应405
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 它会根据http.method_post_not_supported字符串去找另一个字符串,所对应的value值就是消息
        String msg = lStrings.getString("http.method_post_not_supported");
        // 故意响应 405 请求方式 不允许的信息
        this.sendMethodNotAllowed(req, resp, msg);
    }
    
    private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) {
        String protocol = req.getProtocol(); // 获取http协议
        // 如果协议版本不是 HTTP/0.9 或 HTTP/1.0,并且协议字符串非空,则执行 if 块中的代码
        if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
            // 报405错,然后把msg显示出来
            resp.sendError(405, msg); // 这个方法里面就这一行代码在起作用,也就是说这个方法就是在故意响应405
        } else {
            resp.sendError(400, msg);
        }

    }
}

2)其他方法的解释

abstract class HttpServlet extends GenericServlet HttpServlet抽象类,除了基本的实现以外,增加了更多的基础功能

  • private static final String METHOD_DELETE = “DELETE”;
  • private static final String METHOD_HEAD = “HEAD”;
  • private static final String METHOD_GET = “GET”;
  • private static final String METHOD_OPTIONS = “OPTIONS”;
  • private static final String METHOD_POST = “POST”;
  • private static final String METHOD_PUT = “PUT”;
  • private static final String METHOD_TRACE = “TRACE”;
    • 上述属性用于定义常见请求方式名常量值
  • public HttpServlet() {}
    • 构造器,用于处理继承
  • public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
    • 对服务方法的实现
    • 在该方法中,将请求和响应对象转换成对应HTTP协议的HttpServletRequest HttpServletResponse对象
    • 调用重载的service方法
  • public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
    • 重载的service方法,被重写的service方法所调用
    • 在该方法中,通过请求方式判断,调用具体的 do*** 方法完成请求的处理
  • protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  • protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  • protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  • protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  • protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  • protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  • protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    • 对应不同请求方式的处理方法
    • 除了doOptions和doTrace方法,其他的do*** 方法都在故意响应错误信息

四、自定义Servlet

自定义Servlet中,必须要对处理请求的方法 public void service(HttpServletRequest req, HttpServletResponse res) 进行重写,重写过后,Tomcat执行的就是实例化的Servlet对象中我们自己重写的service方法;如果我们没有重写这个service方法,就会找到父类 HttpServlet ,此时无论是GET还是POST,它都会调对应的doGet / doPost,此时它就会响应405。

  • 要么重写service方法
  • 要么重写doGet/doPost方法
  • 如果两个方法都进行了重写,那么service生效
1.部分程序员推荐在servlet中重写do***方法处理请求   理由:service方法中可能做了一些处理,如果我们直接重写service的话,父类中service方法中的处理的功能则失效,他们就会觉得这样可能会产生一些问题
2.目前直接重写service也没有什么问题
3.后续使用了SpringMVC框架后,我们则无需继承HttpServlet,处理请求的方式也无须是do***或者service方法,既然我们以后不会这么写了,所以现在我们重写哪种方法其实也都无所谓了
4.如果doGet和doPost方法中,我们定义的代码都一样,可以让一个方法去调用另一个方法
	例如:
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
            this.doPost(req, resp);
        }

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
            System.out.println("post请求处理的方法");
        }

五、继承关系图解

1682299663047

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值