【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,有理解不对的地方欢迎指正哈。