SpringBoot實(shí)現(xiàn)jsonp跨域通信的方法示例

實(shí)現(xiàn)jsonp跨域通信

成都創(chuàng)新互聯(lián)公司是一家專(zhuān)注網(wǎng)站建設(shè)、網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃、成都微信小程序、電子商務(wù)建設(shè)、網(wǎng)絡(luò)推廣、移動(dòng)互聯(lián)開(kāi)發(fā)、研究、服務(wù)為一體的技術(shù)型公司。公司成立10年以來(lái),已經(jīng)為超過(guò)千家紗窗各業(yè)的企業(yè)公司提供互聯(lián)網(wǎng)服務(wù)?,F(xiàn)在,服務(wù)的超過(guò)千家客戶(hù)與我們一路同行,見(jiàn)證我們的成長(zhǎng);未來(lái),我們一起分享成功的喜悅。

實(shí)現(xiàn)基于jsonp的跨域通信方案

原理

瀏覽器對(duì)非同源ajax請(qǐng)求有限制,不允許發(fā)送跨域請(qǐng)求

目前跨域解決方案有兩種

  • cros配置
  • jsonp請(qǐng)求

cros為新規(guī)范,通過(guò)一個(gè)head請(qǐng)求詢(xún)問(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又有兩種方案

  1. 常規(guī)aop,自己定義切點(diǎn)
  2. ResponseBodyAdvice,Spring提供的可直接用于數(shù)據(jù)返回的工具類(lèi)

本次使用第二種方案

首先是Controller的接口實(shí)現(xiàn)

@RequestMapping("dto")
public Position dto() {
  return new Position(239, 43);
}

返回一個(gè)復(fù)雜類(lèi)型,Spring會(huì)自動(dòng)對(duì)其做json序列化操作

然后的 ResponseBodyAdvice 實(shí)現(xiàn)

該類(lèi)全路徑為: 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類(lèi)型,會(huì)被Json序列化,從而添加了雙引號(hào),解決辦法見(jiàn)
   *
   * @param body        返回值
   * @param methodParameter  方法參數(shù)
   * @param mediaType     當(dāng)前contentType,非String類(lèi)型為json
   * @param aClass       convert的class
   * @param serverHttpRequest request,暫時(shí)支持是ServletServerHttpRequest類(lèi)型,其余類(lèi)型將會(huì)原樣返回
   * @param serverHttpResponse response
   * @return 如果body是String類(lèi)型,加上方法頭后返回,如果是其他類(lèi)型,序列化后返回
   * @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類(lèi)型,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í)際的返回值類(lèi)型為 String ,而字符串類(lèi)型經(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) {
      //繞開(kāi)實(shí)際上返回的String類(lèi)型,不序列化
      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è)類(lèi)搭配使用

代碼

具體實(shí)現(xiàn)可查閱github

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

新聞名稱(chēng):SpringBoot實(shí)現(xiàn)jsonp跨域通信的方法示例
標(biāo)題鏈接:http://bm7419.com/article18/jjeedp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、網(wǎng)站制作Google、品牌網(wǎng)站制作移動(dòng)網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都app開(kāi)發(fā)公司