SpringBoot实现jsonp跨域通信

实现jsonp跨域通信

实现基于jsonp的跨域通信方案

原理

浏览器对非同源ajax请求有限制,不允许发送跨域请求
目前跨域解决方案有两种

  • cros配置
  • jsonp请求

cros为新规范,通过一个head请求询问服务器是否允许跨域,若不允许则被拦截
jsonp则为利用浏览器不限制js脚本的同源性,通过动态创建script请求,服务器传递回一个js函数调用语法,浏览器端按照js函数正常调用回调函数

实现思路

首先确定服务器端应该如何返回数据

一次正确的jsonp请求,服务器端应该返回如下格式数据

1jQuery39948237({key:3}) 2

其中,jQuery39948237为浏览器端要执行的函数名,该函数由ajax库动态创建,并将函数名作为一个请求参数和该次请求的其余参数一并发送,服务器端无需对此参数做过多处理

{key:3}为此次请求返回的数据,作为函数参数传递


其次,服务器端如何处理?

为了兼容jsonp和cros方案,服务器端应该在请求带有函数名参数时返回函数调用,否则正常返回json数据即可


最后,为了减少代码的侵入,不应该将上述流程放入一个Controller正常逻辑中,应该考虑使用aop实现

实现

前端

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

代码如下

1 $.ajax({ 2 url:'http://localhost:8999/boot/dto', 3 dataType:"jsonp", 4 success:(response)=>{ 5 this.messages.push(response); 6 } 7 }) 8

Jquery默认jsonp函数名参数name为callback

后端

本次采用aop实现

具体思路为: 给Controller添加后切点,判断request是否有函数名参数,如果有则修改返回的数据,没有则不做处理

而aop又有两种方案

  • 常规aop,自己定义切点
  • ResponseBodyAdvice,Spring提供的可直接用于数据返回的工具类

本次使用第二种方案


首先是Controller的接口实现

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

返回一个复杂类型,Spring会自动对其做json序列化操作


然后的ResponseBodyAdvice实现

该类全路径为:org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice

1/** 2 * 处理controller返回值,对于有callback值的使用jsonp格式,其余不处理 3 */ 4@RestControllerAdvice(basePackageClasses = IndexController.class) 5public class JsonpAdvice implements ResponseBodyAdvice { 6 7 private Logger logger = LoggerFactory.getLogger(getClass()); 8 @Autowired 9 private ObjectMapper mapper; 10 11 //jquery默认是callback,其余jsonp库可能不一样 12 private final String callBackKey = "callback"; 13 14 @Override 15 public boolean supports(MethodParameter methodParameter, Class aClass) { 16 logger.debug("返回的class={}", aClass); 17 return true; 18 } 19 20 /** 21 * 在此处对返回值进行处理,需要特别注意如果是非String类型,会被Json序列化,从而添加了双引号,解决办法见 22 * 23 * @param body 返回值 24 * @param methodParameter 方法参数 25 * @param mediaType 当前contentType,非String类型为json 26 * @param aClass convert的class 27 * @param serverHttpRequest request,暂时支持是ServletServerHttpRequest类型,其余类型将会原样返回 28 * @param serverHttpResponse response 29 * @return 如果body是String类型,加上方法头后返回,如果是其他类型,序列化后返回 30 * @see com.inkbox.boot.demo.converter.Jackson2HttpMessageConverter 31 */ 32 @Override 33 public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 34 35 if (body == null) 36 return null; 37 // 如果返回String类型,media是plain,否则是json,将会经过json序列化,在下方返回纯字符串之后依然会被序列化,就会添上多余的双引号 38 logger.debug("body={},request={},response={},media={}", body, serverHttpRequest, serverHttpResponse, mediaType.getSubtype()); 39 40 41 if (serverHttpRequest instanceof ServletServerHttpRequest) { 42 HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(); 43 44 String callback = request.getParameter(callBackKey); 45 46 if (!StringUtils.isEmpty(callback)) { 47 //使用了jsonp 48 if (body instanceof String) { 49 return callback + "(\"" + body + "\")"; 50 } else { 51 try { 52 String res = mapper.writeValueAsString(body); 53 logger.debug("转化后的返回值={},{}", res, callback + "(" + res + ")"); 54 55 return callback + "(" + res + ")"; 56 } catch (JsonProcessingException e) { 57 logger.warn("【jsonp支持】数据body序列化失败", e); 58 return body; 59 } 60 } 61 } 62 } else { 63 logger.warn("【jsonp支持】不支持的request class ={}", serverHttpRequest.getClass()); 64 } 65 return body; 66 } 67}

使用@RestControllerAdvice指明切点

bug

经过此步骤,理论上即可实现jsonp调用了。

然而实际测试发现,由于Spring json序列化策略的问题,如果返回jsonp字符串,json序列化之后,将会添上一对引号,如下

应该返回

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

实际返回

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

导致浏览器端无法正常运行函数


经多方查找资料后得知

由于在ResponseBodyAdvice中修改了实际的返回值类型为String,而字符串类型经过Jackson序列化后就会加上引号

解决办法为:修改默认的json序列化MessageConverter处理逻辑,对于实际是String的不做处理

代码如下

1@Component 2public class Jackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { 3 4 private Logger logger = LoggerFactory.getLogger(getClass()); 5 6 @Override 7 protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { 8 if (object instanceof String) { 9 //绕开实际上返回的String类型,不序列化 10 Charset charset = this.getDefaultCharset(); 11 StreamUtils.copy((String) object, charset, outputMessage.getBody()); 12 } else { 13 super.writeInternal(object, type, outputMessage); 14 } 15 } 16} 17 18 19@Configuration 20public class MvcConfig implements WebMvcConfigurer { 21 22 private Logger logger = LoggerFactory.getLogger(getClass()); 23 24 @Autowired 25 private MappingJackson2HttpMessageConverter converter; 26 27 @Override 28 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 29// MappingJackson2HttpMessageConverter converter = mappingJackson2HttpMessageConverter(); 30 converter.setSupportedMediaTypes(new LinkedList<MediaType>() {{ 31 add(MediaType.TEXT_HTML); 32 add(MediaType.APPLICATION_JSON_UTF8); 33 }}); 34 converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); 35 converters.add(converter); 36 } 37} 38

todo

暂时不明白为什么需要两个类搭配使用

代码

具体实现可查阅github

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

springboot的跨域

https://www.cnblogs.com/520playboy/p/7306008.html1、对于前后端分离的项目来说,如果前端项目与后端项目部署在两个不同的域下,那么势必会引起跨域问题的出现。针对跨域问题,我们可能第一个想到的解决方案就是jsonp,并且以前处理跨域问题我基本也是这么处理。但是jsonp方式也同样有不足,不管是对