返回结果(Response)的日期格式化-SpringMVC

MappingJackson2HttpMessageConverter是SpringMVC默认的Json消息转化器。

1.应用场景

例如:一个@RestController类里有个test8()方法:

@RequestMapping(value = "/test8", method = RequestMethod.GET)
    public ResponseEntity test8(@RequestParam(value = "account") String account){
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        SecUserMapper secUserMapperBatch=sqlSession.getMapper(SecUserMapper.class);
        List accountList = new ArrayList();
        accountList.add(account);
        List result =null;
        for(int i=0;i(result, HttpStatus.OK);

    }

public class User {
    private Long id;
    private String account;
    private Date modifyDate;
}

测试结果:

[{"id":1001125070,"account":"caifz","modifyDate":"2021-12-24T10:01:00.000+0000"}]

这里的modifyDate的格式不规范,我们希望请求结果返回“yyyy-MM-dd HH:mm:ss”格式的日期。在每个方法里写日期的转换?那就累死程序猿了。

统一设置返回结果的格式

新建一个config类,实现WebMvcConfigurer接口,继承这个接口后实现extendMessageConverters()方法,这个方法里定义

@Configuration
public class ControllerConfiguration implements WebMvcConfigurer,ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper1=mappingJackson2HttpMessageConverter.getObjectMapper();
        objectMapper1.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return mappingJackson2HttpMessageConverter;
    }

    @Override
    public void extendMessageConverters(List> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
        applicationContext.getBean(MappingJackson2HttpMessageConverter.class);
    }
}

2.机制原理

HttpMessageConverter接口

使用debug模式查看converters里包含哪些HttpMessageConverter,发现在添加我们定义的mappingJackson2HttpmessageConverter前,已经有10个了。


image.png

HttpMessageConverter 接口介绍

image.png

这个接口的作用:Strategy interface that specifies a converter that can convert from and to HTTP requests and responses。策略接口定义了http请求转换为Java对象和Java对象转为http响应。接口有5个方法:canRead(类型是否可读),read(可读则读),canWrite(是否可写),write(可写则写),getSupportedMediaTypes(获取支持的媒体类型)。

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.http.converter;

import java.io.IOException;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;

/**
 * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @since 3.0
 * @param  the converted object type
 */
public interface HttpMessageConverter {

    /**
     * Indicates whether the given class can be read by this converter.
     * @param clazz the class to test for readability
     * @param mediaType the media type to read (can be {@code null} if not specified);
     * typically the value of a {@code Content-Type} header.
     * @return {@code true} if readable; {@code false} otherwise
     */
    boolean canRead(Class clazz, @Nullable MediaType mediaType);

    /**
     * Indicates whether the given class can be written by this converter.
     * @param clazz the class to test for writability
     * @param mediaType the media type to write (can be {@code null} if not specified);
     * typically the value of an {@code Accept} header.
     * @return {@code true} if writable; {@code false} otherwise
     */
    boolean canWrite(Class clazz, @Nullable MediaType mediaType);

    /**
     * Return the list of {@link MediaType} objects supported by this converter.
     * @return the list of supported media types
     */
    List getSupportedMediaTypes();

    /**
     * Read an object of the given type from the given input message, and returns it.
     * @param clazz the type of object to return. This type must have previously been passed to the
     * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
     * @param inputMessage the HTTP input message to read from
     * @return the converted object
     * @throws IOException in case of I/O errors
     * @throws HttpMessageNotReadableException in case of conversion errors
     */
    T read(Class clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    /**
     * Write an given object to the given output message.
     * @param t the object to write to the output message. The type of this object must have previously been
     * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
     * @param contentType the content type to use when writing. May be {@code null} to indicate that the
     * default content type of the converter must be used. If not {@code null}, this media type must have
     * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
     * returned {@code true}.
     * @param outputMessage the message to write to
     * @throws IOException in case of I/O errors
     * @throws HttpMessageNotWritableException in case of conversion errors
     */
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;

}

HttpMessageConverter接口和MappingJackson2HttpMessageConverter实现类

HttpMessageConverter接口和MappingJackson2HttpMessageConverter实现类的集成关系如下:


MappingJackson2HttpMessageConverter.png

MappingJackson2HttpMessageConverter介绍

MappingJackson2HttpMessageConverter实现了HttpMessageConverter,使用Jackson的ObjectMapper实现了读Json和写Json。MappingJackson2HttpMessageConverter的主要逻辑在AbstractJackson2HttpMessageConverter类中。

ObjectMapper介绍

ObjectMapper提供了读写JSON的功能,往返于基本POJO(普通旧Java对象)或往返于通用JSON树模型,以及用于执行转换的相关功能。

HttpMessageConverter是如何配置的?

当我们没有配置HttpMessageConverter时,springmvc正常转换参数和返回结果。Spring boot肯定提供了默认配置。
spring-boot-autoconfigure包里提供了WebMvcAutoConfiguration,HttpMessageConvertersAutoConfiguration,增加了默认的10个HttpMessageConverter。

  • 1.WebMvcAutoConfiguration里包含下面的配置使得WebMvcConfigurationSupport类成为配置类。(EnableWebMvcConfiguration 继承DelegatingWebMvcConfiguration,DelegatingWebMvcConfiguration继承WebMvcConfigurationSupport)
    /**
     * Configuration equivalent to {@code @EnableWebMvc}.
     */
    @Configuration
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration
  • 2.WebMvcConfigurationSupport创建Bean-->getMessageConverters()-->addDefaultHttpMessageConverters(),在addDefaultHttpMessageConverters()里新建了8个HttpMessageConverter
@Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
        adapter.setContentNegotiationManager(mvcContentNegotiationManager());
        adapter.setMessageConverters(getMessageConverters());
        ...
      }

protected final List> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList<>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }

/**
     * Adds a set of default HttpMessageConverter instances to the given list.
     * Subclasses can call this method from {@link #configureMessageConverters}.
     * @param messageConverters the list to add the default message converters to
     */
    protected final void addDefaultHttpMessageConverters(List> messageConverters) {
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringHttpMessageConverter);
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new ResourceRegionHttpMessageConverter());
        ...
     }
  • 3.HttpMessageConvertersAutoConfiguration注入了2个HttpMessageConverter:StringHttpMessageConverter和MappingJackson2HttpMessageConverter。
    public HttpMessageConvertersAutoConfiguration(
            ObjectProvider> convertersProvider) {
        this.converters = convertersProvider.orderedStream().collect(Collectors.toList());
    }
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters() {
        return new HttpMessageConverters(this.converters);
    }
  • 4.自定义配置类里重载了extendMessageConverters(),添加了1个mappingJackson2HttpMessageConverter
@Override
    public void extendMessageConverters(List> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        System.out.println("");
    }

3.数据流转

如何梳理一个请求的数据流转?
首先找到一个所有请求都会经过的类DispatchServlet,找到其中的doDispatch方法,打个断点,然后使用IDEA的debug模式,一路跟踪代码。


dispatchServlet.png

4.搭配使用ResponseBodyAdvice和ControllerAdvice

  • @ControllerAdvice通过切面的方式给controller类加了一个增强器,assignableTypes 指定需要增强的Controller类。
  • @ExceptionHandler这个注解则表示Controller中任何一个方法发生异常,则会被注解了@ExceptionHandler的方法拦截到。对应的异常类执行对应的方法,如果都没有匹配到异常类,则采用近亲匹配的方式。
  • ResponseBodyAdvice接口的beforeBodyWrite方法:在Respons返回前进行处理,设置成统一的对象再返回。
@ControllerAdvice(assignableTypes = GroupMapperController2.class)
public class ControllerResultWrapper implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, 
                                  Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        RestResponse restResponse=new RestResponse();
        restResponse.setSuccess(true);
        restResponse.setMsg("成功");
        restResponse.setData(body);
        return restResponse;
    }

    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e){
        RestResponse restResponse=new RestResponse();
        restResponse.setSuccess(false);
        restResponse.setMsg(e.getMessage());
        return restResponse;
    }
}

你可能感兴趣的:(返回结果(Response)的日期格式化-SpringMVC)