SpringBoot原理学习之web mvc rest2-----扩展rest内容协商

9 篇文章 1 订阅
9 篇文章 0 订阅

这一章节我们会写一个PropertiesHttpMessageConverter,这个Converter主要是可以接受Properties形式的请求参数,并以Properties的形式在返回给客户端。什么意思呢?一般我们请求体是这样写的:

{

   name:1,

   id:1

}

现在我们写的Converter要求按照如下形式传递也可以被解析:

name:1

id:1

1.我们先写一个SpringBoot的引导类

@SpringBootApplication(scanBasePackages = {
        "com.imooc.web.controller",
        "com.imooc.web.config"
})
public class SpringBootRestBootstrap {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRestBootstrap.class, args);
    }
}

这个引导类没什么特殊的,不赘述

2.Controller

@RestController
public class PropertiesRestController {

    @PostMapping(value = "/add/props",
            consumes = "text/properties;charset=UTF-8" // Content-Type 过滤媒体类型
    )
    public Properties addProperties(@RequestBody Properties properties) {
        return properties;
    }

}

这个controller只能够处理contentType是text/properties的请求,Controller接受的是一个Properties类型的参数。

3.关键!PropertiesHttpMessageConverter

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

    public PropertiesHttpMessageConverter() {
        // 设置支持的 MediaType
        super(new MediaType("text", "properties"));
    }

    @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");
    }

    @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);
    }
}

主要是把Http请求读取进来并转换成Properties对象。

4.生效

@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {


    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 不建议添加到 converters 的末尾
        converters.add(new PropertiesHttpMessageConverter());
        converters.set(0, new PropertiesHttpMessageConverter()); // 添加到集合首位
    }
}

把这个converter添加进去,然后还要把它设置到首位,否则的话Properties会被Mapping2Jackson这个converter转换,因为Properties默认可以被当做json格式返回的。这就是我们为什么要把PropertiesHttpMessageConverter给放到第一位的原因!

虽然这样做我们已经添加了我们想要的Converter,但是呢,这样做会有这么几个问题。首先我们的请求参数必须要有@RequestBody才可以生效,有没有别的办法呢?有的,自定义 HandlerMethodArgumentResolver,我们不依赖 @RequestBody , 实现 Properties 格式请求内容,解析为 Properties 对象的方法参数,实现步骤如下:

1.实现HandlerMethodArgumentResolver - PropertiesHandlerMethodArgumentResolver

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);
    }
}

其实resolveAugument的代码看似很多,但是和我们使用了之前的PropertiesHttpMessageConverter来完成操作,所以还好,重点看一下supportsParameter,虽然一行代码搞定了,但是它确实这里实现的关键点,表示了我们的请求参数必须是Properties类型,这个是我们可以让Controller拿掉@RequestBody注解的关键所在!

2.RequestMappingHandlerAdapter#setArgumentResolvers

@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);

        
    }


    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
        // 添加自定义 HandlerMethodArgumentResolver,优先级低于内建 HandlerMethodArgumentResolver
//        if (resolvers.isEmpty()) {
//            resolvers.add(new PropertiesHandlerMethodArgumentResolver());
//        } else {
//            resolvers.set(0, new PropertiesHandlerMethodArgumentResolver());
//        }

    }


}

大家注意看一下addArgumentResolvers,和上面的messageConverter的添加方式很像,但是很可惜,即使我们这样添加了,而且使用了resolvers.set(0, new PropertiesHandlerMethodArgumentResolver());也不能让我们的PropertiesHandlerMethodArgumentResolver优先级很高,后台会采用MapMethodProcessor作为内置的处理器来处理我们的输入参数,然后报错,setCustomAugumentResolver即使设置到第一位,也不行,原因如下,看看RequestMappingHandlerAdapter的代码:

/**

 * Provide resolvers for custom argument types. Custom resolvers are ordered

 * after built-in ones. To override the built-in support for argument

 * resolution use {@link #setArgumentResolvers} instead.

 */

public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {

this.customArgumentResolvers = argumentResolvers;

}

英文说的很明显了,内置的优先,有办法解决这个问题吗?有,看下面

/**

 * Configure the complete list of supported argument types thus overriding

 * the resolvers that would otherwise be configured by default.

 */

public void setArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {

if (argumentResolvers == null) {

this.argumentResolvers = null;

}

else {

this.argumentResolvers = new HandlerMethodArgumentResolverComposite();

this.argumentResolvers.addResolvers(argumentResolvers);

}

}

RequestMappingHandlerAdapter会由SpringBoot自动装配进来,我们可以把这个Adpater引入进来,直接进行操作,所以看到了在上面的init方法里面,我们把自定义的resolver放到了第一个里面~

这样我们的@RequestBody可以和我们讲再见了,那么有没有办法不依赖 @ResponseBody ,实现 Properties 类型方法返回值,转化为 Properties 格式内容响应内容,有的,其实很类似,不过我们还是来实现一下:

1.实现HandlerMethodReturnValueHandler - PropertiesHandlerMethodReturnValueHandler

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);
    }
}

2.RequestMappingHandlerAdapter#setReturnValueHandlers

@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());
//        }

    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值