SpringBoot實現(xiàn)jsonp跨域通信的方法示例
實現(xiàn)jsonp跨域通信
實現(xiàn)基于jsonp的跨域通信方案
原理
瀏覽器對非同源ajax請求有限制,不允許發(fā)送跨域請求
目前跨域解決方案有兩種
- cros配置
- jsonp請求
cros為新規(guī)范,通過一個head請求詢問服務器是否允許跨域,若不允許則被攔截
jsonp則為利用瀏覽器不限制js腳本的同源性,通過動態(tài)創(chuàng)建script請求,服務器傳遞回一個js函數(shù)調用語法,瀏覽器端按照js函數(shù)正常調用回調函數(shù)
實現(xiàn)思路
首先確定服務器端應該如何返回數(shù)據(jù)
一次正確的jsonp請求,服務器端應該返回如下格式數(shù)據(jù)
jQuery39948237({key:3})
其中, jQuery39948237 為瀏覽器端要執(zhí)行的函數(shù)名,該函數(shù)由ajax庫動態(tài)創(chuàng)建,并將函數(shù)名作為一個請求參數(shù)和該次請求的其余參數(shù)一并發(fā)送,服務器端無需對此參數(shù)做過多處理
{key:3} 為此次請求返回的數(shù)據(jù),作為函數(shù)參數(shù)傳遞
其次,服務器端如何處理?
為了兼容jsonp和cros方案,服務器端應該在請求帶有函數(shù)名參數(shù)時返回函數(shù)調用,否則正常返回json數(shù)據(jù)即可
最后,為了減少代碼的侵入,不應該將上述流程放入一個Controller正常邏輯中,應該考慮使用aop實現(xiàn)
實現(xiàn)
前端
前端本次使用jquery庫~~(本來想用axios庫的,但是axios不支持jsonp)~~
代碼如下
$.ajax({ url:'http://localhost:8999/boot/dto', dataType:"jsonp", success:(response)=>{ this.messages.push(response); } })
Jquery默認jsonp函數(shù)名參數(shù)name為 callback
后端
本次采用aop實現(xiàn)
具體思路為: 給Controller添加后切點,判斷request是否有函數(shù)名參數(shù),如果有則修改返回的數(shù)據(jù),沒有則不做處理
而aop又有兩種方案
- 常規(guī)aop,自己定義切點
- ResponseBodyAdvice,Spring提供的可直接用于數(shù)據(jù)返回的工具類
本次使用第二種方案
首先是Controller的接口實現(xiàn)
@RequestMapping("dto") public Position dto() { return new Position(239, 43); }
返回一個復雜類型,Spring會自動對其做json序列化操作
然后的 ResponseBodyAdvice 實現(xiàn)
該類全路徑為: org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
/** * 處理controller返回值,對于有callback值的使用jsonp格式,其余不處理 */ @RestControllerAdvice(basePackageClasses = IndexController.class) public class JsonpAdvice implements ResponseBodyAdvice { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ObjectMapper mapper; //jquery默認是callback,其余jsonp庫可能不一樣 private final String callBackKey = "callback"; @Override public boolean supports(MethodParameter methodParameter, Class aClass) { logger.debug("返回的class={}", aClass); return true; } /** * 在此處對返回值進行處理,需要特別注意如果是非String類型,會被Json序列化,從而添加了雙引號,解決辦法見 * * @param body 返回值 * @param methodParameter 方法參數(shù) * @param mediaType 當前contentType,非String類型為json * @param aClass convert的class * @param serverHttpRequest request,暫時支持是ServletServerHttpRequest類型,其余類型將會原樣返回 * @param serverHttpResponse response * @return 如果body是String類型,加上方法頭后返回,如果是其他類型,序列化后返回 * @see com.inkbox.boot.demo.converter.Jackson2HttpMessageConverter */ @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (body == null) return null; // 如果返回String類型,media是plain,否則是json,將會經過json序列化,在下方返回純字符串之后依然會被序列化,就會添上多余的雙引號 logger.debug("body={},request={},response={},media={}", body, serverHttpRequest, serverHttpResponse, mediaType.getSubtype()); if (serverHttpRequest instanceof ServletServerHttpRequest) { HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(); String callback = request.getParameter(callBackKey); if (!StringUtils.isEmpty(callback)) { //使用了jsonp if (body instanceof String) { return callback + "(\"" + body + "\")"; } else { try { String res = mapper.writeValueAsString(body); logger.debug("轉化后的返回值={},{}", res, callback + "(" + res + ")"); return callback + "(" + res + ")"; } catch (JsonProcessingException e) { logger.warn("【jsonp支持】數(shù)據(jù)body序列化失敗", e); return body; } } } } else { logger.warn("【jsonp支持】不支持的request class ={}", serverHttpRequest.getClass()); } return body; } }
使用 @RestControllerAdvice 指明切點
bug
經過此步驟,理論上即可實現(xiàn)jsonp調用了。
然而實際測試發(fā)現(xiàn),由于Spring json序列化策略的問題,如果返回jsonp字符串,json序列化之后,將會添上一對引號,如下
應該返回
Jquery332({"x":239,"y":43})
實際返回
"Jquery332({\"x\":239,\"y\":43})"
導致瀏覽器端無法正常運行函數(shù)
經多方查找資料后得知
由于在 ResponseBodyAdvice 中修改了實際的返回值類型為 String ,而字符串類型經過 Jackson 序列化后就會加上引號
解決辦法為:修改默認的json序列化 MessageConverter 處理邏輯,對于實際是 String 的不做處理
代碼如下
@Component public class Jackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { private Logger logger = LoggerFactory.getLogger(getClass()); @Override protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { if (object instanceof String) { //繞開實際上返回的String類型,不序列化 Charset charset = this.getDefaultCharset(); StreamUtils.copy((String) object, charset, outputMessage.getBody()); } else { super.writeInternal(object, type, outputMessage); } } } @Configuration public class MvcConfig implements WebMvcConfigurer { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private MappingJackson2HttpMessageConverter converter; @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // MappingJackson2HttpMessageConverter converter = mappingJackson2HttpMessageConverter(); converter.setSupportedMediaTypes(new LinkedList<MediaType>() {{ add(MediaType.TEXT_HTML); add(MediaType.APPLICATION_JSON_UTF8); }}); converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); converters.add(converter); } }
todo
暫時不明白為什么需要兩個類搭配使用
代碼
具體實現(xiàn)可查閱github
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
深入理解@component與@Configuration注解
這篇文章主要介紹了深入理解@component與@Configuration注解,從Spring3.0,@Configuration用于定義配置類,可替換xml配置文件,被注解的類內部包含有一個或多個被@Bean注解的方法,這些方法將會被掃描,并用于構建bean定義,初始化Spring容器,需要的朋友可以參考下2023-11-11Java?Stream?流中?Collectors.toMap?的用法詳解
這篇文章主要介紹了Stream?流中?Collectors.toMap?的用法,Collectors.toMap()方法是把List轉Map的操作,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-01-01基于javascript實現(xiàn)獲取最短路徑算法代碼實例
這篇文章主要介紹了基于javascript實現(xiàn)獲取最短路徑算法代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02SpringMvc響應數(shù)據(jù)及結果視圖實現(xiàn)代碼
這篇文章主要介紹了SpringMvc響應數(shù)據(jù)及結果視圖實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-08-08