springMVC筆記系列(22)——Json各種操做在springMVC中的實現

Json使得更多的開發能夠在單頁面完成,大大改變了現在人們的開發方式,使得前端和後端的分離更加完全。Json是一種輕量級的數據交換格式。Json與XML格式同樣具備良好的結構,但格式更爲簡潔。Json在Restfule Web Service中發揮了重要的做用,使得Restful Web Service成爲業界Web Service的標準,逐漸取代了比較「重」的SOAP。javascript

SOAP(Simple Object Access Protocol)簡單對象訪問協議,是基於HTTP的一種異構系統通訊的協議,說白了就是xml文檔傳輸。css

REST即表述性狀態傳遞(英文:Representational State Transfer,簡稱REST)是Roy Fielding博士在2000年他的博士論文中提出來的一種軟件架構風格。其核心是面向資源,REST專門針對網絡應用設計和開發方式,以下降開發的複雜性,提升系統的可伸縮性。REST提出設計概念和準則爲:html

1.網絡上的全部事物均可以被抽象爲資源(resource)
2.每個資源都有惟一的資源標識(resource identifier),對資源的操做不會改變這些標識
3.全部的操做都是無狀態的前端

REST簡化開發,其架構遵循CRUD原則,該原則告訴咱們對於資源(包括網絡資源)只須要四種行爲:建立,獲取,更新和刪除就能夠完成相關的操做和處理。您能夠經過統一資源標識符(Universal Resource Identifier,URI)來識別和定位資源,而且針對這些資源而執行的操做是經過 HTTP 規範定義的。其核心操做只有GET,PUT,POST,DELETE。java

因爲REST強制全部的操做都必須是stateless的,這就沒有上下文的約束,若是作分佈式,集羣都不須要考慮上下文和會話保持的問題。極大的提升系統的可伸縮性。jquery

 

Restful Web Service與SOAP Web Service區別先來個直觀比較:web

rest輕量級,SOAP重量級;ajax

rest學習起來比較簡單,容易上手,SOAP相對來講難些;spring

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都是數據模型的不一樣表現形式。

 

基於這樣的思想,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;

  view viewResolver
jsp WEB-INF/views/demoObj.jsp UrlBasedViewResolver或InternalResourceViewResolver
pdf PdfView PdfViewResolver
json MappingJackson2JsonView JsonViewResolver
xml MarshallingView XmlViewResolver
xls XlsView XlsViewResolver

配置方法就是在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也提示了沒法處理這兩個屬性。

探究緣由,咱們進入ContentNegotiatingViewResolver類的源代碼,能夠發現,從某個版本開始,ContentNegotiatingViewResolver的mediaTypes屬性已經只有get方法而沒有set方法來了,並且getMediaTypes也變成了protected權限。因此,mvc-dispatcher-servlet.xml中沒法用配置的值注入到ContentNegotiatingViewResolver的mediaTypes等屬性也是能夠預見的。

因此,咱們就將這兩個屬性給註釋掉吧:)

完整的\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對象啊!

咱們能夠在Notepad++上安裝一個插件JSON View來看看。

結果:

總結一下,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

好,本文最後我想說說現代開發模式下如何來開發:

現代的開發模式,一句話——前端和後端分離——如何分離:咱們再也不是須要在某個控制器中將多個不一樣的模型數據進行組織,而後同時傳遞給某個View統一組織;而是前端頁面利用JS腳本 異步地 獲取頁面上各個地方須要顯示的 不一樣的數據模型。MVC的後端開發人員再也不須要爲頁面的顯示須要再特定設計某個控制器實現,而是針對業務和數據模型自己設計好控制器方法,而讓前端開發人員來在頁面上按照須要請求不一樣的url來異步地獲取不一樣的數據模型,分別組織顯示數據,看到了吧,數據模型變成了一個個url資源,而後前端頁面上異步地主動獲取各個資源

好,咱們模擬一個例子:咱們在這種思想下編寫了一個經過JSON數據異步獲取數據模型的jsp頁面:\course_json.jsp

注意:這個必定要放在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們應該本身能知道怎麼作了。

相關文章
相關標籤/搜索