1. 程式人生 > >擴充套件springMVC訊息轉換器來支援jsonp資料格式

擴充套件springMVC訊息轉換器來支援jsonp資料格式

1.JSONP是用來解決json跨域問題的技術。即將傳輸的json轉化為js指令碼。

Callback是JSONP的實現的一種方式,例:getData([{"id":1,"title":"XXXX"},{"id":2,"title":"YYYYY"}])當瀏覽器讀取到funName(json文字)就會用js的語法解析,然後呼叫定義好的js getData函式,以回撥函式的形式獲取json資料。

2.為什麼要在spring mvc中對於jsonp要自己處理?

因為spring mvc預設是不支援jsonp的,所以需要我們自己處理

3.如何在伺服器端實現對JSONP支援

    根據前臺請求url是否攜帶callback,來判斷是否需要jsonp。

a)如果攜帶,說明出現跨域問題,伺服器就需要對跨域問題進行處理,處理方式這裡採用callback,方法執行完後伺服器會返回指定callback名的js script。

b)如果沒攜帶,說明沒有跨域問題,伺服器返回普通json

之前的解決方式:

在controller中自行處理,判斷請求中是否攜帶callback回撥函式名。如果沒有,則不對json進行處理。這種情況多適用於同一個系統下的ajax請求,不出現ajax跨域問題情況下。如果有,就對json進行處理,使其符合jsonp規範。這種情況適用於不同系統的ajax請求,出現跨域問題的情況下。
package com.taotao.manager.controller.api;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.taotao.common.ItemCatResult;
import com.taotao.manager.service.ItemCatService;

@Controller
@RequestMapping("/api/item/cat")
public class ApiItemCatController {
	@Resource
	private ItemCatService itemCatService;
	
	private ObjectMapper objectMapper=new ObjectMapper();
	@RequestMapping(method=RequestMethod.GET)
	/**
	 * required=false 當訪問此方法沒有攜帶對應callback的引數值,則屬性值預設為null。如果不設定並且沒有攜帶資料,會丟擲異常
	 * @param callback  回撥函式名
	 * @return 符合jsonp規範的json格式資料
	 */
	public ResponseEntity<String> all(@RequestParam(value="callback",required=false) String callback){
		try {
			ItemCatResult result = itemCatService.queryAllToTree();//從後臺查詢處資料
			if(result!=null){
				String resultJson = objectMapper.writeValueAsString(result);//jackson元件把後臺資料轉化為json
				if(StringUtils.isEmpty(callback)){//前臺是否傳callback回撥函式名
					return ResponseEntity.ok(resultJson);//沒有的話,返回普通的json
				}else{
					return ResponseEntity.ok(callback+"("+resultJson+")");//有,返回封裝後的json
				}
			}
			return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
	}
}

因為返回型別是ResponseEntity<String>,後臺string物件轉換為前臺資料是在StringHttpMessageConverter訊息轉換器中轉換的,它預設的編碼是iso-8859-1,前臺接收的資料如果產生亂碼,我們需要在spring-mvc配置檔案中寫如下配置

<mvc:annotation-driven>
		<!-- 自定義訊息轉換器 -->
		<mvc:message-converters register-defaults="true">
			<!-- 自定義訊息轉換器,設定編碼為utf-8,防止responseEntity<String>轉換成json物件輸出亂碼 -->
			<bean class="org.springframework.http.converter.StringHttpMessageConverter">
				<constructor-arg index="0" value="utf-8"></constructor-arg>
			</bean> 
		</mvc:message-converters>
	</mvc:annotation-driven>


這種方式實現比較麻煩。因為在每個需要json跨域問題的controller中都要自己手動寫程式碼判斷處理

通過擴充套件springMVC訊息轉換器使得springMVC預設支援jsonp

首先寫一個類繼承org.springframework.http.converter.json.MappingJackson2HttpMessageConverter類,並重寫writeInternal()方法
package com.taotao.common.spring.exetend.converter.json;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonProcessingException;

public class CallbackMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

	// 做jsonp的支援的標識,在請求引數中加該引數
	private String callbackName;

	@Override
	protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
		// 從threadLocal中獲取當前的Request物件
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
		String callbackParam = request.getParameter(callbackName);
		if(StringUtils.isEmpty(callbackParam)){
			// 沒有找到callback引數,直接返回json資料
			super.writeInternal(object, outputMessage);
		}else{
			JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
			try {
				String result =callbackParam+"("+super.getObjectMapper().writeValueAsString(object)+");";
				IOUtils.write(result, outputMessage.getBody(),encoding.getJavaName());
			}
			catch (JsonProcessingException ex) {
				throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
			}
		}
		
	}

	public String getCallbackName() {
		return callbackName;
	}

	public void setCallbackName(String callbackName) {
		this.callbackName = callbackName;
	}

}
然後在springMVC的配置檔案中進行配置
	<mvc:annotation-driven>
		<!-- 自定義訊息轉換器 -->
		<mvc:message-converters>
			<!-- 自定義支援jsonp的MappingJackson2HttpMessageConverter訊息轉發器 -->
			<bean class="com.taotao.common.spring.exetend.converter.json.CallbackMappingJackson2HttpMessageConverter">
				<!-- 做jsonp的支援的標識,回撥函式名,在請求引數中加該引數 -->
				<property name="callbackName" value="callback"></property>
			</bean>
		</mvc:message-converters>
	</mvc:annotation-driven>
然後controller還是可以按照原來的寫法,但此時已經預設支援jsonp
package com.taotao.manager.controller.api;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.taotao.common.ItemCatResult;
import com.taotao.manager.service.ItemCatService;

@Controller
@RequestMapping("/api/item/cat")
public class ApiItemCatController {
	@Resource
	private ItemCatService itemCatService;
	
	private ObjectMapper objectMapper=new ObjectMapper();
	@RequestMapping(method=RequestMethod.GET)
	/**
	 * required=false 當訪問此方法沒有攜帶對應callback的引數值,則屬性值預設為null。如果不設定並且沒有攜帶資料,會丟擲異常
	 * @param callback  回撥函式名
	 * @return 符合jsonp規範的json格式資料
	 */
	public ResponseEntity<ItemCatResult> all(@RequestParam(value="callback",required=false) String callback){
	try {
		ItemCatResult result = itemCatService.queryAllToTree();
		if(result!=null){
			return ResponseEntity.ok(result);
		}
		return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
	}




測試: 普通的獲取json方式:http://manage.taotao.com/rest/api/item/cat
解決跨域請求問題下,請求json資料的url:http://manage.taotao.com/rest/api/item/cat?callback=category.getDataService