1. 程式人生 > >SpringMVC 4.1使用ResponseBodyAdvice支援jsonp

SpringMVC 4.1使用ResponseBodyAdvice支援jsonp

ResponseBodyAdvice是一個介面,介面描述,

[java] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. package org.springframework.web.servlet.mvc.method.annotation;  
  2. /** 
  3.  * Allows customizing the response after the execution of an {@code @ResponseBody} 
  4.  * or an {@code ResponseEntity} controller method but before the body is written 
  5.  * with an {@code HttpMessageConverter}.
     
  6.  * 
  7.  * <p>Implementations may be may be registered directly with 
  8.  * {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver} 
  9.  * or more likely annotated with {@code @ControllerAdvice} in which case they 
  10.  * will be auto-detected by both. 
  11.  * 
  12.  * @author Rossen Stoyanchev
     
  13.  * @since 4.1 
  14.  */
  15. publicinterface ResponseBodyAdvice<T> {  
  16.    /** 
  17.     * Whether this component supports the given controller method return type 
  18.     * and the selected {@code HttpMessageConverter} type. 
  19.     * @param returnType the return type 
  20.     * @param converterType the selected converter type
     
  21.     * @return {@code true} if {@link #beforeBodyWrite} should be invoked, {@code false} otherwise 
  22.     */
  23.    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);  
  24.    /** 
  25.     * Invoked after an {@code HttpMessageConverter} is selected and just before 
  26.     * its write method is invoked. 
  27.     * @param body the body to be written 
  28.     * @param returnType the return type of the controller method 
  29.     * @param selectedContentType the content type selected through content negotiation 
  30.     * @param selectedConverterType the converter type selected to write to the response 
  31.     * @param request the current request 
  32.     * @param response the current response 
  33.     * @return the body that was passed in or a modified, possibly new instance 
  34.     */
  35.    T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,  
  36.          Class<? extends HttpMessageConverter<?>> selectedConverterType,  
  37.          ServerHttpRequest request, ServerHttpResponse response);  
  38. }  
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?在CODE上檢視程式碼片派生到我的程式碼片
  1. @Override
  2. publicfinal Object beforeBodyWrite(Object body, MethodParameter returnType,  
  3.       MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType,  
  4.       ServerHttpRequest request, ServerHttpResponse response) {  
  5.    MappingJacksonValue container = getOrCreateContainer(body);  
  6.    beforeBodyWriteInternal(container, contentType, returnType, request, response);  
  7.    return container;  
  8. }  
@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?在CODE上檢視程式碼片派生到我的程式碼片
  1. @Override
  2. protectedvoid beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,  
  3.       MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {  
  4.    HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();  
  5.    for (String name : this.jsonpQueryParamNames) {  
  6.       String value = servletRequest.getParameter(name);  
  7.       if (value != null) {  
  8.          MediaType contentTypeToUse = getContentType(contentType, request, response);  
  9.          response.getHeaders().setContentType(contentTypeToUse);  
  10.          bodyContainer.setJsonpFunction(value);  
  11.          return;  
  12.       }  
  13.    }  
  14. }  
@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?在CODE上檢視程式碼片派生到我的程式碼片
  1. package com.usoft.web.controller.jsonp;  
  2. import org.springframework.web.bind.annotation.ControllerAdvice;  
  3. import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;  
  4. /** 
  5.  *  
  6.  */
  7. @ControllerAdvice(basePackages = "com.usoft.web.controller.jsonp")  
  8. publicclass JsonpAdvice extends AbstractJsonpResponseBodyAdvice {  
  9.     public JsonpAdvice() {  
  10.         super("callback""jsonp");  
  11.     }  
  12. }  
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?在CODE上檢視程式碼片派生到我的程式碼片
  1. package com.usoft.web.controller.jsonp;  
  2. import org.springframework.stereotype.Controller;  
  3. import org.springframework.web.bind.annotation.RequestMapping;  
  4. import org.springframework.web.bind.annotation.ResponseBody;  
  5. import com.usoft.web.controller.JsonMapper;  
  6. import com.usoft.web.controller.Person;  
  7. /** 
  8.  * jsonp 
  9.  */
  10. @Controller
  11. publicclass JsonpController {  
  12.     /** 
  13.      * callback({"id":1,"age":12,"name":"lyx"}) 
  14.      *  
  15.      * @param args 
  16.      */
  17.     publicstaticvoid main(String args[]) {  
  18.         Person person = new Person(1"lyx"12);  
  19.         System.out.println(JsonMapper.nonNullMapper().toJsonP("callback",  
  20.             person));  
  21.     }  
  22.     @RequestMapping("/jsonp1")  
  23.     public Person jsonp1() {  
  24.         returnnew Person(1"lyx"12);  
  25.     }  
  26.     @RequestMapping("/jsonp2")  
  27.     @ResponseBody
  28.     public Person jsonp2() {  
  29.         returnnew Person(1"lyx"12);  
  30.     }  
  31.     @RequestMapping("/jsonp3")  
  32.     @ResponseBody
  33.     public String jsonp3() {  
  34.         return JsonMapper.nonNullMapper().toJsonP("callback",  
  35.             new Person(1"lyx"12));  
  36.     }  
  37. }  
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?在CODE上檢視程式碼片派生到我的程式碼片
  1. @Override
  2. protectedvoid beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,  
  3.       MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {  
  4.    HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();  
  5.    for (String name : this.jsonpQueryParamNames) {  
  6.       String value = servletRequest.getParameter(name);  
  7.       if (value != null) {  
  8.          MediaType contentTypeToUse = getContentType(contentType, request, response);  
  9.          response.getHeaders().setContentType(contentTypeToUse);  
  10.          bodyContainer.setJsonpFunction(value);  
  11.          return;  
  12.       }  
  13.    }  
  14. }  
@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?在CODE上檢視程式碼片派生到我的程式碼片
  1. @Override
  2. protectedvoid writeInternal(Object object, HttpOutputMessage outputMessage)  
  3.       throws IOException, HttpMessageNotWritableException {  
  4.    JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());  
  5.    JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);  
  6.    try {  
  7.       writePrefix(generator, object);  
  8.       Class<?> serializationView = null;  
  9.       Object value = object;  
  10.       if (value instanceof MappingJacksonValue) {  
  11.          MappingJacksonValue container = (MappingJacksonValue) object;  
  12.          value = container.getValue();  
  13.          serializationView = container.getSerializationView();  
  14.       }  
  15.       if (serializationView != null) {  
  16.          this.objectMapper.writerWithView(serializationView).writeValue(generator, value);  
  17.       }  
  18.       else {  
  19.          this.objectMapper.writeValue(generator, value);  
  20.       }  
  21.       writeSuffix(generator, object);  
  22.       generator.flush();  
  23.    }  
  24.    catch (JsonProcessingException ex) {  
  25.       thrownew HttpMessageNotWritableException("Could not write content: " + ex.getMessage(), ex);  
  26.    }  
  27. }  
  28. @Override
  29. protectedvoid writePrefix(JsonGenerator generator, Object object) throws IOException {  
  30.    if (this.jsonPrefix != null) {  
  31.       generator.writeRaw(this.jsonPrefix);  
  32.    }  
  33.    String jsonpFunction =  
  34.          (object instanceof MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() : null);  
  35.    if (jsonpFunction != null) {  
  36.       generator.writeRaw(jsonpFunction + "(");  
  37.    }  
  38. }  
  39. @Override
  40. protectedvoid writeSuffix(JsonGenerator generator, Object object) throws IOException {  
  41.    String jsonpFunction =  
  42.          (object instanceof MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() : null);  
  43.    if (jsonpFunction != null) {  
  44.       generator.writeRaw(");");  
  45.    }  
  46. }  
@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"});