Date type parameters in Web requests

Keywords: SpringBoot Spring

In web requests, it's often a headache to encounter date types, because it may involve various format issues, such as "2019-03-07 17:10:21", "2019-03-07 17:10", "2019-03-07 17:10", "2019-03-07", and even 1551949821, 1551949821000 and so on. These problems can hardly be guaranteed without documentation alone.
So how can we solve this difficult problem? Let's not talk about any other data binding methods. Today we're going to talk about a non-intrusion and non-awareness solution that combines Springboot features.~
In SpringBook's WebMvc implementation, the following interfaces are specifically provided to solve various format problems

/**
 * Add {@link Converter Converters} and {@link Formatter Formatters} in addition to the ones
 * registered by default.
 */
default void addFormatters(FormatterRegistry registry) {
}

This method is empty by default. The function of this method is easy to understand from the annotations, that is to register Converter and Formatter, so we only need to register a date conversion type Converter to solve the problem.
Let's write a date conversion Converter first.

public class StringDateConverter implements Converter<String, Date> {
    private static Logger logger = LoggerFactory.getLogger(StringDateConverter.class);
    
    private static final String TIME_PATTERN_REGEX = "^\\d{1,13}$";
    
    private static ThreadLocal<SimpleDateFormat[]> dateFormatLocal = new ThreadLocal<SimpleDateFormat[]>() {
        @Override
        protected SimpleDateFormat[] initialValue() {
            return new SimpleDateFormat[] {
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
                    new SimpleDateFormat("yyyy-MM-dd HH:mm"),
                    new SimpleDateFormat("yyyy-MM-dd HH"),
                    new SimpleDateFormat("yyyy-MM-dd")
                };
        }
    };
    
    @Override
    public Date convert(final String source) {
        if (source == null || source.trim().equals("")) {
            return null;
        }
        
        Date result = null;
        String _src = source.trim();
        // 1. Number type
        if (_src.matches(TIME_PATTERN_REGEX)) {
            try {
                long lTime = Long.parseLong(_src);
                if (_src.length() > 10) {
                    result = new Date(lTime);
                } else {
                    result =  new Date(1000L * lTime);
                }
            } catch (Exception e) {
                result = null;
                
                logger.warn("[" + source + "]Unable to convert to date!");
            }
            
            return result;
        }
        // 2. Date type
        SimpleDateFormat[] dateFormats = dateFormatLocal.get();
        for (SimpleDateFormat dateFormat : dateFormats) {
            try {
                dateFormat.setLenient(false);
                
                return dateFormat.parse(source);
            } catch (ParseException e) {
                logger.warn("[" + source + "]Can not be converted into" + dateFormat.toPattern() + "Date of format!");
            }
        }
        
        return null;
    }
}

The StringDateConverter converter supports the conversion of strings in the format "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH", "yyyyy-MM-dd" and long numbers to date types and is thread safe.
Then, we can register the converter into the system. There are also two options:
Method 1. The WebConfig file in the system inherits WebMvcConfigurer and overloads the addFormatters method:

@Configuration
@Import({ WebMvcAutoConfiguration.class })
@ComponentScan(
        value = "com.beyonds.phoenix.shine.web",
        includeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
        })
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringDateConverter());
    }
}

Method 2: Register StringDateConverter directly as a bean!

@Configuration
@Import({ WebMvcAutoConfiguration.class })
@ComponentScan(
        value = "com.beyonds.phoenix.shine.web",
        includeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
        })
public class WebMvcConfiguration {
    /**
     * Custom input date format
     * Override spring.mvc.date-format
     * @return Date format converter
     */
    @Bean
    public StringDateConverter dateConverter() {
        return new StringDateConverter();
    }
}

Why does method 2 work? That's how SpringBoot implements its internal automation mechanism.~
In fact, in the internal implementation of WebMvc AutoConfiguration, there is the following code

@Override
public void addFormatters(FormatterRegistry registry) {
    for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
        registry.addConverter(converter);
    }
    for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
        registry.addConverter(converter);
    }
    for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
        registry.addFormatter(formatter);
    }
}

Its role is clear: automatically scan and register Converter, GenericConverter and Formatter to the web processor~
-End-

Posted by hijack on Sun, 19 May 2019 04:50:46 -0700