社会在发展,时代在进步。人们日常使用的电子产品也从个人电脑发展到平板电脑,智能手机,智能手表,智慧屏,等等。这些电子产品装有各种各样的系统,各种各样的系统装有各种各样的软件,各种各样的软件需向后台服务器请求各种各样的资源,各种各样的资源在各种各样的系统里又有各种各样的表现形式。比如,同样是淘宝订单,当用浏览器查看时,订单就以网页的形式表现;当用手机淘宝APP查看时,订单就以手机淘宝APP的形式表现。
毫无疑问,计算机是处理数据的机器。因此,计算机的核心总是数据。在计算机看来,各种各样的前台终端向后台服务器请求的各种各样的资源都是数据。前台终端拿到后台服务器的数据之后,理应根据自身的特点表现这些数据。比如,如果前台终端是浏览器,则以HTML的形式表现这些数据;如果前台终端是鸿蒙系统,则以鸿蒙系统用户界面的形式表现这些数据;如果前台终端是IOS系统,则以IOS系统用户界面的形式表现这些数据。由此可见,后台服务器仅向前台终端响应一份HTML是不合时宜的。毕竟HTML在浏览器上表现非常容易,在手机上以手机系统用户界面的形式表现就难免让人发疯了。
因此,后台服务器最好只向前台终端响应一份数据,比如一份JSON格式的数据。这样,如果前台终端是浏览器,则可操纵HTML把数据填进网页进行表现;如果前台终端是鸿蒙系统,则可调用鸿蒙系统用户界面API进行表现;如果前台终端是IOS系统,则可调用IOS系统用户界面API进行表现。于是,同样一份数据就能在不同的前台终端以不同的形式完美表现了。
基于这样的思考,前后台分离的开发方式出现了,风靡了,流行了。后台开发人员大可不必关心前台是怎样实现的,只需写好API供前台调用之后响应一份数据给前台就行。前台开发人员同样不必关心后台是怎样实现的,只需调用后台API拿到数据之后以前台终端支持的方式进行表现就行。于是,一种新的编程思想出现了,这种编程思想就是REST。
REST又称REST API,RESTful,RESTful API,是英文REpresentational State Transfer的缩写,中文通常翻译成表述性状态转移,一个抽象到让人摸不着头脑的术语。值得庆幸的得,REST本身并没有“表述性状态转移”这样的术语那样难以理解。毕竟REST只是一种编程思想,阐述了面向数据的后台API应该怎么实现。为了理解REST这种编程思想,让我们以庖丁解牛的手法拆开REST,瞧瞧里头都有哪些东西。只是拆开之前,我们还需知道人们提到表述性状态转移时总会习惯性地说漏一个词,这个词就是资源。因此,表述性状态转移的完整叫法应是资源表述性状态转移(Resource REpresentational State Transfer),能被拆成这样:
1.资源(Resource)
资源就是数据。
2.表述性(REpresentational)
表述性是资源的表述方式。比如,同一份数据既可以用JSON表述,也可以用XML表述。
3.状态(State)
状态是指资源的状态。常用的HTTP请求方法有GET,POST,DELETE,PUT,PATCH,等等。简单来说,这些HTTP请求方法就是资源的状态。
4.转移(Transfer)
转移是指资源的转移。也就是说,资源是流动的。既可从前台流向后台,也可从后台流向前台。资源的转移可以简单地理解为数据的流动。
可以看到REST是围绕资源展开的。资源,也就是数据,是REST的核心。前台向后台发起请求之后,后台响应给前台的应是以某种方式表述的数据。至于前台收到数据之后如何表现则是前台自己的事了。比如,前台收到后台一份以JSON这种格式进行表述的数据之后,能以HTML的形式表现这些数据,把数据渲染成一张好看的网页;也能以鸿蒙系统用户界面的形式表现这些数据,把数据渲染成鸿蒙系统风格的用户界面。
同时我们也注意到了,前台向后台发起的请求是带有状态的。这个状态就表现在HTTP请求方法上。当前台用GET这种HTTP请求方法向后台请求获取数据时,请求是带有GET状态的;当前台用POST这种HTTP请求方法向后台请求新增数据时,请求是带有POST状态的;当前台用DELETE这种HTTP请求方法向后台请求删除数据时,请求是带有DELETE状态的;当前台用PUT或PATCH这种HTTP请求方法向后台请求修改数据时,请求是带有PUT或PATCH状态的。因此,请求由两部分组成:一部分是请求URL;一部分是请求状态。请求URL指定了资源的位置;请求状态指定了请求的行为,也就是请求是用于获取数据的,新增数据的,还是删除数据的,等等。
由此可见,REST描述的是数据应以某种状态某种表述方式在前后台之间转移的编程思想。后台实现的API只要符合这种编程思想就是REST。因此,REST既可采用Spring MVC这种技术进行实现,也可采用诸如.NET,PHP,Python之类的技术进行实现。本书介绍的是Spring,自然应该关注怎样使用Spring MVC这种技术实现REST。基于这样的目的,让我们紧接前文实现的person项目,看看改用REST能够怎么实现。而这,可从控制器的修改开始。
按照前文的实现,控制器返回的是ModelAndView对象。ModelAndView对象保存的是关于数据模型和视图名的信息,而REST风格的控制器应该只需返回数据。因此,ModelAndView对象已经不合时宜了,我们需对ControlPersonInfo控制器做些修改,如下:
1 package com.dream.controller; 2 3 import java.util.*; 4 import org.springframework.stereotype.*; 5 import org.springframework.beans.factory.annotation.*; 6 import org.springframework.web.bind.annotation.*; 7 import org.springframework.web.context.request.*; 8 import com.dream.service.*; 9 10 @Controller 11 public class ControlPersonInfo { 12 private ServicePersonInfo servicePersonInfo = null; 13 14 @Autowired 15 public void setServicePersonInfo(ServicePersonInfo servicePersonInfo) { 16 this.servicePersonInfo = servicePersonInfo; 17 } 18 19 @ResponseBody 20 @RequestMapping(value = "/person_info", method = RequestMethod.GET) 21 public List<ServicePersonInfoResult> visit(WebRequest request) { 22 return this.servicePersonInfo.process(); 23 } 24 }
可以看到visit方法没再返回ModelAndView对象,而是返回保存着人的信息的List<ServicePersonInfoResult>列表对象。更加令人好奇的是,visit方法还新带了个神秘的@ResponseBody注解。
这是怎么回事呢?
前文曾经提到,添加<mvc:annotation-driven />配置启用注解驱动的Spring MVC之后,Spring MVC将会创建一些基础Bean。这些基础Bean提供了些基础服务,能帮Spring MVC解析注解,生成请求映射表,重新映射请求,等等。可是我们不知道的是,除了创建基础Bean之外,Spring MVC还会根据类路径(Classpath)里JAR包的依赖情况默认再建一些Bean。这些Bean同样提供了些可以帮助Spring MVC处理请求的服务。而这,就包括能把Java对象转成JSON格式的数据的Bean。为此,我们需往项目里添加一些JAR包,使Spring MVC发现这些JAR包之后自动创建那些能把Java对象转成JSON格式的数据的Bean。需要添加的JAR包如下:
1.jackson-annotations
https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.3/
2.jackson-core
https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.3/
3.jackson-databind
https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.3/
于是,启用了注解驱动的Spring MVC发现类路径里存有这些JAR包之后就会创建一个MappingJackson2HttpMessageConverter类型的Bean。MappingJackson2HttpMessageConverter是Spring MVC定义的一个类,能够作为消息转换器(message converter)把带有@ResponseBody注解的控制器返回的Java对象转成JSON格式的数据。因此,visit方法带有@ResponseBody注解。这样,消息转换器就能把visit方法返回的Java对象转成JSON格式的数据了。同时我们也注意到了,Spring MVC处理请求的过程变成这样:
1.Web容器收到请求之后把请求交给Web应用程序。
2.Web应用程序收到请求之后把请求统一交给DispatcherServlet处理。
3.DispatcherServlet调用处理器映射把请求重新映射给某个符合请求条件的控制器进行处理。
4.控制器完成请求的处理之后返回一个Java对象给DispatcherServlet
5.DispatcherServlet拿到控制器返回的Java对象之后把它交给消息转换器。
6.消息转换器把Java对象转成JSON格式的数据响应请求。
可以看到Spring MVC处理请求的时候已经不再使用InternalResourceViewResolver视图解析器,而是使用MappingJackson2HttpMessageConverter消息转换器把Java对象转成JSON格式的数据。因此,先前添加的关于InternalResourceViewResolver的配置已经没用了,可以删掉了。
于是,后台REST大功告成。运行程序之后往浏览器里输入http://localhost:8080/person/person_info按回车键,浏览器旋能显示JSON格式的人的信息如下:
当然,我们也能抛开浏览器,通过Postman这个工具请求REST,查看REST响应的JSON,如下:
1.打开Postman
2.点击+按钮,添加一个Collection,并设置Collection的名字为Open Spring
3.右击Open Spring这个Collection,弹出上下文菜单之后点击Add Request添加一个请求。
4.输入名称Person,选择HTTP请求方法为GET,输入请求URL为http://localhost:8080/person/person_info;保存之后点击Send发送请求,Postman随即收到一份JSON格式的数据。
现在的问题是,后台REST已经能够正常运行了;前台应该怎样调用这个REST,把JSON格式的数据通过某种方式进行表现呢?方式是多种多样的。比如,可以写个鸿蒙系统应用程序,在应用程序里通过鸿蒙系统API调用后台REST请求JSON格式的数据,随后调用鸿蒙系统用户界面API将数据以鸿蒙系统用户界面的形式进行表现;也可通过AJAX调用后台REST请求JSON格式的数据,随后通过HTML5以网页的形式表现这些数据。这里,我们采用AJAX这种方式调用后台REST,采用HTML5这种方式表现后台REST响应的数据。因此,先前实现的JSP文件index.jsp已经没用了,可以把它删掉。作为替代,请右击person > web目录,弹出上下文菜单;之后点击New > HTML File,弹出New HTML File对话框;输入index按回车键新建index.html文件,修改如下:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>人的信息</title> 6 <script src="index.js" type="text/javascript"></script> 7 </head> 8 <body> 9 <div id="personInfoPanel"></div> 10 </body> 11 </html>
这是一个HTML文件,里面除了引用<script src="index.js" type="text/javascript"></script>这个JS文件之外,并无太多内容。因此,我们需往index.js文件写些JS脚本,向后台REST请求JSON格式的数据,把数据填进网页里把人的信息显示出来。为此,请右击person > web目录,弹出上下文菜单;之后点击New > JavaScript File,弹出New JavaScript File对话框;输入index按回车键新建index.js文件,修改如下:
1 window.onload = function() { 2 requestPersonInfo(); 3 } 4 function requestPersonInfo() { 5 var request = new XMLHttpRequest(); 6 request.onload = requestPersonInfoHandler; 7 request.open("GET", "person_info"); 8 request.send(); 9 } 10 function requestPersonInfoHandler() { 11 if(this.status == 200 && this.responseText != null) { 12 var responseJson = JSON.parse(this.responseText); 13 var personInfoPanel = document.getElementById("personInfoPanel"); 14 if (personInfoPanel) { 15 personInfoPanel.innerHTML = processPersonInfo(responseJson); 16 } 17 } 18 } 19 function processPersonInfo(personInfoJson) { 20 var personInfo = ""; 21 for(var i = 0; i < personInfoJson.length; i++) { 22 var person = personInfoJson[i]; 23 personInfo += "<p>您好!我是" + person.name + ",是个" + person.gender + "!</p>"; 24 } 25 return personInfo; 26 }
我们在窗口加载完成事件里通过AJAX发起后台REST请求,拿到JSON格式的数据。之后,我们把JSON格式的数据转成JS对象,并通过操作DOM把人的信息填进HTML里。于是,用户在浏览器里打开http://localhost:8080/person/index.html之后,能在网页里看到人的信息。
令人意外的是,当我们满怀信心往浏览器里输入http://localhost:8080/person/index.html之后,却发现浏览器显示的是404找不到页面的错误。这是怎么回事呢?
不知大家可还记得,当初配置DispatcherServlet时曾经指定DispatcherServlet能够映射所有请求。也就是说,不管请求URL是什么,都会映射给DispatcherServlet,由DispatcherServlet进行处理。DispatcherServlet处理请求时会把请求映射给控制器,由相应的控制器进行处理。如果找不到相应的控制器,则会响应一个404找不到页面的错误。
因此,请求http://localhost:8080/person/index.html进入Web应用程序之后也是由DispatcherServlet处理的。可是,我们的程序并没有提供相应的控制器处理这样的请求。于是,DispatcherServelt响应一个404找不到页面的错误。
那么,这个问题应该怎么解决呢?
其实,像HTML,JS,CSS,图片,视频之类的静态资源是不需要控制器进行处理的。前台请求这些静态资源时,应该不作任何处理就把这些静态资源响应给前台。因此,我们需要告诉DispatcherSerlvet这些静态资源应该怎么处理。而这,需要修改配置文件servlet-config.xml如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:mvc="http://www.springframework.org/schema/mvc" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context-4.0.xsd 11 http://www.springframework.org/schema/mvc 12 http://www.springframework.org/schema/mvc/spring-mvc.xsd"> 13 14 <mvc:annotation-driven /> 15 <mvc:default-servlet-handler /> 16 <context:component-scan base-package="com.dream.controller" /> 17 18 </beans>
这里新增了<mvc:default-servlet-handler />配置,用于告诉DispatcherServlet如果找不到控制器用于映射请求,则把请求交给Web容器本身提供的那个默认的Servlet处理。默认的Servlet收到请求之后,如果发现Web应用程序存在用户请求的资源,则把资源响应给用户;如果发现用户请求的资源并不存在,则响应一个404找不到页面的错误。于是,当我们再次运行Web应用程序时,Web应用程序就能响应http://localhost:8080/person/index.html给浏览器了。浏览器收到HTML之后加载和运行JS脚本,通过AJAX调用后台REST请求人的信息,收到一份JSON格式的数据。最后,JS解析JSON格式的数据把人的信息填进HTML里。于是,那些曾在三国时期叱咤风云的帅哥美女再次穿越千年跑进网页和我们会面。
至此,关于怎样使用Spring简化Web开发的基础知识介绍完了。当然,这里介绍的只是冰山一角,还有很多内容没有涉及。比如,任何应用程序都不可或缺的异常处理这里就没有提及。因此,要想比较全面地掌握Spring,还有很长一段路要走。我们将在“细说Spring MVC”时进行详细介绍。下章,我们将会实现一个小项目,巩固一下我们这段时间学过的知识。谢谢大家!