Solve thoroughly the problems of time conversion and serialization in Spring mvc

Keywords: Programming Java Spring Redis RabbitMQ

Pain spot

When developing with Spring mvc, we often encounter that the time strings in some format passed from the front end can not be directly received with the specific type parameters under the java.time package, a new feature of Java 8. We use encapsulation types with java.time Parametric reception also reports deserialization problems, and there are some formatting problems with time type in the return front end. Today we're going to solve them completely.

proposal

In fact, the most scientific suggestion is to use time stamps to represent time. This is the most perfect one, which avoids the compatibility problem of front-end browsers and the serialization/deserialization problem of other middleware. But time expression may be more semantically clear. The two approaches have their own merits. If we insist on using the time class library of Java 8, we can't help it. We will solve these problems one by one with java.time.LocalDateTime.

Local annotation

There are many articles on the Internet saying that the annotation is from the front end to the back end, that is, the front end to the back end to transfer the time parameter formatting use, this is not wrong! __________ However, there is a small problem that this approach can only be applied in cases where deserialization is not involved. That is, the following scenarios apply:

    @GetMapping("/local")
    public Map<String, String> data(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime localDateTime) {
        Map<String, String> map = new HashMap<>(1);
        map.put("data", localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        return map;
    }

If you use it in the following scenario, you can't:


@Data
public class UserInfo {

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime birthday;
    private String name;
    private Integer age;
}


   @PostMapping("/user")
    public Object postData(@RequestBody UserInfo userInfo) {
        System.out.println("userInfo = " + userInfo);
        return userInfo;
    }

The reason is that the Post request parameter in the body needs to be deserialized into objects. The default is to deserialize the jackson class library without triggering the @DateTimeFormat annotation mechanism. Then we need to use jackson's formatted annotation @JsonFormat. We can transform the entity class UserInfo into the following one:

@Data
public class UserInfo {

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime birthday;
    private String name;
    private Integer age;
}

The above two annotations can coexist, but be sure to clarify their respective usage scenarios. Here's a little detail: the format must correspond to the type of time. For example, y yyyy-MM-dd corresponds to java.time.LocalDate. If more personalized @JsonFormat can be replaced by @JsonDeserialize and @JsonSerialize. But their using parameters need to be implemented by yourself as your corresponding time type. If @JsonFormat, @JsonDeserialize and @JsonSerialize exist at the same time, @JsonFormat has a higher priority.

Benefits of local processing

The advantage of local processing lies in eight words: flowers blossom and schools of thought contend. It can maintain diversity and individuality. But there is a new problem: there is no common standard and incompatibility. It is not easy to maintain. So sometimes based on business needs, we can manage the whole situation in a unified way. Next we will explain how to make global configuration.

Global Time Format Configuration

Globalization is also based on @DateTime Format and @JsonFormat scenarios. For the @DateTimeFormat scenario, we implemented the interface provided by Spring:

DateTimeFormatter :

     // Time Formatting
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);

Type conversion interface:

 org.springframework.core.convert.converter.Converter<S,T>

Realization:

    @Bean
    public Converter<String, LocalDateTime> localDateConverter() {
        return new Converter<String, LocalDateTime>() {
            @Override
            public LocalDateTime convert(String source) {
                return LocalDateTime.parse(source, FORMATTER);
            }
        };
    }

Or formatting interfaces:

 org.springframework.format.Formatter<T>

Realization:

    @Bean
    public Formatter<LocalDateTime> localDateFormatter() {
        return new Formatter<LocalDateTime>() {
            @Override
            public LocalDateTime parse(String text, Locale locale) throws ParseException {
                return LocalDateTime.parse(text, FORMATTER);
            }

            @Override
            public String print(LocalDateTime object, Locale locale) {
                return object.format(FORMATTER);
            }
        };
    }

The implementation of the above two interfaces should be registered as Spring Bean. When configuring, one of them can be chosen. S is Source, which is the source, in fact, the time string of the front end. T, Target, is the target, representing the time java type that you need to convert or format.

Then we can configure time series and deserialization as follows (based on default jackson, take Local DateTime as an example):

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {

        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
                 // De serialization
                .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(FORMATTER))
                 // serialize
                .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(FORMATTER));
    }

Similarly, the jsonMapper custom builder needs to be registered as Spring Bean.

Key Points of Global Configuration

Some of the advantages and disadvantages of the global configuration have been described above, and here I'll go over the main points to avoid you stepping on the pit. Global configuration is the same as local configuration. Similarly, pattern should be agreed upon. This requires us to be consistent in the overall situation. We can implement more than one global configuration to adapt to other global configurations such as LocalDate and Offset DateTime. At the same time, if we access other middleware that need to be serialized/deserialized, such as redis, rabbitmq, we should also pay attention to adaptation.

Sum up Through the introduction of the local and global processing of the time format, I believe that the Spring mvc time problem that bothers you will no longer exist. If you feel right, please forward it to other students, give them a compliment and pay attention to it.

Focus on the Public Number: Number Farmer, Little Pang, Get More Information

Posted by jwoo on Wed, 18 Sep 2019 03:55:11 -0700