在介绍前后端发展的过程之前,先描述下JSP的发展。为什么要讲JSP的发展?因为它在前后端交互中扮演重要角色,是早期交互的基础。交互初期,页面主要是由JSP构建。
JSP是服务器端动态页面技术规范,它是以“jsp”为后缀结尾的文件,文件内可以包含HTML和Java代码。JSP技术是一种动态的交互式网页开发技术,具有Java的某些特性,开发上与Java具有互通性。
JSP的运行原理是这样的:当服务器上的某个页面被请求时,JSP引擎将其转换成Java文件,然后执行这个文件,返回字节码文件后会再次执行,最后把执行结果以HTML/XML的格式返回客户端,由客户端将其结果渲染展示。JSP具有Java的某些特征基础,它能够在Java的虚拟机上编译和执行,第一次编译完成后,后续都是动态增量编译,即只针对修改部分进行编译,保证编译高效性。
在传统的交互初期,对动态网页需求日益增加,开发效率低,构建相对复杂。当交互的样式、元素多变后,服务器端存在多次编辑、重复修整等需求,没有简便、强大的交互技术支持。JSP的出现解决了产品初期动态网页需求的快速迭代痛点。
传统交互模式主要由JSP做动态网页与服务器端MVC模式进行交互,如图2-1所示。
图2-1 浏览器和服务器端交互
注意,前后端耦合在一起,静态资源(JS、Img、CSS)等同业务程序在同一个工程中,JSP页面会引入JS、CSS、Img等资源。JSP本身可配置标签,用于动态获取数据展示。两者间的交互方式如下。
1)通过标签。如常用Form表单,通过HTTP传输方式,指定服务器端URL进行传输,服务器端通过处理,返回数据,JSP页面通过标签获取到数据展示。
2)通过引入JS。通过Ajax异步请求方式,页面端Ajax可以和服务器端约定好数据体格式,对页面参数组装后,通过HTTP传输方式,指定服务器端URL进行传输,服务器端通过处理,返回数据,Ajax接收到数据后进行解析并最终展示。
最开始使用第一种方式,页面本身支持,无须引入其他组件。因为页面需要定期展示新的数据而需要刷新,用户体验较差。为了解决页面无刷新动态获取数据,提高用户体验,诞生了第二种方式——Ajax异步请求方式。特点是用户无感知,动态获取服务器端数据,提高用户体验。
服务端采用MVC模式,即Model(模型)、View(视图)、Controller(控制器),这种模式主要体现在模型和视图分离,让一个程序有多种展示形式。
在网页中视图层指用户能够看到并与其交互的界面。MVC可以提供多种视图模型,视图中不展示用户数据,它只是一种输出数据并允许用户操作的方式。模型层指业务规则,可以处理不同业务的任务,模型和数据格式无关,同样一个模型可以为多个视图提供数据。控制器层指接收用户的输入并会调用模型层和视图层去完成业务功能,控制器本身不输出任何数据和格式,控制器主要接收请求并决定它会调用哪个模型去构造并处理请求,以及最后会选择哪个视图来显示数据。
前后端交互工程结构如图2-2所示。
图2-2 前后端交互工程结构示意图
下面介绍如何用JSP引入静态资源代码,具体如代码清单2-1所示。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link href="${pageContext.request.contextPath}/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-1.11.0.min.js"></script> <title>demo</title> </head> <body> <form action="${pageContext.request.contextPath}/demo/query" method="POST"> 标题: <input type="text" id=”title” /> 查询:<input type="submit" id=”query”/> </form> </body> </html>
通过JS内置传统Ajax异步获取服务器端数据,具体如代码清单2-2所示。
query.onclick = function(){ var data= document.getElementById("title"); var request = createXHR(); request.onreadystatechange = function(){ if(request.readyState === 4){ if(request.status === 200){ createResult.innerHTML = request.responseText; }else{ alert("发生错误"+request.status); } } }; request.open("POST", contextPath+"/demo/query",false); request.send(data); }
在jQuery中通过Ajax异步获取服务器端数据,如代码清单2-3所示。
$.ajax({ type:"POST", url: contextPath+"/demo/query", dataType:"json", data:{ title:$("#title").val() }, success:function(data){ if(data.success){ $("#createResult").html(data.msg); }else{ $("#createResult").html("出现错误:"+data.msg); } }, error: function(jqXHR){ alert("发生错误:"+jqXHR.status); } });
在服务器端通过MVC模式处理数据,如代码清单2-4所示。
public class GoodsServlet extends HttpServlet{ private static final long serialVersionUID = -5352453280392723315L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setCharacterEncoding("html/text;charse=utf-8"); /** * 获取查询条件 */ String title = request.getParameter("title"); /** * 控制器层调用模型层 * 实例化service * 获取数据返回信息 */ GoodsInfoService goodsService = new GoodsServiceImpl(); Goods goods = goodsService.query(title); // 存储页面展示数据,控制器层调用视图层 request.getSession().setAttribute("goods", goods); response.sendRedirect("demo.jsp"); } }
传统交互模式存在如下问题。
1)静态资源页面和服务器端代码属于同一工程,耦合性较强。
2)部署在同一个服务器,影响面较广。
3)JSP页面获取数据后需要编译,性能较差。
4)页面渲染速度较差,影响用户体验。
5)横向扩展复杂,分摊压力能力差。
前后端分离交互主要是由HTML页面与服务器端MVC模式进行交互,如图2-3所示。
图2-3 前后端分离交互
这里页面和服务器端已经分开部署,页面全部采用静态HTML。优化了传统交互模式。
1)废弃了传统交互模式中的同工程部署方式,优点在于可以减少服务器端的压力,同时可扩展性明显增强。
2)废弃了JSP动态页面交互技术,全面采用HTML静态页面,通过JS动态获取服务器端数据进行展示。
如图2-3所示,前后端分离交互整体流程如下:
1)浏览器需访问到静态页面;
2)静态页面加载时会加载JS、CSS等资源;
3)JS会采用Ajax异步交互技术与服务器端进行数据交互;
4)服务器端处理完毕后返回数据至HTML页面;
5)HTML页面进行渲染构建并返回至浏览器。
前后端分离交互模式从根本上解决了传统交互模式的“痛点”,它的优势如下:
1)前后端解耦、分开部署编译打包,降低依赖性;
2)H5静态页面可存放CDN进行加速;
3)H5可以部署多份,负载均衡;
4)H5和服务器端可同时进行开发,Mock数据,提高开发效率;
5)H5可打包、拆包、压缩容量,提供轻量级的应用;
6)页面无须刷新,异步加载数据,提高用户体验。
前后端分离交互模式带来的问题
设计初期,主要后端根据业务需求设计和定义接口,由于沟通不到位、开发流程不完整等问题,在前端处理过程中才发现接口不符合预期展示效果并提出更改,浪费许多时间。
由于前后端分开部署,通过接口获取数据存在安全问题,前后端需约定好授权机制,用于签名和认证,如JWT机制。JWT即JSON Web Token,是Web交互过程中一种传递紧凑和自包括的JSON的数据格式。
JWT包括请求头部(header)、内容体(Payload)、密钥(Signature)等,它能确保不会被篡改,即意味着前后端交互是安全的。
下面介绍工作原理。在用户使用证书或者账号密码登入时,服务端会生成一个JSON Web Token并返回,同时存储它的信息,客户端后续调用接口时,需要把Token信息存放在请求头header中,由服务器端对客户端的请求进行Token校验,验证请求是否有权限访问到接口。
由于前后端分开部署存在跨域等问题,即客户端和服务端部署不在同一个域名下,服务端需要对接口进行跨域处理,让客户端请求能正常进来、处理并返回。
可以用前端框架Vue调用后端代码,具体如代码清单2-5所示。
import axios from 'axios' Vue.prototype.$ajax = axios axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'; axios.defaults.baseURL = '服务端接口地址'; new Vue({ data:{ title: this.$refs.title }, methods:{ query:function () { this.$http.post('query',data ,{emulateJSON:true}).then(function(res){ console.log(res.data); },function(res){ console.log(res.status); }); } }, created:function(){ } })
传统交互模式更侧重于开发效率、成本,而前后端分离交互模式更侧重于用户体验。
传统交互模式适用于小型系统。这类系统对用户体验没有太高的要求,追求快速迭代开发,如企业内部管理系统。
前后端分离模式适用于大型互联网产品。这类产品对用户体验有较高的要求,追求客户服务至上、用户体验要求高,如电商、平台等。
整体交互流程如下。
1)前后端要充分了解项目需求,通过需求文档、UI、评审、沟通等。
2)后端主导整体功能,后端根据需求设计相关模型、技术文档、接口文档。
3)前端根据UI、原型思考构思页面体验效果、数据交互方式,并思考存在哪些技术难点。
4)前后端沟通接口设计文档,约定数据交互格式,前端需告知后端有疑问的技术难点,双方讨论,后端需针对相关技术难点进行合理化处理,最后约定好接口文档并评审通过。不建议不写接口文档,不要仅口头交流,这种方式存在极大的项目风险。
5)前后端可同时开发,前端通过接口文档用测试数据进行开发,后端根据文档书写接口内容。
6)前端把测试数据换成调用后端的真实数据,后端配合处理问题。
7)本地测试通过,发布到集成环境。前端同产品、需求沟通体验功能是否达到预期效果。
8)后端根据功能重点关注数据的流向,判断是否符合预期结果。
9)测试通过,发布到生产环境。