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