Spring MVC 4.1 使用ResponseBodyAdvice支持jsonp
使用ResponseBodyAdvice支持jsonp
ResponseBodyAdvice是一个接口,接口描述,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package
org.springframework.web.servlet.mvc.method.annotation;
/**
* Allows customizing the response after the execution of an {@code @ResponseBody}
* or an {@code ResponseEntity} controller method but before the body is written
* with an {@code HttpMessageConverter}.
*
* <p>Implementations may be may be registered directly with
* {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver}
* or more likely annotated with {@code @ControllerAdvice} in which case they
* will be auto-detected by both.
*
* @author Rossen Stoyanchev
* @since 4.1
*/
public
interface
ResponseBodyAdvice<T> {
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked, {@code false} otherwise
*/
boolean
supports(MethodParameter returnType, Class<?
extends
HttpMessageConverter<?>> converterType);
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified, possibly new instance
*/
T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,
Class<?
extends
HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
|
作用:
Allows customizing the response after the execution of an {@code @ResponseBody} or an {@code ResponseEntity} controller method but before the body is written
with an {@code HttpMessageConverter}.
其中一个方法就是 beforeBodyWrite 在使用相应的HttpMessageConvert 进行write之前会被调用,就是一个切面方法。
和jsonp有关的实现类是AbstractJsonpResponseBodyAdvice,如下是 beforeBodyWrite 方法的实现,
1
2
3
4
5
6
7
8
9
|
@Override
public
final
Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType contentType, Class<?
extends
HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
MappingJacksonValue container = getOrCreateContainer(body);
beforeBodyWriteInternal(container, contentType, returnType, request, response);
return
container;
}
|
位于AbstractJsonpResponseBodyAdvice的父类中,而beforeBodyWriteInternal是在AbstractJsonpResponseBodyAdvice中实现的 ,如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Override
protected
void
beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
for
(String name :
this
.jsonpQueryParamNames) {
String value = servletRequest.getParameter(name);
if
(value !=
null
) {
MediaType contentTypeToUse = getContentType(contentType, request, response);
response.getHeaders().setContentType(contentTypeToUse);
bodyContainer.setJsonpFunction(value);
return
;
}
}
}
|
就是根据callback 请求参数或配置的其他参数来确定返回jsonp协议的数据。
如何实现jsonp?
首先继承AbstractJsonpResponseBodyAdvice ,如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
com.usoft.web.controller.jsonp;
import
org.springframework.web.bind.annotation.ControllerAdvice;
import
org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
/**
*
*/
@ControllerAdvice
(basePackages =
"com.usoft.web.controller.jsonp"
)
public
class
JsonpAdvice
extends
AbstractJsonpResponseBodyAdvice {
public
JsonpAdvice() {
super
(
"callback"
,
"jsonp"
);
}
}
|
super("callback", "jsonp");的意思就是当请求参数中包含callback 或 jsonp参数时,就会返回jsonp协议的数据。其value就作为回调函数的名称。
这里必须使用@ControllerAdvice注解标注该类,并且配置对哪些Controller起作用。关于注解@ControllerAdvice 的作用这里不做描述。
Controller实现jsonp,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package
com.usoft.web.controller.jsonp;
import
org.springframework.stereotype.Controller;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.ResponseBody;
import
com.usoft.web.controller.JsonMapper;
import
com.usoft.web.controller.Person;
/**
* jsonp
*/
@Controller
public
class
JsonpController {
/**
* callback({"id":1,"age":12,"name":"lyx"})
*
* @param args
*/
public
static
void
main(String args[]) {
Person person =
new
Person(
1
,
"lyx"
,
12
);
System.out.println(JsonMapper.nonNullMapper().toJsonP(
"callback"
,
person));
}
@RequestMapping
(
"/jsonp1"
)
public
Person jsonp1() {
return
new
Person(
1
,
"lyx"
,
12
);
}
@RequestMapping
(
"/jsonp2"
)
@ResponseBody
public
Person jsonp2() {
return
new
Person(
1
,
"lyx"
,
12
);
}
@RequestMapping
(
"/jsonp3"
)
@ResponseBody
public
String jsonp3() {
return
JsonMapper.nonNullMapper().toJsonP(
"callback"
,
new
Person(
1
,
"lyx"
,
12
));
}
}
|
jsonp2 方法就是 一个jsonp协议的调用。http://localhost:8081/jsonp2?callback=test可以直接调用这个方法,并且返回jsonp协议的数据。
通过debug代码,我们来看一下他是怎么返回jsonp协议的数据的。
正因为我们前面在 该Controller 上配置了 JsonpAdvice 的 ControllerAdvice,在调用 MappingJackson2HttpMessageConverter的write()方法往回写数据的时候,首先会调用
beforeBodyWrite,具体的代码如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Override
protected
void
beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
for
(String name :
this
.jsonpQueryParamNames) {
String value = servletRequest.getParameter(name);
if
(value !=
null
) {
MediaType contentTypeToUse = getContentType(contentType, request, response);
response.getHeaders().setContentType(contentTypeToUse);
bodyContainer.setJsonpFunction(value);
return
;
}
}
}
|
当请求参数中含有配置的相应的回调参数时,就会bodyContainer.setJsonpFunction(value);这就标志着 返回的数据时jsonp格式的数据。
然后接下来就到了 MappingJackson2HttpMessageConverter 的write()方法真正写数据的时候了。看他是怎么写数据的,相关的代码如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Override
protected
void
writeInternal(Object object, HttpOutputMessage outputMessage)
throws
IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator generator =
this
.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try
{
writePrefix(generator, object);
Class<?> serializationView =
null
;
Object value = object;
if
(value
instanceof
MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
}
if
(serializationView !=
null
) {
this
.objectMapper.writerWithView(serializationView).writeValue(generator, value);
}
else
{
this
.objectMapper.writeValue(generator, value);
}
writeSuffix(generator, object);
generator.flush();
}
catch
(JsonProcessingException ex) {
throw
new
HttpMessageNotWritableException(
"Could not write content: "
+ ex.getMessage(), ex);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
|
@Override
protected
void
writePrefix(JsonGenerator generator, Object object)
throws
IOException {
if
(
this
.jsonPrefix !=
null
) {
generator.writeRaw(
this
.jsonPrefix);
}
String jsonpFunction =
(object
instanceof
MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() :
null
);
if
(jsonpFunction !=
null
) {
generator.writeRaw(jsonpFunction +
"("
);
}
}
|
1
2
3
4
5
6
7
8
|
@Override
protected
void
writeSuffix(JsonGenerator generator, Object object)
throws
IOException {
String jsonpFunction =
(object
instanceof
MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() :
null
);
if
(jsonpFunction !=
null
) {
generator.writeRaw(
");"
);
}
}
|
代码非常清晰。看我们jsonp调用的结果。
1
|
http:
//localhost:8081/jsonp2?callback=test
|
响应消息如下,
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/javascript
Transfer-Encoding: chunked
Date: Sun, 19 Jul 2015 13:01:02 GMT
test({"id":1,"age":12,"name":"lyx"});
=================END=================
http://my.oschina.net/xinxingegeya/blog/480510?fromerr=yYIwo0JR