1、概述:
继上一篇,这篇写SpringMvc,此篇博文将以不同的视觉透视SpringMvc,将以数据请求、接收处理和应答返回的过程介绍SpringMvc,看此篇博文需对Http协议、动态网页(Jsp或模板引擎)、数据库、字符集、网络IO(Tcp)、Web服务器(Tomcat或Jetty)有一定的了解。
2、数据请求:
我们知道服务器之间通信(即数据传输),是在Internet网上(也有其它,现在不论),OSI的7层协议分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,我们通常是在传输层和应用层编码开发。
看到这里可能会问,传输层和应用层好像与SpringMvc没多大关系吧!因为基于SpringMvc开发时,我们在Controller层接收应答数据都是以函数接口形式。
我想说能这样Happy编码,完全是Web容器(Tomcat或Jetty),及SpringMvc的功劳,否则,夸张一点说,50%后端Web程序员将转行。那Web容器和SpringMvc在此过程到底干了些什么?
2.1、Web容器:
在看下面内容之前,需明白基于操作系统(windows/linux/unix)开发时,如果没有做到驱动层,或者使用原始Socket,我们通常使用传输层的Tcp协议,进行网络通信开发,Web容器也不例外,应用层使用Http协议,传输层使用Tcp协议。
Http协议通常用于Web开发,从浏览器发出的大多数请求是基于Http协议(可用WebSocket),Http协议是一种超文本传输协议,即可见字符,做过Tcp Socket通信的人都知道,IO都是Byte流,那它如何变成Http超文本呢?
其实由Byte流转变成Http超文本是由Web容器完成,并且会将Http超文本完全或部分映射成本地编码对象,以Java和Tomcat为例,最终会映射成HttpServletRequest和HttpServletResponse,到此Web容器已经完成了两个核心功能:
1、完成网络通信;
2、完成数据的转换;
以下内容全是基于Java和Tomcat简述。
2.2、SpringMvc:
Tomcat已经将数据包转成HttpServletRequest和HttpServletResponse,现在只需要读取值,据值定义逻辑处理,要实现某个功能逻辑,有不同编码设计方式,若没有一个约定,因人而异,各有不同,最终开发维护成本会很高,基于这个SpringMvc应运而生。
SpringMvc即是一种设计模式,也是一种编码规范,可分为两部分Spring和DispatcherServlet,DispatcherServlet继承自Servlet(动态网页开发技术),据多态原理,可被Tomcat加载到内存,为了更好的理解,看一段注解方式启动SpringMvc代码。
public class WebInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
//Spring
AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
app.register(SpringMvcConfig.class);
//DispatcherServlet
ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(app));
dynamic.addMapping("/");
dynamic.setLoadOnStartup(1);
}
}
DispatcherServlet类图:
至于Spring请看上一篇博文,到此必需明白,Tomcat定义Servlet接口规定输入输出参数规范,将实现类或其子类加载到内存,而HttpServlet再一次对Http协议进行映射封装(函数接口的形式),如:
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
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";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE =
"javax.servlet.http.LocalStrings";
private static final ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
/**
* Does nothing, because this is an abstract class.
*/
public HttpServlet() {
// NOOP
}
//Http协议参数映射封装
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
...
到现在为止没有说MVC,MVC是一种设计模式,以Web开发来说,M:数据层、V:视图层、C:逻辑层,而DispatcherServlet和Spring就是实现MVC的手段,即SpringMvc。
2.3、DispatcherServlet和Spring(即SpringMvc):
Tomcat网络IO的Byte流数据,将映射成Java对象,通过HttpServletRequest和HttpServletResponse两个参数对象,传给HttpServlet的方法(据Http协议请求行指定请求方法),如:
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
HttpServletRequest主要包含Http协议的请求行、请求头和请求体,和IO输入流;
HttpServletResponse主要包含Http协议的响应行、响应头和响应体,和IO输出流;
DispatcherServlet将会调用Spring容器提供的对象进行逻辑处理,完成大多数约定编码(即设计模式),最终大多数使用者仅需从Controller开始编码。
3、数据处理:
到此必需明白数据以HttpServletRequest输入,以HttpServletResponse输出,否则不要往下看了!
DispatcherServlet处理数据方式分为两种:
1、自定义;
2、彼定义;
3.1、自定义:
自定义约束规范,完成大多数统一处理,最终调用彼定义的接口函数,下面详细来述此过程。
早期基于Tomcat开发的人都知道,继承HttpServlet开发,一Uri映射一HttpServlet实体对象,如:
<servlet>
<servlet-name>servletOne</servlet-name>
<servlet-class>cn.sd.ServletOne</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletOne</servlet-name>
<url-pattern>/servletOne</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>servletTwo</servlet-name>
<servlet-class>cn.sd.ServletTwo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletTwo</servlet-name>
<url-pattern>/servletTwo</url-pattern>
</servlet-mapping>
而DispatcherServlet是以Uri映射函数的形式完成此功能,即一Uri映射一函数,这个映射过程即自定义处理,也是DispatcherServlet的核心所在,先抛出一些在此过程DispatcherServlet定义的相关术语,处理器映射器、拦截器、处理器适配器、参数转换器。
处理器映射器完成Uri与函数的配对关系(key/value),拦截器利用SpringAop拦截函数、处理器适配器决定函数如何被调用(适配器设计模式)、参数转换器将Http协议请求参数转换成Java函数参数,最后我们可以在Controller层Happy编码。
3.2、彼定义:
彼定义,即你自由编码的地方,但这里通常还是要遵守SpringMvc的规范,否则别人看你代码,将会fuck。SpringMvc定义了Controller层、Service层、持久层(如Mybatis)和视图层(Jsp/Freemaker/Thymeleaf),它们将会被Spring容器统一管理。
这里补充一点,上面写的全部映射和部分映射,Http协议请求行的Uri和Body都能以Key/Value、Json字符、或自定义格式传递参数,数据量不大,一次能读取全部映射,另一种情况如果数据量很大,或者文件上传,就不能全部转换,或不需要转换。
最后就是视图层,现在浏览器是基于Html、Js、Css渲染显示界面,虽说现在流行前后分离,但后端有时还是需要,即要界面开发,也要拿到Java数据,有点不厚道,视图层的出现就是为了解决此问题,Jsp最后会转成Servlet,Freemaker和Thymeleaf字符替换,Jsp最终应该是要转向Response(这段原码没有看过)。
4、应答返回:
应答即DispatcherServlet,把Controller层函数的返回值,或直接把填冲进HttpServletResponse数据,转换成Http协议数据,即响应行、响应头、响应体,最后交由Tomcat应答输出。
最后来一张SpringMvc执行流程图(非我画),助于理解。
用户请求不是直接到达DispatcherServlet,而是由Tomcat读取数据,解析请求行的Uri,找到对应的DispatcherServlet调用执行,完成相应逻辑后,再由Tomcat输出数据,Tomcat调用DispatcherServlet之前可以执行过滤器(aop思想,函数回调方式,如:统一转码)。另外前后分离开发时无视图层。
可能有些人不明白Tomcat容器是如何加载DispatcherServlet,早期Xml时是去读取包下的Web.xml进行加载,基于Java注解开发是去读取META-INF/services/javax.servlet.ServletContainerInitializer文件,读取感兴趣的类加载,如: