SpringMVC 4.1使用ResponseBodyAdvice支援jsonp
ResponseBodyAdvice是一個介面,介面描述,
[java] view plain copy print?- 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
- */
- publicinterface 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);
- }
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 方法的實現,
[java] view plain copy print?- @Override
- publicfinal 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;
- }
@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中實現的 ,如下,
[java] view plain copy print?- @Override
- protectedvoid 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;
- }
- }
- }
@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 ,如下,
[java] view plain copy print?- 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")
- publicclass JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
- public JsonpAdvice() {
- super("callback", "jsonp");
- }
- }
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,
[java] view plain copy print?- 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
- publicclass JsonpController {
- /**
- * callback({"id":1,"age":12,"name":"lyx"})
- *
- * @param args
- */
- publicstaticvoid main(String args[]) {
- Person person = new Person(1, "lyx", 12);
- System.out.println(JsonMapper.nonNullMapper().toJsonP("callback",
- person));
- }
- @RequestMapping("/jsonp1")
- public Person jsonp1() {
- returnnew Person(1, "lyx", 12);
- }
- @RequestMapping("/jsonp2")
- @ResponseBody
- public Person jsonp2() {
- returnnew Person(1, "lyx", 12);
- }
- @RequestMapping("/jsonp3")
- @ResponseBody
- public String jsonp3() {
- return JsonMapper.nonNullMapper().toJsonP("callback",
- new Person(1, "lyx", 12));
- }
- }
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,具體的程式碼如下,
[java] view plain copy print?- @Override
- protectedvoid 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;
- }
- }
- }
@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()方法真正寫資料的時候了。看他是怎麼寫資料的,相關的程式碼如下,
[java] view plain copy print?- @Override
- protectedvoid 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) {
- thrownew HttpMessageNotWritableException("Could not write content: " + ex.getMessage(), ex);
- }
- }
- @Override
- protectedvoid 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 + "(");
- }
- }
- @Override
- protectedvoid writeSuffix(JsonGenerator generator, Object object) throws IOException {
- String jsonpFunction =
- (object instanceof MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() : null);
- if (jsonpFunction != null) {
- generator.writeRaw(");");
- }
- }
@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);
}
}
@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 + "(");
}
}
@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呼叫的結果。
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"});