• 扩展 REST 内容协商


    扩展 REST 内容协商

    核心组件

    组件名称 实现 说明
    内容协商管理器 ContentNegotiationManager ContentNegotiationStrategy 控制策略
    媒体类型 MediaType HTTP 消息媒体类型,如 text/html
    消费媒体类型 @RequestMapping#consumes 请求头 Content-Type 媒体类型映射
    生产媒体类型 @RequestMapping#produces 响应头 Content-Type 媒体类型映射
    HTTP消息转换器 HttpMessageConverter HTTP 消息转换器,用于反序列化 HTTP 请求或序列化响应
    Web MVC 配置器 WebMvcConfigurer 配置 REST 相关的组件
    处理方法 HandlerMethod @RequestMapping 标注的方法
    处理方法参数解析器 HandlerMethodArgumentResolver 用于 HTTP 请求中解析 HandlerMethod 参数内容
    处理方法返回值解析器 HandlerMethodReturnValueHandler 用于 HandlerMethod 返回值解析为 HTTP 响应内容

    自定义 HttpMessageConverter,用于反序列化 HTTP 请求或序列化响应

    需求

    实现Content-Type 为 text/properties 媒体类型的 HttpMessageConverter

    实现步骤

    • 实现 HttpMessageConverter - PropertiesHttpMessageConverter
    • 配置 PropertiesHttpMessageConverter 到 WebMvcConfigurer#extendMessageConverters

    编码实现

    • 编写properties转换器类PropertiesHttpMessageConverter继承 AbstractGenericHttpMessageConverter,实现writeInternal,readInternal和read方法,以及在构造参数中调用父类构造方法添加支持的MediaType
    // 省略import
    import java.io.*;
    import java.lang.reflect.Type;
    import java.nio.charset.Charset;
    import java.util.Properties;
    public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {
    
        public PropertiesHttpMessageConverter() {
            // 设置支持的 MediaType
            super(new MediaType("text", "properties"));
        }
        // 序列化response
        @Override
        protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            // Properties -> String
            // OutputStream -> Writer
            HttpHeaders httpHeaders = outputMessage.getHeaders();
            MediaType mediaType = httpHeaders.getContentType();
            // 获取字符编码
            Charset charset = mediaType.getCharset();
            // 当 charset 不存在时,使用 UTF-8
            charset = charset == null ? Charset.forName("UTF-8") : charset;
            // 字节输出流
            OutputStream outputStream = outputMessage.getBody();
            // 字符输出流
            Writer writer = new OutputStreamWriter(outputStream, charset);
            // Properties 写入到字符输出流
            properties.store(writer,"From PropertiesHttpMessageConverter");
        }
        // http请求反序列化
        @Override
        protected Properties readInternal(Class<? extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    
            // 字符流 -> 字符编码
            // 从 请求头 Content-Type 解析编码
            HttpHeaders httpHeaders = inputMessage.getHeaders();
            MediaType mediaType = httpHeaders.getContentType();
            // 获取字符编码
            Charset charset = mediaType.getCharset();
            // 当 charset 不存在时,使用 UTF-8
            charset = charset == null ? Charset.forName("UTF-8") : charset;
    
            // 字节流
            InputStream inputStream = inputMessage.getBody();
            InputStreamReader reader = new InputStreamReader(inputStream, charset);
            Properties properties = new Properties();
            // 加载字符流成为 Properties 对象
            properties.load(reader);
            return properties;
        }
    
        @Override
        public Properties read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            return readInternal(null, inputMessage);
        }
    }
    
    • 编写config配置类RestWebMvcConfigurer,实现WebMvcConfigurer接口,重写extendMessageConverters方法,将PropertiesHttpMessageConverter对象添加到集合首位,不然会被默认传出json串
    @Configuration
    public class RestWebMvcConfigurer implements WebMvcConfigurer {
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            // 不建议添加到 converters 的末尾
    //        converters.add(new PropertiesHttpMessageConverter());
            converters.set(0, new PropertiesHttpMessageConverter()); // 添加到集合首位
        }
    }
    
    • 编写Controller方法,接受参数和返回类型都是properties类型
    package com.web.config;
    @RestController
    public class PropertiesRestController {
    
        @PostMapping(value = "/add/props",
                consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
        )
        public Properties addProperties(@RequestBody Properties properties) {
            return properties;
        }
    
    }
    
    • 启动类一定要扫描到config下的包
    @SpringBootApplication(scanBasePackages = {
            "com.web.controller",
            "com.web.config"
    })
    public class SpringBootRestBootstrap {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootRestBootstrap.class, args);
        }
    }
    
    • 使用postman测试
      在Headers中设置Content-Type和Accept为text/properties

    自定义 HandlerMethodArgumentResolver

    需求

    • 不依赖 @RequestBody , 实现 Properties 格式请求内容,解析为 Properties 对象的方法参数
    • 复用 PropertiesHttpMessageConverter

    实现步骤

    • 实现HandlerMethodArgumentResolver - PropertiesHandlerMethodArgumentResolver
    • 配置 PropertiesHandlerMethodArgumentResolver 到 WebMvcConfigurer#addArgumentResolvers,因为优先级顺序,会出现问题
    • RequestMappingHandlerAdapter#setArgumentResolvers

    代码实现

    • 编写PropertiesHandlerMethodArgumentResolver类实现HandlerMethodArgumentResolver接口
    public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return Properties.class.equals(parameter.getParameterType());
        }
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            // 复用 PropertiesHttpMessageConverter,见上面
            PropertiesHttpMessageConverter converter = new PropertiesHttpMessageConverter();
            ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
            // Servlet Request API
            HttpServletRequest request = servletWebRequest.getRequest();
            HttpInputMessage httpInputMessage = new ServletServerHttpRequest(request);
            return converter.read(null, null, httpInputMessage);
        }
    }
    
    • 配置 PropertiesHandlerMethodArgumentResolver 到 WebMvcConfigurer#addArgumentResolvers,此处不能简单的在addArgumentResolvers方法中resolvers参数中添加PropertiesHandlerMethodReturnValueHandler对象,因为添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver,唯有重新设定requestMappingHandlerAdapter对象中的argumentResolvers属性,自定义Handler对象亦如此
    @Configuration
    public class RestWebMvcConfigurer implements WebMvcConfigurer {
        @Autowired
        private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
        @PostConstruct
        public void init() {
            // 获取当前 RequestMappingHandlerAdapter 所有的 Resolver 对象
            List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
            List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>(resolvers.size() + 1);
            // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
            newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
            // 添加 已注册的 Resolver 对象集合
            newResolvers.addAll(resolvers);
            // 重新设置 Resolver 对象集合
            requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
    
            // 获取当前 HandlerMethodReturnValueHandler 所有的 Handler 对象
            List<HandlerMethodReturnValueHandler> handlers = requestMappingHandlerAdapter.getReturnValueHandlers();
            List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(handlers.size() + 1);
            // 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
            newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
            // 添加 已注册的 Handler 对象集合
            newHandlers.addAll(handlers);
            // 重新设置 Handler 对象集合
            requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
        }
         public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
            // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
            // 添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver
    //        if (resolvers.isEmpty()) {
    //            resolvers.add(new PropertiesHandlerMethodArgumentResolver());
    //        } else {
    //            resolvers.set(0, new PropertiesHandlerMethodArgumentResolver());
    //        }
    
        }
    }
    
    • controller类中的handlerMethod方法不再需要添加@RequestBody注解
    @RestController
    public class PropertiesRestController {
    
        @PostMapping(value = "/add/props",
                consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
        )
        public Properties addProperties(
    //            @RequestBody
                Properties properties) {
            return properties;
        }
    }
    

    自定义 HandlerMethodReturnValueHandler

    需求

    • 不依赖 @ResponseBody ,实现 Properties 类型方法返回值,转化为 Properties 格式内容响应内容
    • 复用 PropertiesHttpMessageConverter

    实现步骤

    • 实现HandlerMethodReturnValueHandler - PropertiesHandlerMethodReturnValueHandler
    • 配置 PropertiesHandlerMethodReturnValueHandler 到 WebMvcConfigurer#addReturnValueHandlers,因为优先级顺序,会出现问题
    • RequestMappingHandlerAdapter#setReturnValueHandlers

    代码实现

    • 编写类PropertiesHandlerMethodReturnValueHandler继承HandlerMethodReturnValueHandler接口
    public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            // 判断方法的返回类型,是否与 Properties 类型匹配
            return Properties.class.equals(returnType.getMethod().getReturnType());
        }
        @Override
        public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
            // 强制装换
            Properties properties = (Properties) returnValue;
            // 复用 PropertiesHttpMessageConverter
            PropertiesHttpMessageConverter converter = new PropertiesHttpMessageConverter();
            ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
            // Servlet Request API
            HttpServletRequest request = servletWebRequest.getRequest();
            String contentType = request.getHeader("Content-Type");
            // 获取请求头 Content-Type 中的媒体类型
            MediaType mediaType = MediaType.parseMediaType(contentType);
            // 获取 Servlet Response 对象
            HttpServletResponse response = servletWebRequest.getResponse();
            HttpOutputMessage message = new ServletServerHttpResponse(response);
            // 通过 PropertiesHttpMessageConverter 输出
            converter.write(properties, mediaType, message);
            // 告知 Spring Web MVC 当前请求已经处理完毕
            mavContainer.setRequestHandled(true);
        }
    }
    
    • 配置,同上Resolver
    @Configuration
    public class RestWebMvcConfigurer implements WebMvcConfigurer {
        @Autowired
        private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
        @PostConstruct
        public void init() {
            // 获取当前 RequestMappingHandlerAdapter 所有的 Resolver 对象
            List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
            List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>(resolvers.size() + 1);
            // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
            newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
            // 添加 已注册的 Resolver 对象集合
            newResolvers.addAll(resolvers);
            // 重新设置 Resolver 对象集合
            requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
    
            // 获取当前 HandlerMethodReturnValueHandler 所有的 Handler 对象
            List<HandlerMethodReturnValueHandler> handlers = requestMappingHandlerAdapter.getReturnValueHandlers();
            List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(handlers.size() + 1);
            // 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
            newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
            // 添加 已注册的 Handler 对象集合
            newHandlers.addAll(handlers);
            // 重新设置 Handler 对象集合
            requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
        }
         public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
            // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
            // 添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver
    //        if (resolvers.isEmpty()) {
    //            resolvers.add(new PropertiesHandlerMethodArgumentResolver());
    //        } else {
    //            resolvers.set(0, new PropertiesHandlerMethodArgumentResolver());
    //        }
    
        }
    }
    
    • controller类中可以去除@RestController注解
    //@RestController
    @Controller
    public class PropertiesRestController {
    
        @PostMapping(value = "/add/props",
                consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
        )
        public Properties addProperties(
    //            @RequestBody
                Properties properties) {
            return properties;
        }
    
    }
    
  • 相关阅读:
    建立自己的影响力
    在病房垒代码
    知乎确实不错
    不在乎你用到了什么技术,而在于你提供了哪些有价值的东西
    oschina上有不少好的项目
    我为何坚守Java?
    掌握了学习方法才能学到更多知识
    Jrebel实现Jetty 热部署
    互联网到底能干什么?我们还能干些什么?
    centos 阿里云 安装VNC Viewer
  • 原文地址:https://www.cnblogs.com/fjf3997/p/13063651.html
Copyright © 2020-2023  润新知