springMVC笔记系列(22)——Json各类操作在springMVC中的实现

Json使得更多的开发可以在单页面完成,大大改变了如今人们的开发方式,使得前端和后端的分离更加彻底。Json是一种轻量级的数据交换格式。Json与XML格式一样具有良好的结构,但格式更为简洁。Json在Restfule Web Service中发挥了重要的作用,使得Restful Web Service成为业界Web Service的标准,逐渐取代了比较“重”的SOAP。

SOAP(Simple Object Access Protocol)简单对象访问协议,是基于HTTP的一种异构系统通信的协议,说白了就是xml文档传输。

REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。其核心是面向资源,REST专门针对网络应用设计和开发方式,以降低开发的复杂性,提高系统的可伸缩性。REST提出设计概念和准则为:

1.网络上的所有事物都可以被抽象为资源(resource)
2.每一个资源都有唯一的资源标识(resource identifier),对资源的操作不会改变这些标识
3.所有的操作都是无状态的

REST简化开发,其架构遵循CRUD原则,该原则告诉我们对于资源(包括网络资源)只需要四种行为:创建,获取,更新和删除就可以完成相关的操作和处理。您可以通过统一资源标识符(Universal Resource Identifier,URI)来识别和定位资源,并且针对这些资源而执行的操作是通过 HTTP 规范定义的。其核心操作只有GET,PUT,POST,DELETE。

由于REST强制所有的操作都必须是stateless的,这就没有上下文的约束,如果做分布式,集群都不需要考虑上下文和会话保持的问题。极大的提高系统的可伸缩性。

 

Restful Web Service与SOAP Web Service区别先来个直观比较:

rest轻量级,SOAP重量级;

rest学习起来比较简单,容易上手,SOAP相对来说难些;

rest能通过http形式的直接调用,基于JSON,SOAP通过XML传输;

rest效率和速度来说相对快些,SOAP则稍逊一筹

(本文出自: http://my.oschina.net/happyBKs/blog/707994)

对于SOAP Webservice和Restful Webservice的选择问题,首先需要理解就是SOAP偏向于面向活动,有严格的规范和标准,包括安全,事务等各个方面的内容,同时SOAP强调操作方法和操作对象的分离,有WSDL文件规范和XSD文件分别对其定义。而REST强调面向资源,只要我们要操作的对象可以抽象为资源即可以使用REST架构风格。

REST核心是url和面向资源,url代替了原来复杂的操作方法。REST允许我们通过url设计系统,就像测试驱动开发使用测试用例设计类接口一样。所有可以被抽象为资源的东西都可以使用RESTful的url,当我们以传统的用SOAP方式实现的一个查询订单服务的时候可以看到,这个服务首先存在输入的查询条件,然后才是输出结果集。那么对于类似场景要使用REST,不可避免的会将传统的SOAP服务拆分为一个HTTP POST操作和一个HTTP GET操作。前面是输入,而后面是输出。

使用REST的关键是如何抽象资源,抽象的越精确,对REST的应用越好。如何进行抽象,面向资源的设计和传统的面向结构和对象设计区别,资源和对象,数据库表之间的差别是另外一个在分析设计时候要考虑的问题。在REST分析设计中如何改变传统的SOAP分析设计思想又是一个重要问题。

扯远了扯远了。。。。。

 

Json的格式如下:

{
"employees": [
  { "firstName":"Bill" , "lastName":"Gates" },
  { "firstName":"George" , "lastName":"Bush" },
  { "firstName":"Thomas" , "lastName":"Carter" }
]
}

springMVC提供了对Json协同工作的机制。

数据在springMVC的应用场景中就是我们的Model,我们希望得到的Model数据的一种呈现,自然包括了Web页面的Html格式的呈现方式,这是一种面向人呈现出的格式;另一方面,现在很多网络服务都提供了App应用,例如后端的一些大数据分析等等消耗数据的应用,Apps变身成了数据的使用者,这时,Json就成了一种更好的面向机器或者说应用的数据模型传递的选择。在这个过程中,数据模型本身没有变,变的只是它的呈现方式。这就是springMVC看待Json的方式——Json、Html都是数据模型的不同表现形式。

142921_Qt7o_1156339.png

 

基于这样的思想,springMVC会如何处理Json呢?

springMVC使用ViewResolver的机制来处理这种相同数据不同呈现方式的应用场景,用ViewResolver来处理不同的数据呈现格式。

例如,如果我们想把数据模型以Html的方式呈献给人类用户,那么可以通过JSPView来处理数据模型呈现。同时,如果是机器用户,需要一个JSON格式的数据呈现,那么springMVC就把这个数据请求代理给了JsonView。

ViewResolver将相同的数据呈现为不同的表现形式,那么它如何配置呢?

我们需要配置的东西:一个是ContentNegotiatingViewResolver,配置Json的数据格式还需要配置一个MappingJackson2JsonView。

ContentNegotiatingViewResolver

ContentNegotiatingViewResolver支持在Spring MVC下输出不同的格式;
ContentNegotiatingViewResolver是ViewResolver的一个实现;
ContentNegotiatingViewResolver使用request的媒体类型,根据扩展名选择不同的view输出不同的格式;
ContentNegotiatingViewResolver不是自己处理view,而是代理给不同的ViewResolver来处理不同的view;

 viewviewResolver
jspWEB-INF/views/demoObj.jspUrlBasedViewResolver或InternalResourceViewResolver
pdfPdfViewPdfViewResolver
jsonMappingJackson2JsonViewJsonViewResolver
xmlMarshallingViewXmlViewResolver
xlsXlsViewXlsViewResolver

配置方法就是在springMVC项目的前端控制器配置文件中加入:

    <!-- 配置ViewResolver。 可以用多个ViewResolver。 使用order属性排序。 InternalResourceViewResolver放在最后。 -->
    <bean
            class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="order" value="1"/>
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json"/>
                <entry key="xml" value="application/xml"/>
                <entry key="htm" value="text/html"/>
            </map>
        </property>

        <property name="defaultViews">
            <list>
                <!-- JSON View -->
                <bean
                        class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
                </bean>
            </list>
        </property>
        <property name="ignoreAcceptHeader" value="true"/>
    </bean>

springMVC在做相关处理是须要对JavaBean转换成Json的,这个部分需要对一个jar的依赖,因此在pom.xml中加入依赖坐标:

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>

版本在pom.xml中的属性中单独定义:

  <properties>
    <commons-lang.version>2.6</commons-lang.version>
    <slf4j.version>1.7.21</slf4j.version>
    <spring.version>4.2.6.RELEASE</spring.version>
    <jackson.version>2.7.4</jackson.version>
  </properties>

这样Jackson自己的核心jar以及它所依赖的其他jar都会被Maven项目自动导入。

之后,我们需要在控制器类中编写一个支持Json的方法:

	@RequestMapping(value="/{courseId}",method=RequestMethod.GET)
	public @ResponseBody Course getCourseInJson(@PathVariable Integer courseId){
		return  courseService.getCoursebyId(courseId);
	}

这个控制器方法在响应URL请求时与之前一样,使用的注解@RequestMapping来将URL映射到该控制器方法;不同的是,该控制器方法的返回不再是一个代表view资源路径(jsp)的字段字符串,而是一个bean对象,这里是Course类对象。注意,这里的返回前还加上了注解@ResponseBody,说明这个返回的Course类对象会被响应Response所使用。

好,我们看看这样的控制器方法会返回给我们什么?

运行webapp,请求http://localhost:8080/mvc/courses/123,发现尽然服务器出错了!

可以看到原因如下:

HTTP Status 500 - Servlet.init() for servlet mvc-dispatcher threw exception

type Exception report

message Servlet.init() for servlet mvc-dispatcher threw exception

description The server encountered an internal error that prevented it from fulfilling this request.

exception

javax.servlet.ServletException: Servlet.init() for servlet mvc-dispatcher threw exception
	org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
	org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
	org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
	org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
	org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
	org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
	org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2476)
	org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2465)
	java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	java.lang.Thread.run(Thread.java:745)
root cause

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.view.ContentNegotiatingViewResolver#0' defined in ServletContext resource [/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'mediaTypes' of bean class [org.springframework.web.servlet.view.ContentNegotiatingViewResolver]: Bean property 'mediaTypes' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?


..........
	

出现这种错误,应该是前端控制器配置文件中ContentNegotiatingViewResolver中的属性mediaTypes和ignoreAcceptHeader报错了。

我们在idea对\WEB-INF\configs\spring\mvc-dispatcher-servlet.xml也提示了无法处理这两个属性。

182539_QAUX_1156339.png

探究原因,我们进入ContentNegotiatingViewResolver类的源代码,可以发现,从某个版本开始,ContentNegotiatingViewResolver的mediaTypes属性已经只有get方法而没有set方法来了,而且getMediaTypes也变成了protected权限。所以,mvc-dispatcher-servlet.xml中无法用配置的值注入到ContentNegotiatingViewResolver的mediaTypes等属性也是可以预见的。

163639_7DFf_1156339.png

所以,我们就将这两个属性给注释掉吧:)

完整的\WEB-INF\configs\spring\mvc-dispatcher-servlet.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 -->

    <!-- 启用Spring基于annotation的DI, 使用户可以在Spring MVC中使用Spring的强大功能。 激活 @Required
        @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 -->
    <context:annotation-config />

    <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其他型的bean, 如@Service -->
    <context:component-scan base-package="com.happyBKs.controller">
        <context:include-filter type="annotation"  expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!-- HandlerMapping, 无需配置, Spring MVC可以默认启动。 DefaultAnnotationHandlerMapping
        annotation-driven HandlerMapping -->

    <!-- 扩充了注解驱动,可以将请求参数绑定到控制器参数 -->
    <mvc:annotation-driven />

    <!-- 静态资源处理, css, js, imgs -->
    <mvc:resources mapping="/resources/**" location="/resources/" />


    <!-- 配置ViewResolver。 可以用多个ViewResolver。 使用order属性排序。 InternalResourceViewResolver放在最后。 -->
    <bean
            class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="order" value="1"/>
        <!--<property name="mediaTypes">-->
            <!--<map>-->
                <!--<entry key="json" value="application/json"/>-->
                <!--<entry key="xml" value="application/xml"/>-->
                <!--<entry key="htm" value="text/html"/>-->
            <!--</map>-->
        <!--</property>-->

        <property name="defaultViews">
            <list>
                <!-- JSON View -->
                <bean
                        class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
                </bean>
            </list>
        </property>
        <!--<property name="ignoreAcceptHeader" value="true"/>-->
    </bean>

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsps/" />
        <property name="suffix" value=".jsp" />
    </bean>


    <!--200*1024*1024即200M resolveLazily属性启用是为了推迟文件解析,以便捕获文件大小异常 -->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="209715200"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="resolveLazily" value="true"/>
    </bean>

</beans>

 

运行后服务器后,请求http://localhost:8080/mvc/courses/123,发现浏览器(客户端)接收到了一个JSON格式的数据,正是我们的那个Course对象啊!

181130_SjTD_1156339.png

我们可以在Notepad++上安装一个插件JSON View来看看。

201924_Cvwv_1156339.png

结果:

202205_TAP1_1156339.png

总结一下,springMVC在这里完成了两件事情,一个是将bean对象转换成JSON数据格式,另一个是将其作为Response相应返回给客户端。在这个例子中,客户端发给服务器端一个URL请求,服务器应用将模型数据呈现为JSON数据格式返回。

这种@ResponseBody是最简单最简洁的方式,但如果不使用注解方式,还有一种实现方式:
 

    @RequestMapping(value="/jsontype/{courseId}",method=RequestMethod.GET)
    public ResponseEntity<Course> getCourseInJson2(@PathVariable Integer courseId){
        Course course =   courseService.getCoursebyId(courseId);
        return new ResponseEntity<Course>(course, HttpStatus.OK);
    }

同样,接收一个URL请求,从URL中取出路径中某段转换成方法参数中的参数值,控制器类的方法处理完之后,返回一个结果。但这个返回的记过不再是一个字符串,也不再是一个注解了@ResponseBody的bean,而是一个ResponseEntity的泛型对象。这个泛型对象的泛型参数类型是一个Bean类型,构造方法参数有两个:一个是bean对象,一个是HttpStatus枚举类型值。

运行结果如下:请求http://localhost:8080/mvc/courses/jsontype/345

211114_KO2r_1156339.png

好,本文最后我想说说现代开发模式下如何来开发:

现代的开发模式,一句话——前端和后端分离——如何分离:我们不再是需要在某个控制器中将多个不同的模型数据进行组织,然后同时传递给某个View统一组织;而是前端页面利用JS脚本 异步地 获取页面上各个地方需要显示的 不同的数据模型。MVC的后端开发人员不再需要为页面的显示需要再特定设计某个控制器实现,而是针对业务和数据模型本身设计好控制器方法,而让前端开发人员来在页面上按照需要请求不同的url来异步地获取不同的数据模型,分别组织显示数据,看到了吧,数据模型变成了一个个url资源,然后前端页面上异步地主动获取各个资源

好,我们模拟一个例子:我们在这种思想下编写了一个通过JSON数据异步获取数据模型的jsp页面:\course_json.jsp

213212_WfCe_1156339.png

注意:这个一定要放在WEB-INF之外,因为WEB-INF都是私有的,这里一定要暴露在公共领域!!!!

course_json.jsp如下:

<%--
  Created by IntelliJ IDEA.
  User: happBKs
  Date: 2016/7/7
  Time: 21:29
  To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>happBKs</title>

    <link rel="stylesheet"
          href="<%=request.getContextPath()%>/resources/css/main.css"
          type="text/css" />
    <script type="text/javascript"
            src="<%=request.getContextPath()%>/resources/js/jquery-1.11.3.min.js"></script>

</head>
<script>
    jQuery(function($){
        var urlStr = "<%=request.getContextPath()%>/courses/<%=request.getParameter("courseId")%>";
        //alert("Before Call:"+urlStr);
        $.ajax({
            method: "GET",
            url: urlStr,
            success:function(data,status,jqXHR){
                //alert("Success:"+data);
                var course = data;
                var path = "<%=request.getContextPath()%>/";
                $(".course-title").html(course.title);
                $(".course_video").attr("src", path+course.imgPath);
                $("#learningNum").text(course.learningNum);
                $("#duration").text(course.duration);
                $("#levelDesc").text(course.levelDesc);
                $(".course_shortdecription").html(course.descr);

                var chapterList = course.chapterList;
                var chapter;

                for(var i = 0;i<chapterList.length;i++){
                    chapter = chapterList[i];

                    var liObj = $("li",$("#chapterTemplate")).clone();
                    $(".outline_name", liObj).text(chapter.title);
                    $(".outline_descr", liObj).text(chapter.descr);
                    liObj.appendTo("#couList");
                }// ~ end for
            }
        }); // end ajax
    });
</script>
<body>


<div id="main">

    <div class="newcontainer" id="course_intro">
        <div class="course-title"></div>
        <div class="course_info">
            <div class="course-embed l">
                <div id="js-course-img" class="img-wrap">
                    <img width="600" height="340" alt=""
                         class="course_video" />
                </div>
                <div id="js-video-wrap" class="video" style="display: none">
                    <div class="video_box" id="js-video"></div>
                </div>
            </div>
            <div class="course_state">
                <ul>
                    <li><span>学习人数</span> <em id="learningNum"></em></li>
                    <li class="course_hour"><span>课程时长</span> <em
                            class="ft-adjust"><span id="duration"></span>秒</em></li>
                    <li><span>课程难度</span> <em id="levelDesc"></em></li>
                </ul>
            </div>

        </div>
        <div class="course_list">
            <div class="outline">
                <h3 class="chapter_introduces">课程介绍</h3>
                <div class="course_shortdecription"></div>

                <h3 class="chapter_catalog">课程提纲</h3>
                <ul id="couList">

                </ul>
            </div>

        </div>
    </div>

</div>

<div id="chapterTemplate"  style="display:none">
    <li class="clearfix open"><a href="#">
        <div class="openicon"></div>
        <div class="outline_list l">
            <h5 class="outline_name"></h5>
            <p class="outline_descr"></p>
        </div>
    </a></li>
</div>

</body>
</html>

这个页面通过ajax的方式异步地访问服务器请求,比如:

 jQuery(function($){
        var urlStr = "<%=request.getContextPath()%>/courses/<%=request.getParameter("courseId")%>";

JS脚本中通过url请求来获取服务器端的数据模型。

这里,我只是希望你了解这种编程方式,目的——前后端完美分离!

 

最后最后,做个总结:

本文提到的一个关键内容——ContentNegotiatingViewResolver,它将不同的数据呈现请求 转化成 不同的View,将不同格式的数据分发到不同的数据呈现请求,其中包含了我们的JSON。

第二,我们在服务端代码中,使用ResponseEntity这个泛型类来处理我们的返回结果,我们只需要将模型数据类对象导入到这个泛型中,就可以转化成我们的JSON格式。

另外一种,我们可以通过@ResponseBody注解来处理我们的返回数据。

类似地,利用@RequestBody 获取页面通过JSON方式提交给服务器端的格式,这个与@ResponseBody有异曲同工之妙:一个将请求的JSON数据接收转化为数据模型,一个将控制器返回结果转化为JSON格式。@RequestBody没有在本文中介绍,但是相信看我博客的coder们应该自己能知道怎么做了。

 

 

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/happyBKs/blog/707994

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值