《看透Spring MVC 源代码分析与实践》读书笔记——详解Servlet

  最近在看《看透Spring MVC 源代码分析与实践》这本书,以为真心不错。这本书不仅仅讲的是Spring MVC,第一章为网站基础知识,包括网站架构、常见协议、Servlet和Tomcat等内容。虽然在作开发的时候并不会直接用到,不过理解了以后可让咱们在进行具体开发的时候更加驾轻就熟,还能够经过对具体内容的学习学到一些优秀思想。此次我想先从最简单的Servlet内容谈起,毕竟每一个Java Web开发人员应该对Servlet都不陌生。文章内容主要是对书中知识点的概括总结,外加一部分本身的理解。
  你们都知道Servlet是Server+Applet的缩写,表示一个服务器应用。其实Servlet就是一套规范,按照这套规范写的代码就能够直接在Java的服务器上运行。Servlet 3.1中Servlet的结构以下图所示(来源:www.54tianzhisheng.cn/2017/07/09/… ,你们能够没事看看这个博客,有些内容讲的很好): java

图片未加载成功
  Servlet 3.1中Servlet接口的定义以下:

public interface Servlet {
  
    public void init(ServletConfig config) throws ServletException;
  
    public ServletConfig getServletConfig();
  
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
  
    public String getServletInfo();
  
    public void destroy();
}
复制代码

  init方法在容器启动时被容器调用且只会调用一次(当load-on-startup设置为负数或不设置时会在Servlet第一次被用到时被调用)。init方法被调用时,由容器传给它一个ServletConfig类型的参数,ServletConfig顾名思义指的是Servlet的配置,咱们在web.xml中定义Servlet时经过init-param标签配置的参数就是经过ServletConfig来保存的;
  getServletConfig方法用于获取这个ServletConfig对象;
  service方法用于具体处理一个请求,它处理的请求是不限请求类型的。具体怎么处理呢,一会讲到实现它的方法的时候会说;
  getServlet方法能够获取一些Servlet相关的信息,须要本身实现,默认返回空字符串;
  destroy方法主要用于在Servlet销毁(通常指关闭服务器)时释放一些资源,也只会调用一次。web

ServletConfig

  ServletConfig接口定义以下:服务器

public interface ServletConfig {
  
    public String getServletName();
  
    public ServletContext getServletContext();
  
    public String getInitParameter(String name);
  
    public Enumeration<String> getInitParameterNames();
}
复制代码

  getServletName用于获取Servlet的名字,也就是咱们在web.xml中定义的servlet-name;
  getInintParameter用于获取init-param配置的参数;
  getInitParameterNames用于获取配置的全部init-param的名字集合;
  getServletContext的返回值ServletContext表明这个应用自己。 我身边不少人都不明白“表明应用自己”是什么意思,这是说ServletContext里设置的参数,能够被当前应用的全部Servlet共享。你们常常在Session或Application中保存参数,然后者不少时候就是保存在了ServletContext中。能够把ServletConfig理解成Servlet级的,而ServletContext是Context(也就是Application)级的。固然,ServletContext的功能要强大不少,并非保存一下配置参数。
  可能上面说的比较抽象,下面来举一个例子。在web.xml文件中写以下配置信息:架构

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>application-context.xml</param-value>
</context-param>
<servlet>
    <servlet-name>DemoServlet</servlet-name>
    <servlet-class>com.hawk.DemoServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>demo-servlet.xml</param-value>
    </init-param>
</servlet>
复制代码

  上面经过context配置的contextConfigLocation配置到了ServletContext中,而经过servlet下的init-param配置的contextConfigLocation配置到了ServletConfig中。在Servlet中能够分别经过它们的getInitParameter方法进行获取:app

String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");
String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");
复制代码

  为了操做方便,GenericServlet定义了getInitParameter方法,内部返回getServletConfig().getInitParameter的返回值,所以若是须要获取SevletConfig中的参数,能够直接调用getInitParameter,而没必要再调用getServletConfig()。
  ServletContext中常见的用法就是保存Application级的属性,经过调用setAttribute方法:框架

getServletContext().setAttribute("contextConfigLocation", "new path");
复制代码

  而ServletConfig不可设置属性。学习

GenericServlet

  GenericServlet是Servlet的默认实现(注意:GenericServlet是一个抽象类,service方法并未被实现),是与具体协议无关的Servlet,主要作了三件事:(1)实现ServletConfig接口,能够直接调用ServletConfig里的方法;(2)提供了无参的init方法;(3)提供了log方法。下面分别来解释一下:
  GenericServlet实现了ServletConfig接口,在须要调用ServletConfig中方法的时候能够直接调用,而没必要先获取ServletConfig。好比,获取ServletContext的时候能够直接调用getServletContext,而无需调用getServletConfig().getServletContext(),不过和刚才说的getInitParameter同样,其底层实现实际上是在内部调用了:网站

public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
}
复制代码

  GenericServlet实现了Servlet的init(ServletConfig config)方法,在里面将config设置给了内部属性config,而后调用了无参的init方法(这个无参的init方法是GenericServlet新增的,专门用于被它的子类覆盖):this

public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}
复制代码

  这么写的做用很明显:首先将参数config设置给了内部属性config,这样就能够在ServletConfig的接口方法中直接调用config的相应方法来执行;其次,以后咱们在写Servlet的时候就能够只处理本身的初始化逻辑(即只需覆盖无参的init方法),而不须要再关心config,也不须要再调用super.init(config)了。一开始说到容器启动时会调用init(ServletConfig config)方法,而该方法会调用无参的init方法,从而实现咱们本身的初始化逻辑。须要注意的是,若是在本身的Servlet中重写了带参数的init方法,必定要记着调用init(config),不然这里的config属性接收不到值,相应的ServletConfig接口方法就不能执行了。
  GenericServlet提供两个log方法,一个记录日志,一个记录异常,具体实现是经过传给ServletContext的日志实现的。通常咱们都有本身的日志处理方式,因此这两个log方法用得不是不少。spa

HttpServlet

  HttpServlet是用HTTP协议实现的Servlet的子类,通常咱们在写Servlet的时候就是直接继承这个类,Spring MVC中最重要的DispatcherServlet也是继承的这个类。分析这个类主要关心的是如何处理请求,HttpServlet主要重写了service方法,首先将ServletRequest和ServletResponse转换成HttpServletRequest和HttpServletResponse,而后根据Http请求的类型不一样将请求路由到了不一样的处理方法,代码以下:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        // 转换request和reponse的类型
        // 若是请求类型不相符,则抛出异常
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }

        this.service(request, response);
    }

    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、doPost这样的具体处理方法相信你们都很熟悉了,这里就再也不概述,重点是HttpServlet将不一样的请求方式路由到不一样的处理方法这个实现思想。   最后呢,想谈一下我我的对Servlet这种底层事物的见解。有的人在学了各类框架后,就对Servlet、JDBC这种底层事物很不屑,毕竟实际开发中Servlet是用不到的。但我以为,不管多么高级的框架,本质上都是对这些底层事物的封装,区别在于封装的程度不一样,了解这些底层事物的实现细节,对于框架的学习能起到事半功倍的做用。   最后的最后,墙裂推荐《看透Spring MVC 源代码分析与实践》这本书,我以为很OK。

相关文章
相关标签/搜索