借一古老技术考察你对SpringBoot掌握程度

开发 前端
我们的服务端又该实现呢?直接在对应的接口中进行修改吗?如果直接修改接口,那么当我又希望返回的是数据又该如何,重新再来一个接口吗?

环境:Spring3.2.5

本篇文章将通过一个古老的技术JSONP来考察在座的对SpringBoot中某些技术的掌握程度。

1. 简介

JSONP(JSON with Padding)是一种非官方的协议,主要用于解决浏览器跨域数据访问的问题。它利用HTML的<script>标签可以跨域加载资源的特性,通过服务器端生成包含JSON数据的JavaScript函数调用,并返回给客户端执行。客户端需要预先定义好回调函数,以便在数据加载完毕后接收并处理数据。JSONP简单易用,但仅支持GET请求,且存在安全风险,如XSS攻击和CSRF攻击。随着技术的发展,CORS等更安全的跨域解决方案逐渐取代了JSONP。

关于JSONP的应用示例

现有如下接口地址:http://localhost:9100/jsonps,返回数据如下:

[{"id":1,"name":"张三"},{"id":2,"name":"李四"},{"id":3,"name":"王五"}]

JSONP需要我们传递一个类似回调的参数,服务端拿到值后会将最终的响应数据拼接成javascript函数调用的形式,如下:

<script src="http://localhost:9100/jsonps?callback=getUsers"></script>

通过<script>标签引用上面的即可地址,同时传递了callback参数,当请求到达服务端后会拿到callback参数对应的getUsers值,与真正的数据做拼接,如下:

getUsers([{"id":1,"name":"张三"},{"id":2,"name":"李四"},{"id":3,"name":"王五"}]);

上面将是服务端响应的最终结果。这就是javascript函数的调用,我们只要保证前端页面中有getUsers函数即可,它会自动的执行该函数。

以上就是JSONP实现的基本原理。

思考:我们的服务端又该实现呢?直接在对应的接口中进行修改吗?如果直接修改接口,那么当我又希望返回的是数据又该如何,重新再来一个接口吗?

接下来我们通过HttpMessageConverter和ResponseBodyAdvice来实现即支持原始数据又支持JSONP格式的数据响应。

2. 实战案例

2.1 Rest接口定义

@RestController
@RequestMapping(("/jsonps"))
public class JsonpController {
  
  static List<User> DATAS = List.of(new User(1L, "张三"), new User(2L, "李四"), new User(3L, "王五")) ;
  
  @GetMapping("")
  public List<User> queryUsers() {
    return DATAS ;
  }
}

接口非常简单直接返回List集合。

2.2 自定义JSON包装器

public class JsonpMappingJacksonValue extends MappingJacksonValue {


  private String jsonpFunction ;
  
  public JsonpMappingJacksonValue(Object value) {
    super(value);
  }
  // getters, setters
}

该类继承了MappingJacksonValue,同时增加了jsonpFunction的属性,后面会根据该属性是否有值对结果进行处理,如果没有值则原始返回。而MappingJacksonValue类的作用就是一个POJO序列化到JSON时提供额外的序列号指令。

SpringBoot默认响应JSON数据是通过MappingJackson2HttpMessageConverter类,在该类中的writeInternal方法中会判断当前输出的值是否是MappingJacksonValue,如果是最终也会获取其中的Value进行输出客户端的。

2.3 自定义ResponseBodyAdvice

@ControllerAdvice
public class JsonpControllerAdvice implements ResponseBodyAdvice<Object> {


  // 参数值必须满足该正则
  private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
  // 参数名称默认callback,你也可以通过配置方式设置
  private String jsonpQueryParamName = "callback" ;
  
  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    // 只要转换器是jackson(json数据输出)
    // 当然你也可以自定义实现,比如:方法上有具体的某个注解等
    return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
  }
  @Override
  public Object beforeBodyWrite(
      Object body, MethodParameter returnType, 
      MediaType selectedContentType,
      Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
      ServerHttpResponse response) {
    // 创建MappingJacksonValue对象(包装原始的数据)
    JsonpMappingJacksonValue container = this.getOrCreateContainer(body) ;
    // 取得请求的callback参数值
    HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest() ;
    String value = servletRequest.getParameter(jsonpQueryParamName) ;
    // 如果不存在直接返回,不做任何处理
    if (value != null) {
      // 不满足条件也直接返回
      if (!CALLBACK_PARAM_PATTERN.matcher(value).matches()) {
        return container ;
      }
      // 设置响应头为:application/javascript;charset=utf-8
      MediaType contentTypeToUse = new MediaType("application", "javascript", StandardCharsets.UTF_8) ;
      response.getHeaders().setContentType(contentTypeToUse) ;
      // 设置jsonp函数名,后面就会根据该值判断是否要进行处理
      container.setJsonpFunction(value) ;
    }
    return container ;
  }
  // ...
}

自定义ResponseBodyAdvice的作用是将返回客户端的数据包装为MappingJacksonValue对象,然后设置jsonp会调用函数名。

接下来就是最重要的,如何在写入客户端时,将数据改造成JSONP所需要的格式。

2.4 重写HttpMessageConverter

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
  MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter() {
    protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
      // 我们上面设置的值在这里用上了,关键就在该值是否有
      // 只有有值的情况下我们才会进行JSONP的处理
      String jsonpFunction =
          (object instanceof JsonpMappingJacksonValue ? ((JsonpMappingJacksonValue) object).getJsonpFunction() : null);
      if (jsonpFunction != null) {
        generator.writeRaw("/**/");
        generator.writeRaw(jsonpFunction + "(");
      }
    }
    protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {
      String jsonpFunction =
          (object instanceof JsonpMappingJacksonValue ? ((JsonpMappingJacksonValue) object).getJsonpFunction() : null);
      if (jsonpFunction != null) {
        generator.writeRaw(");") ;
      }
    }
  } ;
  return converter ;
}

在这里我们自定义了MappingJackson2HttpMessageConverter 的writePrefix和writeSuffix方法,这两个方法都进行判断,如果期望输出的是JSONP格式才会进行数据处理。

到此就完成了所有处理过程,每一步你都懂吗?

说明:本篇文章不是教你实现JSONP这个技术并使用它,JSONP本就是用来解决跨域的问题,我用CORS技术不比它简单,安全。这里只是借用这个JSONP来检验你对其它知识的掌握程度。

验证上面的代码

不使用callback参数请求

图片图片

使用callback参数请求

图片图片

成功,当你的页面中有getUsers方法时,会自动调用getUsers方法。

通过HTML页面进行测试

<html>
  <head>
    <script>
      function getUsers(users) {
        alert(JSON.stringify(users))
      }
    </script>
    <script src="http://localhost:9100/jsonps?callback=getUsers"></script>
  </head>
</html>

访问上面的页面,输出结果:

图片 图片

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2024-11-01 08:34:18

Spring配置@Bean

2015-09-15 09:50:12

2021-10-21 08:13:11

Springboot

2020-11-09 07:25:20

函数 JavaScript数据

2023-03-23 08:11:59

2015-09-29 09:24:22

Node.js面试题

2020-12-10 11:00:37

JavaJVM命令

2018-02-08 10:47:19

存储技术列存储

2022-02-18 07:32:13

Linux项目代码

2023-10-24 11:44:21

2018-02-02 16:41:01

程序员编程Web

2020-05-06 14:54:59

技术人工智能大数据

2024-04-12 09:01:08

2019-09-29 15:30:58

JavaScript框架V8

2016-12-26 16:34:41

技术

2010-08-04 15:01:00

2019-08-07 15:42:14

区块链区块链技术开发言语

2015-08-17 15:12:56

新技术语言框架

2022-07-24 09:56:40

大数据技术

2020-02-03 13:55:49

技术研发指标
点赞
收藏

51CTO技术栈公众号