SpringBoot实现jsonp跨域通信的方法示例

 更新时间:2019年09月17日 10:00:16   作者:inkroom  
这篇文章主要介绍了SpringBoot实现jsonp跨域通信的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

实现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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 深入理解@component与@Configuration注解

    深入理解@component与@Configuration注解

    这篇文章主要介绍了深入理解@component与@Configuration注解,从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被扫描,并用于构建bean定义,初始化Spring容器,需要的朋友可以参考下
    2023-11-11
  • SpringBoot深入分析讲解监听器模式下

    SpringBoot深入分析讲解监听器模式下

    监听器模式,大家应该并不陌生,主要的组成要素包括了事件、监听器以及广播器;当事件发生时,广播器负责将事件传递给所有已知的监听器,而监听器会对自己感兴趣的事件进行处理
    2022-07-07
  • Java Stream 流中 Collectors.toMap 的用法详解

    Java Stream 流中 Collectors.toMap 的用法详解

    这篇文章主要介绍了Stream 流中 Collectors.toMap 的用法,Collectors.toMap()方法是把List转Map的操作,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-01-01
  • 浅谈Java 中的单元测试

    浅谈Java 中的单元测试

    这篇文章主要介绍了Java 中的单元测试的相关资料,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-09-09
  • 基于javascript实现获取最短路径算法代码实例

    基于javascript实现获取最短路径算法代码实例

    这篇文章主要介绍了基于javascript实现获取最短路径算法代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • idea同时打开多个项目的图文教程

    idea同时打开多个项目的图文教程

    这篇文章主要给大家介绍了idea同时打开多个项目的图文教程,文章通过图文结合的形式给大家讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-02-02
  • Java中Hashtable集合的常用方法详解

    Java中Hashtable集合的常用方法详解

    本篇文章给大家带来的内容是关于Java中Hashtable集合的常用方法详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。下面我们就来学习一下吧
    2021-11-11
  • SpringMvc响应数据及结果视图实现代码

    SpringMvc响应数据及结果视图实现代码

    这篇文章主要介绍了SpringMvc响应数据及结果视图实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 用Java验证pdf文件的电子章签名

    用Java验证pdf文件的电子章签名

    这篇文章主要介绍了如何用Java验证pdf文件的电子章签名,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-12-12
  • Java编写实现多人聊天室

    Java编写实现多人聊天室

    这篇文章主要为大家详细介绍了Java编写实现多人聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09

最新评论