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