中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot實現jsonp跨域通信的方法示例

發布時間:2020-09-20 17:26:11 來源:腳本之家 閱讀:189 作者:inkroom 欄目:編程語言

實現jsonp跨域通信

實現基于jsonp的跨域通信方案

原理

瀏覽器對非同源ajax請求有限制,不允許發送跨域請求

目前跨域解決方案有兩種

  • cros配置
  • jsonp請求

cros為新規范,通過一個head請求詢問服務器是否允許跨域,若不允許則被攔截

jsonp則為利用瀏覽器不限制js腳本的同源性,通過動態創建script請求,服務器傳遞回一個js函數調用語法,瀏覽器端按照js函數正常調用回調函數

實現思路

首先確定服務器端應該如何返回數據

一次正確的jsonp請求,服務器端應該返回如下格式數據

jQuery39948237({key:3})

其中, jQuery39948237 為瀏覽器端要執行的函數名,該函數由ajax庫動態創建,并將函數名作為一個請求參數和該次請求的其余參數一并發送,服務器端無需對此參數做過多處理

{key:3} 為此次請求返回的數據,作為函數參數傳遞

其次,服務器端如何處理?

為了兼容jsonp和cros方案,服務器端應該在請求帶有函數名參數時返回函數調用,否則正常返回json數據即可

最后,為了減少代碼的侵入,不應該將上述流程放入一個Controller正常邏輯中,應該考慮使用aop實現

實現

前端

前端本次使用jquery庫~~(本來想用axios庫的,但是axios不支持jsonp)~~

代碼如下

$.ajax({
    url:'http://localhost:8999/boot/dto',
    dataType:"jsonp",
    success:(response)=>{
      this.messages.push(response);
    }
  })

Jquery默認jsonp函數名參數name為 callback

后端

本次采用aop實現

具體思路為: 給Controller添加后切點,判斷request是否有函數名參數,如果有則修改返回的數據,沒有則不做處理

而aop又有兩種方案

  1. 常規aop,自己定義切點
  2. ResponseBodyAdvice,Spring提供的可直接用于數據返回的工具類

本次使用第二種方案

首先是Controller的接口實現

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

返回一個復雜類型,Spring會自動對其做json序列化操作

然后的 ResponseBodyAdvice 實現

該類全路徑為: 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  方法參數
   * @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支持】數據body序列化失敗", e);
            return body;
          }
        }
      }
    } else {
      logger.warn("【jsonp支持】不支持的request class ={}", serverHttpRequest.getClass());
    }
    return body;
  }
}

使用 @RestControllerAdvice 指明切點

bug

經過此步驟,理論上即可實現jsonp調用了。

然而實際測試發現,由于Spring json序列化策略的問題,如果返回jsonp字符串,json序列化之后,將會添上一對引號,如下

應該返回

Jquery332({"x":239,"y":43})

實際返回

"Jquery332({\"x\":239,\"y\":43})"

導致瀏覽器端無法正常運行函數

經多方查找資料后得知

由于在 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

暫時不明白為什么需要兩個類搭配使用

代碼

具體實現可查閱github

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

沙田区| 明水县| 石门县| 分宜县| 华容县| 两当县| 吴川市| 内黄县| 丰宁| 平舆县| 白沙| 曲靖市| 洮南市| 景洪市| 毕节市| 东至县| 阜宁县| 康平县| 城固县| 沅陵县| 新宁县| 海宁市| 文成县| 金寨县| 青州市| 黄石市| 安福县| 龙门县| 宝丰县| 长沙市| 民权县| 曲阳县| 徐闻县| 阳东县| 方城县| 绥德县| 余干县| 米泉市| 湘乡市| 汶上县| 诏安县|