Spring MVC 4.1 支持jsonpjavascript
ResponseBodyAdvice是一個接口,接口描述,java
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); }
做用:web
Allows customizing the response after the execution of an {@code @ResponseBody} or an {@code ResponseEntity} controller method but before the body is writtenspring
with an {@code HttpMessageConverter}.json
其中一個方法就是 beforeBodyWrite 在使用相應的HttpMessageConvert 進行write以前會被調用,就是一個切面方法。mvc
和jsonp有關的實現類是AbstractJsonpResponseBodyAdvice,以下是 beforeBodyWrite 方法的實現,app
@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中實現的 ,以下,ide
@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?jsonp
首先繼承AbstractJsonpResponseBodyAdvice ,以下,
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,
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,具體的代碼以下,
@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()方法真正寫數據的時候了。看他是怎麼寫數據的,相關的代碼以下,
@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"});
=================END=================