深入Spring:自定义ResponseBody

作者: wcong | 来源:发表于2016-05-10 11:37 被阅读8907次

    前言

    上一篇文章介绍了SpringMvcRequestMappingHandlerMapping,自定义了ControllerRequestMapping
    这里再介绍一下HandlerAdapterHttpMessageConverter,并通过自定义注解来实现RequestBodyResponseBodyHttpMessageConverter最常见的应用就是json的decode和encode。

    RequestBody和ResponseBody

    上一篇文章介绍了RequestMappingHandlerMappingDispatcherServlet的作用。
    RequestMappingHandlerMapping扫描了RequestMapping注释的HttpRequest对应的处理方法,并通过实现HandlerMapping的接口代理对应的方法。
    HandlerAdapter则是封装了HandlerMapping的方法,并围绕HandlerMapping实现一些嵌入操作。
    RequestMappingHandlerAdapter是其中一个典型的例子,这个类包含HandlerMethodArgumentResolverHandlerMethodReturnValueHandler的一些实现类来处理RequestMapping的参数和返回值。

        private HandlerMethodArgumentResolverComposite argumentResolvers;
        private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    

    RequestResponseBodyMethodProcessorHandlerAdapter的内部一个重要的类,这个类同时实现了HandlerMethodArgumentResolverHandlerMethodReturnValueHandler
    其中HandlerMethodArgumentResolver接口有两个方法。

    public interface HandlerMethodArgumentResolver {
        boolean supportsParameter(MethodParameter parameter);
        Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
    }
    

    HandlerMethodReturnValueHandler接口同样也有两个方法。

    public interface HandlerMethodReturnValueHandler {
        boolean supportsReturnType(MethodParameter returnType);
        void handleReturnValue(Object returnValue, MethodParameter returnType,
                ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
    }
    

    所以从RequestResponseBodyMethodProcessor实现的方法,就可以看出来这个类,会处理被@RequestBody注解的参数,和@ResponseBody注解的返回值。

        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(RequestBody.class);
        }
        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
                    returnType.getMethodAnnotation(ResponseBody.class) != null);
        }
    

    接下来就介绍一下自定义ResponseBody和RequestBody的使用方法。

    自定义ResponseBody和RequestBody

    1. 自定义注解,因为都是附加的注解,就不需要再加上@Component的注解了。
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyResponseBody {
    }
    
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyRequestBody {
    }
    
    1. 定义数据结构,简便起见,这里只decode和encode特定的类。RequestData@MyRequestBody修饰的类,ResponseData@MyResponseBody修饰的类。
        public static class RequestData {
            private Map<String, String> data;
            public Map<String, String> getData() {
                return data;
            }
            public void setData(Map<String, String> data) {
                this.data = data;
            }
            public String toString() {
                return "{\"data\":" + data + "}";
            }
        }
    
       public static class ResponseData {
           private Map<String, String> data;
           public Map<String, String> getData() {
               return data;
           }
           public void setData(Map<String, String> data) {
               this.data = data;
           }
       }
    
    1. 定义controller
        @Controller
        public static class MyController {
            @RequestMapping
            @MyResponseBody
            public ResponseData index(@MyRequestBody RequestData requestData) {
                System.out.println(requestData);
                ResponseData responseData = new ResponseData();
                responseData.setData(requestData.getData());
                return responseData;
            }
        }
    
    1. 注入HandlerAdapter,并加入了两个MyResolver,只不过一个是setCustomArgumentResolvers,另一个是setCustomReturnValueHandlers
      MyResolver都加入了DataMessageConvert,这个实现了HttpMessageConverter,稍后会介绍到。
        @Configuration
        public static class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
            @Bean
            public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
                RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
                List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
                converters.add(new DataMessageConvert());
                List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
                argumentResolvers.add(new MyResolver(converters));
                requestMappingHandlerAdapter.setCustomArgumentResolvers(argumentResolvers);
                List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
                returnValueHandlers.add(new MyResolver(converters));
                requestMappingHandlerAdapter.setCustomReturnValueHandlers(returnValueHandlers);
                return requestMappingHandlerAdapter;
            }
        }
    
    1. MyResolver继承了AbstractMessageConverterMethodProcessor,并自定义了supportsParametersupportsReturnType来加载自定义的注解。resolveArgumenthandleReturnValue是沿用RequestResponseBodyMethodProcessor的方法,来调用HttpMessageConverter的处理方法。
        public static class MyResolver extends AbstractMessageConverterMethodProcessor {
            public MyResolver(List<HttpMessageConverter<?>> converters) {
                super(converters);
            }
            public boolean supportsParameter(MethodParameter parameter) {
                return parameter.hasParameterAnnotation(MyRequestBody.class);
            }
            public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
                Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
                String name = Conventions.getVariableNameForParameter(parameter);
                WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
                if (arg != null) {
                    validateIfApplicable(binder, parameter);
                    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                    }
                }
                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
                return arg;
            }
            public boolean supportsReturnType(MethodParameter returnType) {
                return returnType.getMethodAnnotation(MyResponseBody.class) != null;
            }
            public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
                mavContainer.setRequestHandled(true);
                writeWithMessageConverters(returnValue, returnType, webRequest);
            }
        }
    
    1. HttpMessageConverter是处理RequestMapping的注释的方法的参数和返回值的接口类。自定义HttpMessageConverter,继承了AbstractGenericHttpMessageConverter来实现一些公用的方法。
      实现了canRead方法,只解码RequestData这个类,同样实现了canWrite了方法,只编码ResponseData这个类。
      简便起见这里只编码和解码Map<String, String>,方法也很简单,key和value直接用','链接,不同的entry之间用';'连接。
        public static class DataMessageConvert extends AbstractGenericHttpMessageConverter<Object> {
            @Override
            public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
                return ((Class) type).isAssignableFrom(RequestData.class);
            }
            @Override
            public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
                return ((Class) type).isAssignableFrom(ResponseData.class);
            }
            public List<MediaType> getSupportedMediaTypes() {
                return Collections.singletonList(MediaType.ALL);
            }
            protected boolean supports(Class<?> clazz) {
                return clazz.isAssignableFrom(Map.class);
            }
            public Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
                return readMap(inputMessage);
            }
            private Object readMap(HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
                Charset cs = Charset.forName("UTF-8");
                StringBuilder stringBuilder = new StringBuilder();
                InputStream inputStream = inputMessage.getBody();
                byte[] b = new byte[1024];
                int length;
                while ((length = inputStream.read(b)) != -1) {
                    ByteBuffer byteBuffer = ByteBuffer.allocate(length);
                    byteBuffer.put(b, 0, length);
                    byteBuffer.flip();
                    stringBuilder.append(cs.decode(byteBuffer).array());
                }
                String[] list = stringBuilder.toString().split(";");
                Map<String, String> map = new HashMap<String, String>(list.length);
                for (String entry : list) {
                    String[] keyValue = entry.split(",");
                    map.put(keyValue[0], keyValue[1]);
                }
                RequestData requestData = new RequestData();
                requestData.setData(map);
                return requestData;
            }
            public void writeInternal(Object o, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                StringBuilder stringBuilder = new StringBuilder();
                Map<String, String> map = ((ResponseData) o).getData();
                for (Map.Entry<String, String> entry : map.entrySet()) {
                    stringBuilder.append(entry.getKey()).append(",").append(entry.getValue()).append(";");
                }
                stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                outputMessage.getBody().write(stringBuilder.toString().getBytes());
            }
            public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
                return readMap(inputMessage);
            }
        }
    
    1. 程序入口,跟上一篇文章类似。
        @Configuration
        public class CustomizeResponseBodyTest {
            public static void main(String[] args) throws ServletException, IOException {
                MockServletContext mockServletContext = new MockServletContext();
                MockServletConfig mockServletConfig = new MockServletConfig(mockServletContext);
                AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
                annotationConfigWebApplicationContext.setServletConfig(mockServletConfig);
                annotationConfigWebApplicationContext.register(CustomizeResponseBodyTest.class);
                DispatcherServlet dispatcherServlet = new DispatcherServlet(annotationConfigWebApplicationContext);
                dispatcherServlet.init(mockServletConfig);
                MockHttpServletResponse response = new MockHttpServletResponse();
                MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
                request.addHeader("Accept", "application/json");
                request.setContent(("result,hello world;date," + Calendar.getInstance().getTimeInMillis()).getBytes());
                dispatcherServlet.service(request, response);
                System.out.println(new String(response.getContentAsByteArray()));
            }
            ...
        }
    

    结语

    这里主要介绍了HandlerAdapterHttpMessageConverterHandlerAdapter封装了HandlerMapping的方法,而HttpMessageConverter则是转换Request和Response的内容的接口,比如json的encode和decode。接下来会介绍更多关于Mvc的内容。

    相关文章

      网友评论

        本文标题:深入Spring:自定义ResponseBody

        本文链接:https://www.haomeiwen.com/subject/ojcyrttx.html