ringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,允许流程如下所示:
【1】什么是WebDataBinder
由@InitBinder
标识的方法,可以对WebDataBinder
对象进行初始化(于目标方法执行前执行)。WebDataBinder
是DataBinder
的子类,用于完成由表单字段到JavaBean
属性的绑定。
以下为WebDataBinder的注释:
- 将请求参数绑定到JavaBean对象;
- 设计用于web环境,但不依赖Servlet API;用作更具体的DataBinder变体的基类,例如
org.springframework.web.bind.ServletRequestDataBinder
。 - 包括对字段标记的支持,用于解决HTML复选框和下拉选择的常见问题:检测字段是表单的一部分,但由于字段为空而未生成请求参数。
- 字段标记允许检测该状态并相应地重置相应的bean属性。对于不存在的参数,默认值可以为字段指定一个值,而不是空值。
需要注意的是:
- ①
@InitBinder
方法不能有返回值,它必须声明为void
; - ②
@InitBinder
方法的参数通常是WebDataBinder
;
通常在@Controller注解的类中使用@ExceptionHandler, @InitBinder, and @ModelAttribute方法。如果你想在全局使用(也就是跨controller),那么你可以在@ControllerAdvice 或@RestControllerAdvice类中声明这些方法。
在项目启动时,@RequestMapping 和@ExceptionHandler 方法的基础结构类检测用@ControllerAdvice注解的Spring bean,然后在运行时应用它们的方法。全局@ExceptionHandler 方法(来自@ControllerAdvice)在本地方法(来自@Controller)之后应用。相比之下,全局@ModelAttribute 和@InitBinder 方法应用于本地方法(当前controller中方法)之前。
默认情况下,@ControllerAdvice注解的方法应用于每个请求(也就是说所有控制器)。
【2】如何使用@InitBinder
① @InitBinder日期转换
如下所示,在某个controller或者全局@ControllerAdvice
注解的controller中应用。
@InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true)); }
该方法常用于表单–对象的日期属性的类型转换。
② 使用xml配置自定义日期转换器
springmvc.xml
<!-- 注册处理器映射器/处理器适配器 ,添加conversion-service属性--> <mvc:annotation-driven conversion-service="conversionService"/> <!-- 创建conversionService,并注入dateConvert--> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="dateConvert"/> </set> </property> </bean> <!-- 创建自定义日期转换规则 --> <bean id="dateConvert" class="com.web.convert.DateConvert"/>
自定义日期转换器
public class DateConvert implements Converter<String, Date> { public Date convert(String s) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { return simpleDateFormat.parse(s); } catch (ParseException e) { e.printStackTrace(); } return null; } }
除了使用@InitBinder
在数据绑定的时候进行格式化,还可以采用Formatter
格式化器或者Convert
转换器进行格式化。
【3】自定义Formatter进行日期格式化
默认情况下(也就是不做额外配置)前端传输"yyyy-MM-dd HH:mm:ss"格式的日期字符串,后端使用LocalDateTime接收是接收不到的,此时获取的为null。同样,你将LocalDateTime序列化为前端需要的"yyyy-MM-dd HH:mm:ss"格式的日期字符串也是不可能的。这就需要额外进行配置。
简单的String与LocalDateTime转换
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern; @Bean public Formatter<LocalDateTime> localDateTimeFormatter() { return new Formatter<LocalDateTime>() { @Override public LocalDateTime parse(String text, Locale locale) { return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern)); } @Override public String print(LocalDateTime localDateTime, Locale locale) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); return formatter.format(localDateTime); } }; }
【4】自定义Converter进行日期格式转换
String与LocalDateTime格式转换
@Configuration public class MappingConverterAdapter { private static final Logger logger=LoggerFactory.getLogger(MappingConverterAdapter.class); @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern; //String转换为LocalDateTime @Bean public Converter<String, LocalDateTime> localDateTimeConvert() { return new Converter<String, LocalDateTime>() { @Override public LocalDateTime convert(String source) { if(StringUtils.isEmpty(source)){ return null; } DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); LocalDateTime dateTime = null; try { //2020-01-01 00:00:00 dateTime = LocalDateTime.parse(dateTimeText(source), df); } catch (Exception e) { logger.error(e.getMessage(),e); } return dateTime; } }; } //LocalDateTime转换为String @Bean public Converter<LocalDateTime, String> localDateTimeToString() { return new Converter<LocalDateTime, String>() { @Override public String convert(LocalDateTime localDateTime) { if(localDateTime==null){ return null; } DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); return formatter.format(localDateTime); } }; } /** * 时间字符串格式化 * @param source * @return */ private static String dateTimeText(String source){ //2020-01-01 00:00:00 switch (source.length()){ case 10: logger.debug("传过来的是日期格式:{}",source); source=source+" 00:00:00"; break; case 13: logger.debug("传过来的是日期 小时格式:{}",source); source=source+":00:00"; break; case 16: logger.debug("传过来的是日期 小时:分钟格式:{}",source); source=source+":00"; break; } return source; } }
【5】LocalDateTime日志格式化与JSON序列/反序列化
当使用json序列化时,有时可能会遇到异常情况
- 序列化/反序列失败
- 返回的LocalDateTime不是常见的显示标准"
yyyy-MM-dd HH:mm:ss
",而是多了一个T,如2021-10-22T09:41:40.601
(LocalDateTime默认String格式)
这时你可能需要注入下面这个配置:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Configuration public class LocalDateTimeSerializerConfig { @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String pattern; // localDateTime 序列化器 @Bean public LocalDateTimeSerializer localDateTimeSerializer() { return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern)); } // localDateTime 反序列化器 @Bean public LocalDateTimeDeserializer localDateTimeDeserializer() { return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(pattern)); } @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { // return new Jackson2ObjectMapperBuilderCustomizer() { // @Override // public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) { jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, localDateTimeSerializer()); // jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class,localDateTimeDeserializer()); // } // }; //这种方式同上 return builder -> { builder.serializerByType(LocalDateTime.class, localDateTimeSerializer()); builder.deserializerByType(LocalDateTime.class,localDateTimeDeserializer()); builder.simpleDateFormat(pattern); }; } }