【Spring MVC】前置篇-Servlet 的认识

1  前言

这节我们要开始 Spring MVC之旅了,大家应该知道Spring MVC 是基于 Servlet 实现的。所以要分析 Spring MVC,首先应追根溯源,弄懂 Servlet。Servlet是 server + applet 的缩写,表示服务器应用,也就是一种服务能力。那么这节我们就先来看下 Servlet 相关的知识,更好的去理解 SpringMVC,推荐大家看一本书《看透Spring MVC》,写的很好,里边不仅只是说 Spring MVC,会先讲一些网络的基础以及Tomcat的东西做引导的,大家可以看看。

Servlet 是 J2EE 规范之一,在遵守该规范的前提下,我们可将 Web 应用部署在 Servlet 容器下。这样做有什么好处或者说为什么要这么做呢?那就要说一下 Servlet 容器的重要性了。当你要写一个 Web 应用,你难道还要去解析 HTTP 协议相关的内容么,比如请求头、请求体什么的,也就是说 Servlet 容器帮我们把这些都做了,不用我们再去关心这方面的事情,它会把请求内容解析封装成对象,传给我们,让我们更好的聚焦业务逻辑。

2  源码分析

那我们先来看看 Servlet接口及其实现类结构,然后再进行更进一步的说明。

2.1  Servlet 与 ServletConfig

我们先来看看 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 方法会在容器启动时由容器调用,也可能会在 Servlet 第一次被使用时调用,只会调用一次,调用时机取决 load-on-startup 的配置,当load-on-startup设置为负数或者不设置时会在Servlet第一次用到时才被调用;
  • getSerletConfig 方法用于获取ServletConfig,配置类相关的;
  • service 提供服务的核心方法;
  • getServletInfo 可以获取一些Servlet相关的信息,如作者、版权等,这个方法需要自己实现,默认返回空字符串;
  • destroy主要用于在Servlet销毁(一般指关闭服务器)时释放一些资源,也只会调用一次。
public interface ServletConfig {
    
    public String getServletName();

    public ServletContext getServletContext();

    public String getInitParameter(String name);

    public Enumeration<String> getInitParameterNames();
}
  • getServletName 方法,该方法用于获取 servlet 名称,也就是 <servlet-name>标签中配置的内容。
  • getServletContext 方法用于获取 Servlet 上下文。如果说一个 ServletConfig 对应一个 Servlet,那么一个 ServletContext 则是对应所有的 Servlet,其表示的是当前 Web应用;
  • getInitParameter 方法用于获取 <context-param>标签中配置的参数值;
  • getInitParameterNames 则是获取所有配置的名称集合。

以上是 Servlet 与 ServletConfig 两个接口的说明,比较简单。说完这两个接口,我们继续往下看,接下来是 GenericServlet。

2.2  GenericServlet

GenericServlet是与具体协议无关的,实现了 Servlet 和 ServletConfig 两个接口,为这两个接口中的部分方法提供了简单的实现,是一个比较基础的类,不会做太多的工作,只是给子类提供了一些简单的便利,主要有:

  • 提供无参的 init 方法;
  • 提供 log 方法;
  • 实现Servlet 、ServletConfig

我们这里看三个方法:一个是 init,一个是 getServletContext,一个是 getInitParameter

2.2.1  init

实现了Servlet的init(ServletConfig config)方法,在里面将config设置给了内部变量config,然后调用了无参的init()方法,在子类中可以通过覆盖它来完成自己的初始化工作,代码如下:

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

首先,将参数config设置给了内部属性config,这样就可以在ServletConfig的接口方法中直接调用config的相应方法来执行;其次,这么做之后我们在写Servlet的时候就可以只处理自己的初始化逻辑,而不需要再关心config了;还有一个作用就是在重写init方法时也不需要再调用super.init(config)了。如果在自己的Servlet中重写了带参数的init方法,那么一定要记着调用super.init(config),否则这里的config属性就接收不到值,相应的ServletConfig接口方法也就不能执行了。

2.2.2  getServletContext

实现了ServletConfig 的 getServletContext 方法,我们在需要调用ServletConfig中方法的时候可以直接调用,而不再需要先获取ServletConfig了,比如,获取ServletContext的时候可以直接调用getServletContext,而无须调用getServletConfig().getServletContext()了,不过其底层实现其实是在内部调用了。getServletContext的代码如下:

public ServletContext getServletContext() {
    ServletConfig sc = getServletConfig();
    if (sc == null) {
        throw new IllegalStateException(
            lStrings.getString("err.servlet_config_not_initialized"));
    }
    return sc.getServletContext();
}

2.2.3  getInitParameter

实现了ServletConfig 的 getInitParameter 方法,其实也还是间接调用了内部的 ServletConfig 的方法我们看下:

public String getInitParameter(String name) {
    ServletConfig sc = getServletConfig();
    if (sc == null) {
        throw new IllegalStateException(
            lStrings.getString("err.servlet_config_not_initialized"));
    }
    return sc.getInitParameter(name);
}

好那么关于 GenericServlet 就看这么多,东西比较少是一个比较基础的类。

2.3  HttpServlet

HttpServlet 是用HTTP协议实现的Servlet的基类,写Servlet时直接继承它就可以了,我们后面要看的Spring MVC中的DispatcherServlet就是继承的HttpServlet。既然HttpServlet是跟协议相关的,当然主要关心的是如何处理请求了,所以HttpServlet主要重写了service方法。在service方法中首先将ServletRequest和ServletResponse转换为了HttpServletRequest和HttpServletResponse,然后根据Http请求的类型不同将请求路由到了不同的处理方法。代码如下:

@Override
public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {
    HttpServletRequest  request;
    HttpServletResponse response;
    
    if (!(req instanceof HttpServletRequest &&
            res instanceof HttpServletResponse)) {
        throw new ServletException("non-HTTP request or response");
    }

    request = (HttpServletRequest) req;
    response = (HttpServletResponse) res;

    // 调用重载方法,该重载方法接受 HttpServletRequest 和 HttpServletResponse 类型的参数
    service(request, response);
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String method = req.getMethod();

    // 处理 GET 请求
    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // 调用 doGet 方法
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    // 处理 HEAD 请求
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    // 处理 POST 请求
    } else if (method.equals(METHOD_POST)) {
        // 调用 doPost 方法
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

在调用doGet方法前还对是否过期做了检查,如果没有过期则直接返回304状态码使用缓存;doHead调用了doGet的请求,然后返回空body的Response;doOptions和doTrace正常不需要使用,主要是用来做一些调试工作,doOptions返回所有支持的处理类型的集合,正常情况下可以禁用,doTrace是用来远程诊断服务器的,它会将接收到的header原封不动地返回,这种做法很可能会被黑客利用,存在安全漏洞,所以如果不是必须使用,最好禁用。由于doOptions和doTrace的功能非常固定,所以HttpServlet做了默认的实现,这里就不贴源码了哈,大家打开自己看看就知道啦。

3  小结

好那么这节带大家简单看了下 Servlet 容器的能力,大概知道下是怎么回事,下节我们就开始看我们的 Spring MVC,有理解不对的地方欢迎指正哈。

posted @ 2023-03-20 07:52  酷酷-  阅读(65)  评论(0编辑  收藏  举报