SpringBoot2学习笔记(七)——内容协商

内容协商

内容协商机制是指客户端和服务器端就响应的资源内容进行交涉,然后提供给客户端最为适合的资源。内容协商会以响应资源的语言、字符集、编码方式等作为判断的基准。

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

HttpMessageConverter为HTTP消息转换接口,Spring根据不同的媒体类型进行了相应的实现。

自定义HttpMessageConverter

假如现在要实现一个用于处理 Content-Type 为 text/properties 媒体类型的 HttpMessageConverter 实现类 PropertiesHttpMessageConverter,当我们在请求体中传输下面内容时:

name:mrbrid
age:18

我们可以通过继承AbstractGenericHttpMessageConverter的方式来实现HttpMessageConverter接口。

序列化与反序列化过程

//自定义内容类型转换
public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

	//需要为自定的解析器制定解析的MideaType类型
    public PropertiesHttpMessageConverter() {
        super(new MediaType("text", "properties"));
    }

    //序列化过程,返回数据封装
    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        //获取请求头
        HttpHeaders headers = httpOutputMessage.getHeaders();
        //获取contentType
        MediaType contentType = headers.getContentType();
        //获取编码
        Charset charset = null;
        if (contentType != null){
            charset = contentType.getCharset();
        }
        charset = charset == null ? Charset.forName("UTF-8") : charset;

        //获取请求体
        OutputStream outputStream = httpOutputMessage.getBody();
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset);

        properties.store(writer, "Serialized by PropertiesHttpMessageConverter#writeInternal");
    }



    //反序列化过程,请求数据封装
    @Override
    protected Properties readInternal(Class<? extends Properties> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        Properties properties = new Properties();
        //获取请求头
        HttpHeaders headers = httpInputMessage.getHeaders();
        //获取content-type
        MediaType contentType = headers.getContentType();
        //获取编码
        Charset charset = null;
        if (contentType != null){
            charset = contentType.getCharset();
        }
        charset = charset == null ? Charset.forName("utf-8") : charset;
        //获取请求体
        InputStream body = httpInputMessage.getBody();
        InputStreamReader reader = new InputStreamReader(body, charset);

        properties.load(reader);
        return properties;
    }

    @Override
    public Properties read(Type type, Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return readInternal(null, httpInputMessage);
    }
}

在类目录下新建config包,创建WebConfigurer对象,实现WebMvcConfigurer接口。extendMessageConverters方法为WebMvcConfigurer的默认方法,这里我们重写这个方法,用于将PropertiesHttpMessageConverter添加到消息转换器集合中。

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    	//需要将自定义的内容解析器放在首位,否则会优先采用前面的解析器,譬如会返回json类型的数据。
        converters.add(0, new PropertiesHttpMessageConverter());
    }
}

新建Controller类,模拟实验。

    @GetMapping(value = "test", consumes = "text/properties")
    @ResponseBody
    public Properties test(@RequestBody Properties properties){
        return properties;
    }

postman 模拟请求
在这里插入图片描述

name:xuzq11
password:111111

请求返回
在这里插入图片描述

自定义HandlerMethodArgumentResolver

上面这种方式必须依赖于@RequestBody和@ResponseBody注解,除此之外我们还可以通过自定义HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler实现类的方式来处理内容协商。

HandlerMethodArgumentResolver俗称方法参数解析器,用于解析由@RequestMapping注解(或其派生的注解)所标注的方法的参数。这里我们开始通过实现HandlerMethodArgumentResolver的方式来将HTTP请求体的内容自动解析为Properties对象。

  1. 新建PropertiesHandlerMethodReturnValueHandler实现HandlerMethodArgumentResolver接口
//@RequestMapping标注的方法的参数类型转换
public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    //指定支持解析的参数类型
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return Properties.class.equals(methodParameter.getParameterType());
    }


	//请求参数封装
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        ServletWebRequest webRequest = (ServletWebRequest) nativeWebRequest;
        HttpServletRequest request = webRequest.getRequest();

        String contentType = request.getContentType();
        MediaType mediaType = MediaType.parseMediaType(contentType);
        Charset charset = mediaType.getCharset() == null ? Charset.forName("utf-8") : mediaType.getCharset();

        InputStream inputStream = request.getInputStream();
        InputStreamReader reader = new InputStreamReader(inputStream, charset);

        Properties properties = new Properties();
        properties.load(reader);
        return properties;
    }
}

接着,我们还需将PropertiesHandlerMethodArgumentResolver添加到Spring自带的HandlerMethodArgumentResolver实现类集合中。值得注意的是,我们不能在配置类WebMvcConfigurer中通过重写addArgumentResolvers的方式来添加。通过这个方法来添加的方法参数解析器不会覆盖Spring内置的方法参数解析器,如果需要这么做的话,可以直接通过修改RequestMappingHandlerAdapter来实现。

@Configuration
public class WebConfigurer implements WebMvcConfigurer {


    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        // 获取当前 RequestMappingHandlerAdapter 所有的 ArgumentResolver对象
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        List<HandlerMethodArgumentResolver> newArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1);
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合第一个位置
        newArgumentResolvers.add(0, new PropertiesHandlerMethodArgumentResolver());
        // 将原 ArgumentResolver 添加到集合中
        newArgumentResolvers.addAll(argumentResolvers);
        // 重新设置 ArgumentResolver对象集合
        requestMappingHandlerAdapter.setArgumentResolvers(newArgumentResolvers);
    }
}

之所以要将PropertiesHandlerMethodArgumentResolver添加到第一个位置是因为Properties本质也是一个Map对象,而Spring内置的MapMethodProcessor就是用于处理Map参数类型的,如果不将PropertiesHandlerMethodArgumentResolver优先级提高,那么Properties类型参数会被MapMethodProcessor解析,从而出错。

在这里插入图片描述

自定义HandlerMethodReturnValueHandler

HandlerMethodArgumentResolver俗称方法返回值解析器,用于解析由@RequestMapping注解(或其派生的注解)所标注的方法的返回值。这里我们开始通过实现HandlerMethodReturnValueHandler的方式来自定义一个用于处理返回值类型为Properties类型的解析器。

然后创建PropertiesHandlerMethodReturnValueHandler实现HandlerMethodReturnValueHandler:

public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        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;

        ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;

        HttpServletResponse response = servletWebRequest.getResponse();
        ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);

        // 获取请求头
        HttpHeaders headers = servletServerHttpResponse.getHeaders();

        MediaType contentType = headers.getContentType();
        // 获取编码
        Charset charset = null;
        if (contentType != null) {
            charset = contentType.getCharset();
        }

        charset = charset == null ? Charset.forName("UTF-8") : charset;

        // 获取请求体
        OutputStream body = servletServerHttpResponse.getBody();
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(body, charset);

        properties.store(outputStreamWriter, "Serialized by PropertiesHandlerMethodReturnValueHandler#handleReturnValue");
    }
}

接着将PropertiesHandlerMethodReturnValueHandler添加到到Spring自带的HandlerMethodReturnValueHandler实现类集合中,添加方式和自定义HandlerMethodArgumentResolver一致

@Configuration
public class WebConfigurer implements WebMvcConfigurer {


    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @PostConstruct
    public void init() {
        // 获取当前 RequestMappingHandlerAdapter 所有的 ArgumentResolver对象
        List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        List<HandlerMethodArgumentResolver> newArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1);
        // 添加 PropertiesHandlerMethodArgumentResolver 到集合第一个位置
        newArgumentResolvers.add(0, new PropertiesHandlerMethodArgumentResolver());
        // 将原 ArgumentResolver 添加到集合中
        newArgumentResolvers.addAll(argumentResolvers);
        // 重新设置 ArgumentResolver对象集合
        requestMappingHandlerAdapter.setArgumentResolvers(newArgumentResolvers);

        // 获取当前 RequestMappingHandlerAdapter 所有的 returnValueHandlers对象
        List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newReturnValueHandlers = new ArrayList<>(returnValueHandlers.size() + 1);
        // 添加 PropertiesHandlerMethodReturnValueHandler 到集合第一个位置
        newReturnValueHandlers.add(0, new PropertiesHandlerMethodReturnValueHandler());
        // 将原 returnValueHandlers 添加到集合中
        newReturnValueHandlers.addAll(returnValueHandlers);
        // 重新设置 ReturnValueHandlers对象集合
        requestMappingHandlerAdapter.setReturnValueHandlers(newReturnValueHandlers);
    }
}

在这里插入图片描述
此时,请求能正常返回,但在控制台中会出现异常情况。
因为在Spring中如果Controller中的方法没有被@ResponseBody标注的话,默认会把返回值当成视图的名称,而这里我们并不希望解析的Properties值被当成视图名称,所以我们需要在PropertiesHandlerMethodReturnValueHandler的handleReturnValue方法最后一行添加如下代码:

// 告诉 Spring MVC 请求已经处理完毕
mavContainer.setRequestHandled(true);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值