扩展 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;
    }

}
posted @ 2020-06-08 08:50  樊梨花大大王  阅读(134)  评论(0编辑  收藏  举报