jsonp是ajax跨域解决方案的一种办法,就是借助标签<script></script>可以实现不同域之间数据请求的一种方式,类似iframe,不受跨域限制,它请求返回之后,会以一种回调的形式调起挂在window对象上的全局方法callback,这里的callback就是我们在url请求中指定的回调函数,参数就是我们请求服务端包装在callback之内的数据。
jsonp的实现,需要前后端配合,不是说前端可以实现,后端也可以实现,他们需要配合才能实现一个完整的jsonp请求。springboot1.x对jsonp还有一个AbstractJsonpResponseBodyAdvice的抽象类可以去实现jsonp,而无需改动普通的GET请求,但是在springboot2.x版本里面已经没有这个类了,所以又回到了springmvc阶段对jsonp的实现。
springboot对jsonp的实现,最简单的就是需要获取前端请求的callback参数,然后拼接返回结果组成字符串的形式返回。
springboot项目依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
</dependencies>
Java代码部分对jsonp的配合处理:
package com.xxx.springboot.web;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
@RestController
@RequestMapping("/jsonp")
public class JsonpController {
@GetMapping("/test")
public String test(@RequestParam("callback")String callback) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", 200);
map.put("data", Arrays.asList(new String[]{"1","2","3"}));
String result = JSON.toJSONString(map);
return callback+"("+result+")";
}
}
这段代码有三个需要注意的地方:
1、必须要获取callback参数,
2、返回结果要通过callback参数拼接,不是原来的任意对象结构了,变成了字符串形式,
3、如果类上注解是@Controller,那么方法名上需要注解@ResponseBody,或者直接在类上注解@RestController 。
这个实现方式是请求方法带返回参数的形式,还有一种是无返回值(void)的方法形式,这种方式,就需要通过response.getWriter().write()来向请求写出结果。
package com.xxx.springboot.web;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.alibaba.fastjson.JSON;
@RequestMapping("/jsonp2")
@Controller
public class JsonpWriteController {
@GetMapping("/test")
public void test(@RequestParam("callback")String callback,HttpServletResponse response) throws IOException {
Map<String, Object> map = new HashMap<String, Object>();
map.put("code", 200);
map.put("data", Arrays.asList(new String[]{"1","2","3","4"}));
String result = JSON.toJSONString(map);
response.getWriter().write(callback+"("+result+")");
}
}
以上部分只是讲了在服务端端如何通过springboot代码来配合实现jsonp,并不是就解决了问题,它只完成了一半。
下面介绍web端如何实现jsonp的,jsonp原理是利用script脚本没有跨域限制,请求完成之后,会调起本地挂在在window对象上的回调函数,从而实现回显数据或者其他业务。
我这里通过nginx启动一个http服务,然后将一个index.html指定到nginx配置的root路径下,这样nginx下请求地址是http://localhost,而springboot启动请求地址是http://localhost:8080,这样就构成了不同域,ajax请求因为跨域而受限制,可以用来验证jsonp。
原生的jsonp在web页面上的实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jsonp</title>
<style>
#root input{padding:10px;border:1px solid #ddd;color:#fff;border-radius: 3px;background: lightgreen;font-size: 16px;}
#root .box{padding:5px;}
#root textarea{width:790px;height:100px;resize: none;}
</style>
</head>
<body>
<div id="root">
<div class="box">
<input type="button" value="jsonp" onclick="handleclick()"/>
</div>
<div class="box">
<textarea id="output"></textarea>
</div>
</div>
<script>
function showData(data){
var res = JSON.stringify(data)
document.querySelector("#output").textContent = res
}
function handleclick(){
var url = "http://127.0.0.1:8080/jsonp/test?callback=showData&_t=123"
var script = document.createElement("script")
script.setAttribute("src",url)
script.onerror = null
document.getElementsByTagName("head")[0].appendChild(script)
}
</script>
</body>
</html>
现在代码准备好了,启动springboot服务和nginx服务,打开浏览器输入地址:http://localhost,点击jsonp按钮,请求结果反应在下方输入框中。
前面说springboot的两种返回形式有差别,可以改动代码,做验证:
<script>
function showData(data){
var res = JSON.stringify(data)
document.querySelector("#output").textContent = res
}
function handleclick(){
var url = "http://127.0.0.1:8080/jsonp2/test?callback=showData&_t=123"
var script = document.createElement("script")
script.setAttribute("src",url)
script.onerror = null
document.getElementsByTagName("head")[0].appendChild(script)
}
</script>
这次请求,数据体中是["1","2","3","4"],是对应JsonpWriteController类中void方法体通过write方式写出来的。
当然了,前端页面这里的jsonp,是原生的,而jquery对jsonp的实现做了封装,它将其封装到ajax上,只不过需要指定dataType参数为jsonp。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jsonp</title>
<style>
#root input{padding:10px;border:1px solid #ddd;color:#fff;border-radius: 3px;background: lightgreen;font-size: 16px;}
#root .box{padding:5px;}
#root textarea{width:790px;height:100px;resize: none;}
</style>
</head>
<body>
<div id="root">
<div class="box">
<input type="button" value="jsonp" onclick="handleclick()"/>
</div>
<div class="box">
<input type="button" value="jsonp by jquery" onclick="handleclick2()" />
</div>
<div class="box">
<textarea id="output"></textarea>
</div>
</div>
<script src="https://code.jquery.com/jquery-1.12.4.js" ></script>
<script>
function showData(data){
var res = JSON.stringify(data)
document.querySelector("#output").textContent = res
}
function handleclick(){
var url = "http://127.0.0.1:8080/jsonp/test?callback=showData&_t=123"
var script = document.createElement("script")
script.setAttribute("src",url)
script.onerror = null
document.getElementsByTagName("head")[0].appendChild(script)
}
var base_url = "http://localhost:8080/"
function handleclick2(){
$.ajax({
url:base_url+"jsonp2/test",
type:"get",
dataType:"jsonp",
//jsonp:"callback",
success:function(res){
showData(res)
},
error:function(xhr,textStatus,errorThrown){
console.log("error")
}
})
}
</script>
</body>
</html>
实现效果:
这里两者请求不一样,一个带了callback=showData,那个是原生实现的,还有一个是jQuery封装了jsonp的实现,它没有设置callback参数,默认回调函数就是success,我们在success里面调用了showData。
还有一种方式,就是指定jsonp参数,就是callback,同时,还需要指定jsonpCallback就是回调的真实函数,在这里是showData,而不用指定success函数。
function handleclick2(){
$.ajax({
url:base_url+"jsonp2/test",
type:"get",
dataType:"jsonp",
jsonp:"callback",
jsonpCallback:"showData",
//success:function(res){
// showData(res)
//},
error:function(xhr,textStatus,errorThrown){
console.log("error")
}
})
}
jsonp实现跨域的两个缺点:
1、需要服务端配合修改代码,
2、只支持get请求。