SpringBoot實(shí)現(xiàn)jsonp跨域通信的方法示例
實(shí)現(xiàn)jsonp跨域通信
實(shí)現(xiàn)基于jsonp的跨域通信方案
原理
瀏覽器對(duì)非同源ajax請(qǐng)求有限制,不允許發(fā)送跨域請(qǐng)求
目前跨域解決方案有兩種
- cros配置
- jsonp請(qǐng)求
cros為新規(guī)范,通過(guò)一個(gè)head請(qǐng)求詢問(wèn)服務(wù)器是否允許跨域,若不允許則被攔截
jsonp則為利用瀏覽器不限制js腳本的同源性,通過(guò)動(dòng)態(tài)創(chuàng)建script請(qǐng)求,服務(wù)器傳遞回一個(gè)js函數(shù)調(diào)用語(yǔ)法,瀏覽器端按照js函數(shù)正常調(diào)用回調(diào)函數(shù)
實(shí)現(xiàn)思路
首先確定服務(wù)器端應(yīng)該如何返回?cái)?shù)據(jù)
一次正確的jsonp請(qǐng)求,服務(wù)器端應(yīng)該返回如下格式數(shù)據(jù)
jQuery39948237({key:3})
其中, jQuery39948237 為瀏覽器端要執(zhí)行的函數(shù)名,該函數(shù)由ajax庫(kù)動(dòng)態(tài)創(chuàng)建,并將函數(shù)名作為一個(gè)請(qǐng)求參數(shù)和該次請(qǐng)求的其余參數(shù)一并發(fā)送,服務(wù)器端無(wú)需對(duì)此參數(shù)做過(guò)多處理
{key:3} 為此次請(qǐng)求返回的數(shù)據(jù),作為函數(shù)參數(shù)傳遞
其次,服務(wù)器端如何處理?
為了兼容jsonp和cros方案,服務(wù)器端應(yīng)該在請(qǐng)求帶有函數(shù)名參數(shù)時(shí)返回函數(shù)調(diào)用,否則正常返回json數(shù)據(jù)即可
最后,為了減少代碼的侵入,不應(yīng)該將上述流程放入一個(gè)Controller正常邏輯中,應(yīng)該考慮使用aop實(shí)現(xiàn)
實(shí)現(xiàn)
前端
前端本次使用jquery庫(kù)~~(本來(lái)想用axios庫(kù)的,但是axios不支持jsonp)~~
代碼如下
$.ajax({
url:'http://localhost:8999/boot/dto',
dataType:"jsonp",
success:(response)=>{
this.messages.push(response);
}
})
Jquery默認(rèn)jsonp函數(shù)名參數(shù)name為 callback
后端
本次采用aop實(shí)現(xiàn)
具體思路為: 給Controller添加后切點(diǎn),判斷request是否有函數(shù)名參數(shù),如果有則修改返回的數(shù)據(jù),沒(méi)有則不做處理
而aop又有兩種方案
- 常規(guī)aop,自己定義切點(diǎn)
- ResponseBodyAdvice,Spring提供的可直接用于數(shù)據(jù)返回的工具類
本次使用第二種方案
首先是Controller的接口實(shí)現(xiàn)
@RequestMapping("dto")
public Position dto() {
return new Position(239, 43);
}
返回一個(gè)復(fù)雜類型,Spring會(huì)自動(dòng)對(duì)其做json序列化操作
然后的 ResponseBodyAdvice 實(shí)現(xiàn)
該類全路徑為: org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
/**
* 處理controller返回值,對(duì)于有callback值的使用jsonp格式,其余不處理
*/
@RestControllerAdvice(basePackageClasses = IndexController.class)
public class JsonpAdvice implements ResponseBodyAdvice {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper mapper;
//jquery默認(rèn)是callback,其余jsonp庫(kù)可能不一樣
private final String callBackKey = "callback";
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
logger.debug("返回的class={}", aClass);
return true;
}
/**
* 在此處對(duì)返回值進(jìn)行處理,需要特別注意如果是非String類型,會(huì)被Json序列化,從而添加了雙引號(hào),解決辦法見
*
* @param body 返回值
* @param methodParameter 方法參數(shù)
* @param mediaType 當(dāng)前contentType,非String類型為json
* @param aClass convert的class
* @param serverHttpRequest request,暫時(shí)支持是ServletServerHttpRequest類型,其余類型將會(huì)原樣返回
* @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,將會(huì)經(jīng)過(guò)json序列化,在下方返回純字符串之后依然會(huì)被序列化,就會(huì)添上多余的雙引號(hào)
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("轉(zhuǎn)化后的返回值={},{}", 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 指明切點(diǎn)
bug
經(jīng)過(guò)此步驟,理論上即可實(shí)現(xiàn)jsonp調(diào)用了。
然而實(shí)際測(cè)試發(fā)現(xiàn),由于Spring json序列化策略的問(wèn)題,如果返回jsonp字符串,json序列化之后,將會(huì)添上一對(duì)引號(hào),如下
應(yīng)該返回
Jquery332({"x":239,"y":43})
實(shí)際返回
"Jquery332({\"x\":239,\"y\":43})"
導(dǎo)致瀏覽器端無(wú)法正常運(yùn)行函數(shù)
經(jīng)多方查找資料后得知
由于在 ResponseBodyAdvice 中修改了實(shí)際的返回值類型為 String ,而字符串類型經(jīng)過(guò) Jackson 序列化后就會(huì)加上引號(hào)
解決辦法為:修改默認(rèn)的json序列化 MessageConverter 處理邏輯,對(duì)于實(shí)際是 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) {
//繞開實(shí)際上返回的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
暫時(shí)不明白為什么需要兩個(gè)類搭配使用
代碼
具體實(shí)現(xiàn)可查閱github
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 詳解JS同源策略和CSRF
- JavaScript同源策略和跨域訪問(wèn)實(shí)例詳解
- JS實(shí)現(xiàn)的ajax和同源策略(實(shí)例講解)
- js同源策略詳解
- 如何利用js在兩個(gè)html窗口間通信
- JavaScript中EventBus實(shí)現(xiàn)對(duì)象之間通信
- 淺談vue websocket nodeJS 進(jìn)行實(shí)時(shí)通信踩到的坑
- Vue.js組件通信之自定義事件詳解
- Vue.js子組件向父組件通信的方法實(shí)例代碼詳解
- 在vue中使用SockJS實(shí)現(xiàn)webSocket通信的過(guò)程
- JavaScript 如何實(shí)現(xiàn)同源通信
相關(guān)文章
Spring Security動(dòng)態(tài)權(quán)限的實(shí)現(xiàn)方法詳解
這篇文章主要和小伙伴們簡(jiǎn)單介紹下 Spring Security 中的動(dòng)態(tài)權(quán)限方案,以便于小伙伴們更好的理解 TienChin 項(xiàng)目中的權(quán)限方案,感興趣的可以了解一下2022-06-06
Java RabbitMQ的工作隊(duì)列與消息應(yīng)答詳解
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
使用IntelliJ?IDEA創(chuàng)建簡(jiǎn)單的Java?Web項(xiàng)目完整步驟
這篇文章主要介紹了如何使用IntelliJ?IDEA創(chuàng)建一個(gè)簡(jiǎn)單的JavaWeb項(xiàng)目,實(shí)現(xiàn)登錄、注冊(cè)和查看用戶列表功能,使用Servlet和JSP技術(shù),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01
代理角色java設(shè)計(jì)模式之靜態(tài)代理詳細(xì)介紹
查了好多資料,發(fā)現(xiàn)還是不全,干脆自己整理吧,至少保證在我的做法正確的,以免誤導(dǎo)讀者,也是給自己做個(gè)記錄吧!2013-05-05
SpringMvc接收參數(shù)方法總結(jié)(必看篇)
下面小編就為大家?guī)?lái)一篇SpringMvc接收參數(shù)方法總結(jié)(必看篇)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例
這篇文章主要介紹了微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
mybatis-plus插入一條數(shù)據(jù),獲取插入數(shù)據(jù)自動(dòng)生成的主鍵問(wèn)題
這篇文章主要介紹了mybatis-plus插入一條數(shù)據(jù),獲取插入數(shù)據(jù)自動(dòng)生成的主鍵問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
SpringBoot框架實(shí)現(xiàn)支付和轉(zhuǎn)賬功能
在 Spring Boot 框架中實(shí)現(xiàn)支付和轉(zhuǎn)賬功能時(shí),涉及到多個(gè)細(xì)節(jié)和注意點(diǎn),這些功能通常需要高度的安全性、穩(wěn)定性和可擴(kuò)展性,本文介紹了實(shí)現(xiàn)支付和轉(zhuǎn)賬功能的一些關(guān)鍵點(diǎn),需要的朋友可以參考下2024-08-08

