写在最前面,这篇文章中对涉及到的各种源码不会进行进一步探讨,只是对整个的处理过程有一个相应的解释,从而去理解 springmvc 和页面端的交互和前后端分离的意义。
Part1 http协议
当你在浏览器输入某个网址,按下回车,到浏览器展示页面,中间发生了什么?
很简单,浏览器把你想要访问某个资源的想法转发给了你访问的网站服务器,网站服务器把你可以访问的资源返回给了浏览器。那么这双向的数据交互就用到了 http 协议。
http 协议是基于 TCP/IP协议 的应用层协议,是浏览器和服务器之间数据交换的一种规范。我们可以把浏览器请求服务器的过程称为 http 请求报文,把服务器响应浏览器的请求的过程称为 http 响应报文。http 请求报文和 http 响应报文从结构上来讲差不多,都可以分为报文头部和报文正文(也就是请求头,请求体,响应头,响应体)。具体结构如下图:
![](https://img-blog.csdnimg.cn/20190827211436399.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nhcmxvc3Ri,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190827212004306.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nhcmxvc3Ri,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190827212852674.png)
报文头部携带了请求的地址,时间,格式及字符集等等信息(某些情况下传输后乱码的问题可以通过设置这里的字符集解决)。报文正文携带了请求的参数和响应的内容,在早期版本的 http 协议中,传输的内容就是 html ,后续的版本中对此进行了升级,可以传输图片、视频等(详细的可以自己百度)。
另外,http 协议是无状态的,也就是说两次连接是独立的,连接一次就要三次握手校验,并不能保存现在常用的登录状态。
Part2 springmvc 是如何处理 http 请求的
首先,我们回忆一下不使用框架情况下的 javaWeb 是怎么处理的 。如下图:
此外还需要在 web.xml 中配置哪个请求地址进入到那个 servlet 中。也就是说,javaWeb 为我们做了网址映射,当我们访问某个地址的时候可以进入到指定的 servlet 中进行处理。
我们接下来看一下 doGet 和 doPost 的入参:
查看接口中的这些方法,对照 http 请求和响应报文中的内容,我们可以发现 javaWeb 为我们封装了一个请求报文对象和响应报文对象,http 请求中的信息都在 HttpServletRequest 中,而服务器的响应都被封装在了 HttpServletResponse 中被返回给了浏览器。
接下来我们来看一下 springmvc 的写法。如下图:
这是一个比较常见的 springmvc 的写法,@RequestMapping 指定了这个方法用来处理哪个网址请求,接受什么方法和入参,以及返回什么数据(model)和 jsp(view),也就是 ModelAndView。
再深入一些,也就是 spring 的 DispatcherServlet 对请求进行拦截,然后根据映射信息去查找应该调用哪个 controller 的哪个 method 去执行(如果仔细看 tomcat 启动信息,可以看到 spring 生成所有的映射信息)。虽然在这里我们编写 method 的时候不需要传入 HttpServletRequest 和 HttpServletResponse ,但实际上这部分是由 spring 框架帮我们完成了,也就是对 method 所需的入参进行了绑定,如果编程的时候遇到入参一直绑定不成功的情况,直接接收 HttpServletRequest 作为入参进行获取所需的数据,当然你需要确定你所需的数据确实在请求报文中。
Part3 springmvc 返回 http 响应
首先看下 spring 配置:
所以,springmvc 会通过我们配置的 ViewResolver 去解析我们指定的视图,找到 jsp 页面,组装数据,然后经容器一系列的处理后进行返回。那么,这里返回给响应报文的就是我们指定的 jsp 么?并不是。
我们编写的 jsp 文件中经常会有很多 <c> 标签来对传入的 model 进行渲染,有些甚至还会有 java 代码(非常不推荐这种方式),这些东西,浏览器是无法进行执行和渲染的,还需要进行进一步的处理。这时候就要去看 tomcat 工作目录了:
我们可以看到,我们的 jsp 页面被处理成了一个 java 文件和 一个 class 文件,让我们来打开看一下:
我们写的 jsp 文件在这里通过 out.write() 被翻译成了 html ,包括 model 里的数据,最后被放入到 response 中,也就是 http 响应报文。
Part4 前后端分离的意义
通过上面三步,我们大致了解了 springmvc 处理 http 请求和进行 http 响应的过程。这个过程有个特点,我们的后端代码和页面都需要被放在 tomcat 容器中(大部分情况下是同一个 tomcat 中),我们即使是只想访问一个 jsp 页面也需要每次都进行 index_jsp.java 和 index_jsp.class 和编译,注意是每次!这个时候,如果有大量用户访问你的 jsp,都还没到实际的服务呢,可能服务器已经趴下了。
那么,我们提出一个设想,我做一个集群,横向对服务进行扩展,这样也可以。但是对于单台服务器,服务能力并没有任何的提升,还是访问一次 jsp,所有事情都做一遍,该趴下继续趴下。
另外一个设想,既然浏览器接受的是 html 页面,那我们就把浏览器不能直接用的 jsp 换成能直接用的 html ,这样就可以避免每访问一次编译一次,让服务器的处理能力更多的用到我们的服务上去,而且页面间的跳转也交给页面去完成好了,完全不需要后端的参与。更进一步,tomcat 容器其实对于静态资源的处理能力要比 nginx 低不少,用 nginx 去处理静态资源更合适,而且也更方便横向扩展。