SpringMVC 对 JSON-P 的支持

1. 概述

如果你既开发后端也开发前端,那么您就知道浏览器在处理AJAX请求时所具有的同源策略约束,也就是我们常说的跨域问题。什么叫同源呢,如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。否则,都是跨域。

当然,解决这种问题的方法有很多,其中一种(也是较常见的)方法是使用JSON-P,本文讨论Spring 对使用JSON-P数据格式的支持。

2. JSON-P in Action

很重要的一点是:浏览器不会对 <script> 标签施加同源策略,允许跨不同域加载脚本,即我们可以在 <script> 中加载不同源的资源的。JSON-P 技术实际上是利用这一点通过将JSON响应作为 javascript 函数的参数传递。

2.1 准备工作

在我们的示例中,我们使用一个简单的 Company 类:

1
2
3
4
5
6
7
public class Company {

private long id;
private String name;

// standard setters and getters
}

并通过一个 Controller 的方法 返回Company实例的 JSON 数据:

1
2
3
4
5
6
7
8
9
10
@RestController
public class CompanyController {

@RequestMapping(value = "/companyRest",
produces = MediaType.APPLICATION_JSON_VALUE)
public Company getCompanyRest() {
Company company = new Company(1, "Xpto");
return company;
}
}

在客户端,我们使用 jQuery 库来创建和发送AJAX请求:

1
2
3
4
5
6
7
8
$.ajax({
url: 'http://localhost:8080/spring-mvc-java/companyRest',
data: {
format: 'json'
},
type: 'GET',
...
});

如果我们页面是通过如下的 URL 来展示并进行AJAX请求:

1
http://localhost:8080/spring-mvc-java/companyRest

服务器的响应如下:

1
{"id":1,"name":"Xpto"}

由于请求是同源的(相同的协议、域和端口),因此响应不会被阻止,浏览器将允许JSON数据。

2.2 跨源请求

换种方式,如果我们通过如下的 URL 来访问页面呢:

1
http://127.0.0.1:8080/spring-mvc-java/companyRest

注意,我们的页面域名为 127.0.0.1 ,而在 js 代码中,AJAX 请求是请求的目标是 localhost 域,违反了同源策略。因此响应将被浏览器阻止,不会正常的返回想要的数据。

这个时候就需要使用JSON-P技术了,我们可以向请求添加回调参数:

1
http://127.1.1.1:8080/spring-mvc-java/companyRest?callback=getCompanyData

在 JS 中进行 AJAX 时请求添加以下参数:

1
2
3
4
5
6
$.ajax({
...
jsonpCallback:'getCompanyData',
dataType: 'jsonp',
...
});

getCompanyData 是指接收到响应时会调用的函数,同时服务端的响应也进行相应的包装,如下所示:

1
getCompanyData({"id":1,"name":"Xpto"});

根据 JSONP 协议,这个时候浏览器不会阻止它,因为它会将响应视为在客户端和服务器之间协商并达成一致的脚本,因为请求和响应中都匹配 getCompanyData,这样就能解决跨域的问题。

3. 使用 AbstractJsonpResponseBodyAdvice 更改响应

从Spring 4.1开始,我们现在可以访问通过自定义一个 AbstractJsonpResponseBodyAdvice 的实现,来完成根据JSON-P标准的格式化响应。

其实非常的简单,我们自定义一个 AbstractJsonpResponseBodyAdvice 的实现,如下所示:

1
2
3
4
5
6
7
8
@ControllerAdvice
public class JsonpControllerAdvice
extends AbstractJsonpResponseBodyAdvice {

public JsonpControllerAdvice() {
super("callback");
}
}

主要是传递给父类一个类似 密钥 的东西 callback,这个是在 JSON-P 返回时定义的回调方法,注意,这个需要和请求中的 jsonpCallback 参数名保持一致才能实现 JSON-P 的协议。

5. 验证

通过前面讨论的配置,我们可以使我们的REST应用程序响应JSON-P。在下面的示例中,我们将返回公司的数据,因此我们的AJAX请求URL应该是这样的:

1
http://127.0.0.1:8080/spring-mvc-java/companyRest?callback=getCompanyData

进行之前的 JSON-P 配置扣,响应将如下所示:

1
`getCompanyData({"id":1,"name":"Xpto"});`

如上所述,尽管源自不同的域,但此格式的响应不会被阻止。JsonpControllerAdvice 会被应用到所有 @ResponseBodyResponseEntity 注解的方法上。