扩展SpringMVC框架的消息转化器

发布时间 2023-08-09 20:33:48作者: 小徐同学x

1、消息转化器

请求和响应都有对应的body,而这个body就是需要关注的主要数据。

请求体与请求的查询参数或者表单参数是不同的,请求体的表述一般就是一段字符串,而查询参数可以看作url的一部分,这两个是位于请求报文的不同地方。表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。总之可以把请求体看作客户端通过请求报文捎带的字符串。

响应体则是浏览器渲染页面的依据,对于一个普通html页面得响应,响应体就是这个html页面的源代码。

请求体和响应体都是需要配合Content-Type头部使用的,这个头部主要用于说明body中得字符串是什么格式的,比如:text,json,xml等。对于请求报文,只有通过此头部,服务器才能知道怎么解析请求体中的字符串,对于响应报文,浏览器通过此头部才知道应该怎么渲染响应结果,是直接打印字符串还是根据代码渲染为一个网页。

还有一个与body有关的头部是Accept,这个头部标识了客户端期望得到什么格式的响应体。服务器可根据此字段选择合适的结果表述。

对于HttpServletRequest和HttpServletResponse,可以分别调用getInputStream和getOutputStream来直接获取body。但是获取到的仅仅只是一段字符串。

而对于java来说,处理一个对象肯定比处理一个字符串要方便得多,也好理解得多。所以根据Content-Type头部,将body字符串转换为java对象是常有的事。反过来,根据Accept头部,将java对象转换客户端期望格式的字符串也是必不可少的工作。

 

对于消息转换器的调用,都是在RequestResponseBodyMethodProcessor类中完成的。它实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口,分别实现了处理参数和处理返回值的方法。

而要动用这些消息转换器,需要在特定的位置加上@RequestBody和@ResponseBody。
下面是源码不一定看,但是要看自定义消息转换器。

/**
 * RequestResponseBodyMethodProcessor.class
 */
···
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));
    }
···

对返回值的消息转换来说:

/**
 * RequestResponseBodyMethodProcessor.class
 */
···
public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException;

大致流程为:
1.根据返回值获取其类型,其中MethodParameter封装了方法对象,可获去方法返回值类型。

1 /**
2  * AbstractMessageConverterMethodProcessor.class
3  */
4 ···
5             outputValue = value;
6             valueType = getReturnValueType(outputValue, returnType);
7             declaredType = getGenericType(returnType);

2.根据request的Accept和HandellerMapping的produces属性经过比对、排序从而得到最应该转换的消息格式(MediaType)。

 1 /**
 2  * AbstractMessageConverterMethodProcessor.class
 3  */
 4 ···
 5         List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
 6         List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
 7         Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
 8 ···
 9 //匹配
10 ···
11         List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
12         MediaType.sortBySpecificityAndQuality(mediaTypes);
13 
14         MediaType selectedMediaType = null;
15 ···
16 //选择最具体的MediaType
17 ···

3.遍历所有已配置的消息转换器,调用其canWrite方法,根据返回值类型(valueType)和消息格式(MediaType)来检测是否可以转换。

 1 /**
 2  * AbstractMessageConverterMethodProcessor.class
 3  */
 4 ···
 5 for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
 6 ···
 7      if (messageConverter.canWrite(valueType, selectedMediaType)){
 8 ···
 9         ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
10 ···
11      }
12 }

4.若有对应的转换器,则执行消息转换,即write方法。在write方法中,将返回值被转换后得到的字符串写在Response的输出流中。若找不到对应的转换器,则抛出HttpMediaTypeNotAcceptableException异常,浏览器会收到一个406 Not Acceptable状态码。

2、自定义消息转换器

除了spring提供的9个默认的消息转换器,还可以添加自定义的消息转换器,或者更换消息转换器的实现。

一个自定义消息转换器的例子:
对象映射器:

 1 /**
 2  * 此类是对象映射器:
 3  * 基于jackson将Java对象转为json,或者将json转为Java对象
 4  * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 5  * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 6  * 这里将数据类型转为其他类型解决一些数字偏大的从而导致js四舍五入出现精度不高的行为,所以下面有我们有一种将long类型的转化为string类型。
 7  */
 8 public class JacksonObjectMapper extends ObjectMapper {
 9 
10     public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
11     public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
12     public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
13 
14     public JacksonObjectMapper() {
15         super();
16         //收到未知属性时不报异常
17         this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
18 
19         //反序列化时,属性不存在的兼容处理
20         this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
21 
22 
23         SimpleModule simpleModule = new SimpleModule()
24                 .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
25                 .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
26                 .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
27 
28                 .addSerializer(BigInteger.class, ToStringSerializer.instance)
29                 //将long转化为字符串
30                 .addSerializer(Long.class, ToStringSerializer.instance)
31                 .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
32                 .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
33                 .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
34 
35         //注册功能模块 例如,可以添加自定义序列化器和反序列化器
36         this.registerModule(simpleModule);
37     }
38 }

 

扩展mvc框架的消息转换器,配置SpringMVC使用我们自定义的jackson对象转换器:

 1 @Slf4j
 2 @Configuration
 3 public class WebMvcConfig extends WebMvcConfigurationSupport {
 4     /*设置静态资源的映射*/
 5 
 6     @Override
 7     protected void addResourceHandlers(ResourceHandlerRegistry registry) {
 8         log.info("开始进行对静态资源的映射");
 9         registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
10         registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
11 
12     }
13 
14     /**
15      * 扩展mvc框架的消息转换器,不使用它默认的转换器,使用这个转换器,就能按照
16      * JacksonObjectMapper的对象映射器来进行对象之间转换。
17      * @param converters
18      */
19     @Override
20     protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
21         log.info("扩展消息转换器");
22         //创建消息转换器对象
23         MappingJackson2HttpMessageConverter messageConverter=new MappingJackson2HttpMessageConverter();
24         //设置对象转换器,底层使用Jackson将Java对象转为json
25         messageConverter.setObjectMapper(new JacksonObjectMapper());
26         //将上面的消息转换器对象追加到mvc框架的转换器集合中,第一个参数是设置优先级,最先使用这个消息转换器。
27         converters.add(0,messageConverter);
28     }
29 }          

作者:JerryL_
链接:https://www.jianshu.com/p/2f633cb817f5