@JsonFormat 使用样例
-
前端传递一个日期的字符串,后端想使用LocalDate 或者 LocalDateTime接收;
-
后端的实体类,属性是LocalDate 或者 LocalDateTime,但是给到前端时,想以指定的字符串格式显示
public class Person {
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd",shape = JsonFormat.Shape.STRING)
private LocalDate birthDate;
private LocalDateTime deadDate;
添加接口,前端请求
{
"birthDate":"2022-12-22",
"name":"lisi"
}
查询接口,前端接收到的
[
{
"name": "zhangsan",
"age": 25,
"birthDate": "1998-02-13",
"deadDate": "2078-02-13T18:58:55.898"
}
]
@JsonFormat 实现原理
思路
public class Person {
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd",shape = JsonFormat.Shape.STRING)
private LocalDate birthDate;
private LocalDateTime deadDate;
我先说下思路,我们在代码中,给birthDate添加了一个@JsonFormat
注解,这个注解中,我们指定了pattern。
实现的思路,分为2步:
- 读取注解中的信息,比如pattern的值是什么样的格式,shape的值是什么样子的,读取到这些值以后,存储到反序列化器中
- 读取一个值以后,一开始是一个Object类型的 或者 string类型的,接下来利用这个反序列化器中的pattern格式,进行解析,从而得到日期类型的值
详细分析
// org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
...
// Actually invoke the handler. 1到2的入口
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
...
else {
// No synchronization on session demanded at all... 2到3的入口
mav = invokeHandlerMethod(request, response, handlerMethod);
}
....
return mav;
}
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
// 3到4的入口
invocableMethod.invokeAndHandle(webRequest, mavContainer);
...
}
// org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 4到5的入口
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
...
}
// org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 5到6的入口
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
...
}
getMethodArgumentValues(request, mavContainer, providedArgs)
方法正好是获取方法请求参数值的,于是我们猜想,时间 Format 转换应该在这里面完成的
//org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 6到7的入口
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
}
return args;
}
处理请求参数的逻辑在this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)
这个方法里.
这个resolvers,其实是HandlerMethodArgumentResolverComposite
// org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
// 7到8的入口
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
该方法会选择对应的HandlerMethodArgumentResolver
来处理,由于我使用@RequestBody
来接收请求参数的,于是HandlerMethodArgumentResolver
为RequestResponseBodyMethodProcessor
;如果是表单接收,则是ServletModelAttributeMethodProcessor
// org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 8到9的入口
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
...
}
该方法中,会将HttpServletRequest
转化为ServletServerHttpRequest
(请求参数在里面),然后调用AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(HttpInputMessage, MethodParameter, Type)
来处理请求参数:
// org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#readWithMessageConverters
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
// 9到10的入口
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
...
}
// org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
...
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 10到11的入口
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
...
}
}
}
...
}
该方法会遍历消息转换器messageConverters
,来处理请求参数中的每一个参数,我们先看总共有多少个消息转换器:
他们都是HttpMessageConverter接口的实现类
在这8个messageConverters
中,只有MappingJackson2HttpMessageConverter
是GenericHttpMessageConverter
,于是我们直接看MappingJackson2HttpMessageConverter#canRead(Type, Class<?>,MediaType)
方法:
// org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canRead(java.lang.reflect.Type, java.lang.Class<?>, org.springframework.http.MediaType)
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
if (!canRead(mediaType)) {
return false;
}
JavaType javaType = getJavaType(type, contextClass);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), mediaType);
if (objectMapper == null) {
return false;
}
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// 11到12的入口
if (objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
logWarningIfNecessary(javaType, causeRef.get());
return false;
}
//com.fasterxml.jackson.databind.ObjectMapper#canDeserialize(com.fasterxml.jackson.databind.JavaType, java.util.concurrent.atomic.AtomicReference<java.lang.Throwable>)
public boolean canDeserialize(JavaType type, AtomicReference<Throwable> cause)
{
// 12到13的入口
return createDeserializationContext(null,
getDeserializationConfig()).hasValueDeserializerFor(type, cause);
}
// com.fasterxml.jackson.databind.DeserializationContext#hasValueDeserializerFor
public boolean hasValueDeserializerFor(JavaType type, AtomicReference<Throwable> cause) {
try {
// 13到14的入口
return _cache.hasValueDeserializerFor(this, _factory, type);
...
}
// com.fasterxml.jackson.databind.deser.DeserializerCache#hasValueDeserializerFor
public boolean hasValueDeserializerFor(DeserializationContext ctxt,
DeserializerFactory factory, JavaType type)
throws JsonMappingException
{
/* Note: mostly copied from findValueDeserializer, except for
* handling of unknown types
*/
JsonDeserializer<Object> deser = _findCachedDeserializer(type);
if (deser == null) {
// 14到15的入口
deser = _createAndCacheValueDeserializer(ctxt, factory, type);
}
return (deser != null);
}
protected JsonDeserializer<Object> _createAndCacheValueDeserializer(DeserializationContext ctxt,
DeserializerFactory factory, JavaType type)
throws JsonMappingException
{
/* Only one thread to construct deserializers at any given point in time;
* limitations necessary to ensure that only completely initialized ones
* are visible and used.
*/
...
// 15到16的入口
return _createAndCache2(ctxt, factory, type);
...
}
// com.fasterxml.jackson.databind.deser.DeserializerCache#_createAndCache2
protected JsonDeserializer<Object> _createAndCache2(DeserializationContext ctxt,
DeserializerFactory factory, JavaType type)
throws JsonMappingException
...
try {
deser = _createDeserializer(ctxt, factory, type);
...
}
上面的流程图,如下:
以下是copy
protected JsonDeserializer<Object> _createAndCache2(DeserializationContext ctxt,
DeserializerFactory factory, JavaType type)
throws JsonMappingException
{
JsonDeserializer<Object> deser;
try {
deser = _createDeserializer(ctxt, factory, type);
} catch (IllegalArgumentException iae) {
// We better only expose checked exceptions, since those
// are what caller is expected to handle
throw JsonMappingException.from(ctxt, iae.getMessage(), iae);
}
if (deser == null) {
return null;
}
/* cache resulting deserializer? always true for "plain" BeanDeserializer
* (but can be re-defined for sub-classes by using @JsonCachable!)
*/
// 27-Mar-2015, tatu: As per [databind#735], avoid caching types with custom value desers
boolean addToCache = !_hasCustomHandlers(type) && deser.isCachable();
/* we will temporarily hold on to all created deserializers (to
* handle cyclic references, and possibly reuse non-cached
* deserializers (list, map))
*/
/* 07-Jun-2010, tatu: Danger: [JACKSON-296] was caused by accidental
* resolution of a reference -- couple of ways to prevent this;
* either not add Lists or Maps, or clear references eagerly.
* Let's actually do both; since both seem reasonable.
*/
/* Need to resolve? Mostly done for bean deserializers; required for
* resolving cyclic references.
*/
if (deser instanceof ResolvableDeserializer) {
_incompleteDeserializers.put(type, deser);
((ResolvableDeserializer)deser).resolve(ctxt);
_incompleteDeserializers.remove(type);
}
if (addToCache) {
_cachedDeserializers.put(type, deser);
}
return deser;
}
首先它会创建一个反序列化器_createDeserializer(ctxt, factory, type)
,然后通过这个反序列化器来处理请求参数((ResolvableDeserializer)deser).resolve(ctxt)
。在创建反序列化器的过程中,它会给每个请求参数生成一个MethodProperty
对象,用来封装请求参数的信息,如参数名称,参数类型,注解信息等等,感兴趣的小伙伴可以自己往下看,由于篇幅原因我就不累赘了。我们直接看它处理请求参数的逻辑:
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
...
for (SettableBeanProperty origProp : _beanProperties) {
SettableBeanProperty prop = origProp;
JsonDeserializer<?> deser = prop.getValueDeserializer();
deser = ctxt.handlePrimaryContextualization(deser, prop, prop.getType());
prop = prop.withValueDeserializer(deser);
// Need to link managed references with matching back references
prop = _resolveManagedReferenceProperty(ctxt, prop);
}
...
}
该方法会遍历SettableBeanProperty
集合,SettableBeanProperty
是MethodProperty
的父类。然后调用DeserializationContext#handlePrimaryContextualization
方法:
public JsonDeserializer<?> handlePrimaryContextualization(JsonDeserializer<?> deser,
BeanProperty prop, JavaType type)
throws JsonMappingException
{
if (deser instanceof ContextualDeserializer) {
_currentType = new LinkedNode<JavaType>(type, _currentType);
try {
deser = ((ContextualDeserializer) deser).createContextual(this, prop);
} finally {
_currentType = _currentType.next();
}
}
return deser;
}
该方法通过根据主属性反序列化器JsonDeserializer
(该反序列化器还不是完整版,没有注解信息的)来反序列化POJO属性的值,这里有个判断deser instanceof ContextualDeserializer
,我们来看ContextualDeserializer
是个什么东东:
package com.fasterxml.jackson.databind.deser;
import com.fasterxml.jackson.databind.*;
/**
* Add-on interface that {@link JsonDeserializer}s can implement to get a callback
* that can be used to create contextual (context-dependent) instances of
* deserializer to use for handling properties of supported type.
* This can be useful
* for deserializers that can be configured by annotations, or should otherwise
* have differing behavior depending on what kind of property is being deserialized.
*<p>
* Note that in cases where deserializer needs both contextualization and
* resolution -- that is, implements both this interface and {@link ResolvableDeserializer}
* -- resolution via {@link ResolvableDeserializer} occurs first, and contextual
* resolution (via this interface) later on.
*/
public interface ContextualDeserializer
{
/**
* Method called to see if a different (or differently configured) deserializer
* is needed to deserialize values of specified property.
* Note that instance that this method is called on is typically shared one and
* as a result method should <b>NOT</b> modify this instance but rather construct
* and return a new instance. This instance should only be returned as-is, in case
* it is already suitable for use.
*
* @param ctxt Deserialization context to access configuration, additional
* deserializers that may be needed by this deserializer
* @param property Method, field or constructor parameter that represents the property
* (and is used to assign deserialized value).
* Should be available; but there may be cases where caller cannot provide it and
* null is passed instead (in which case impls usually pass 'this' deserializer as is)
*
* @return Deserializer to use for deserializing values of specified property;
* may be this instance or a new instance.
*
* @throws JsonMappingException
*/
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException;
}
ContextualDeserializer
是一个扩展接口,实现该接口来获得不同参数类型的反序列化器实例。例如PrimitiveArrayDeserializers
是基本类型数组反序列化器,DateBasedDeserializer
是时间类型反序列化器。但是注意,基本类型和 String 类型并不是ContextualDeserializer
。所以我们的name
属性类型是 String 并不会进入判断条件,startTime
属性的类型是Date,故进入了判断条件。
关键部分
随后通过DateBasedDeserializer#createContextual
来获取时间类型反序列化器实例:
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException
{
final JsonFormat.Value format = findFormatOverrides(ctxt, property,
handledType());
if (format != null) {
TimeZone tz = format.getTimeZone();
final Boolean lenient = format.getLenient();
// First: fully custom pattern?
if (format.hasPattern()) {
final String pattern = format.getPattern();
final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
if (tz == null) {
tz = ctxt.getTimeZone();
}
df.setTimeZone(tz);
if (lenient != null) {
df.setLenient(lenient);
}
return withDateFormat(df, pattern);
}
...
return this;
}
protected JsonFormat.Value findFormatOverrides(DeserializationContext ctxt,
BeanProperty prop, Class<?> typeForDefaults)
{
if (prop != null) {
return prop.findPropertyFormat(ctxt.getConfig(), typeForDefaults);
}
// even without property or AnnotationIntrospector, may have type-specific defaults
return ctxt.getDefaultPropertyFormat(typeForDefaults);
}
在这个方法里,终于找到我们想看到的关键字了JsonFormat
,我们应该迫不及待的看它的findFormatOverrides(ctxt, property,handledType())
方法,因为它会找出参数上@JsonFormat
注解,以便解析,由于我们的property
是SettableBeanProperty
类型,而SettableBeanProperty
又是ConcreteBeanPropertyBase
的子类,于是我们直接看ConcreteBeanPropertyBase#findPropertyFormat
方法:
public JsonFormat.Value findPropertyFormat(MapperConfig<?> config, Class<?> baseType)
{
// 15-Apr-2016, tatu: Let's calculate lazily, retain; assumption being however that
// baseType is always the same
JsonFormat.Value v = _propertyFormat;
if (v == null) {
JsonFormat.Value v1 = config.getDefaultPropertyFormat(baseType);
JsonFormat.Value v2 = null;
AnnotationIntrospector intr = config.getAnnotationIntrospector();
if (intr != null) {
AnnotatedMember member = getMember();
if (member != null) {
v2 = intr.findFormat(member);
}
}
if (v1 == null) {
v = (v2 == null) ? EMPTY_FORMAT : v2;
} else {
v = (v2 == null) ? v1 : v1.withOverrides(v2);
}
_propertyFormat = v;
}
return v;
}
public final class MethodProperty extends SettableBeanProperty {
private static final long serialVersionUID = 1;
protected final AnnotatedMethod _annotated;
public AnnotatedMember getMember() { return _annotated; }
该方法就是来获取参数上@JsonFormat
的值,getMember
方法会取MethodProperty#_annotated
属性值,于是就拿到了请求参数上的所有注解(可能多个),我们的startTime
就一个@JsonFormat
注解,然后通过findFormat(member)
方法获取指定@JsonFormat
注解信息。
拿到注解信息之后,就可以继续在 DateBasedDeserializer#createContextual
方法中,来生成时间类型的反序列化器实例DateBasedDeserializer#withDateFormat
:
protected DateDeserializer withDateFormat(DateFormat df, String formatString) {
return new DateDeserializer(this, df, formatString);
}
生成的DateDeserializer
赋值给SettableBeanProperty
的_valueDeserializer
属性,以便下次使用。
那么,哪里用到了生成的时间类型反序列化器DateDeserializer
呢?直接看AbstractJackson2HttpMessageConverter#read
方法:
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(type, contextClass);
return readJavaType(javaType, inputMessage);
}
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
try {
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
return this.objectMapper.readerWithView(deserializationView).forType(javaType).
readValue(inputMessage.getBody());
}
}
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex);
}
}
public <T> T readValue(InputStream src, JavaType valueType)
throws IOException, JsonParseException, JsonMappingException
{
return (T) _readMapAndClose(_jsonFactory.createParser(src), valueType);
}
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
Object result;
JsonToken t = _initForReading(p, valueType);
final DeserializationConfig cfg = getDeserializationConfig();
final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
if (cfg.useRootWrapping()) {
result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
} else {
result = deser.deserialize(p, ctxt);
}
ctxt.checkUnresolvedObjectId();
}
if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
_verifyNoTrailingTokens(p, ctxt, valueType);
}
return result;
}
}
先找到处理请求参数的根反序列化器,因为我们请求参数是使用对象接收的,于是根反序列化器为BeanDeserializer
,于是我们直接看反序列化的操作 BeanDeserializer#deserialize
:
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
// 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
// what it is, including "expected behavior".
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, p.getCurrentToken());
}
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
...
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
...
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
// 这行代码,是入口
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
_valueInstantiator.createUsingDefault(ctxt)
方法生成一个默认值的请求参数对象bean
,然后遍历每个请求参数,调用prop.deserializeAndSet(p, ctxt, bean)
方法,来进行反序列化并赋值操作,我们看startTime
参数的反序列化过程,prop
为MethodProperty
,我们看其deserializeAndSet
方法:
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object instance) throws IOException
{
Object value;
if (p.hasToken(JsonToken.VALUE_NULL)) {
if (_skipNulls) {
return;
}
value = _nullProvider.getNullValue(ctxt);
} else if (_valueTypeDeserializer == null) {
value = _valueDeserializer.deserialize(p, ctxt);
} else {
value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
try {
_setter.invoke(instance, value);
} catch (Exception e) {
_throwAsIOE(p, e, value);
}
}
该方法利用本参数的MethodProperty#_valueDeserializer
反序列化器,来执行反序列化操作,由于参数startTime
的反序列化器为DateDeserializer
,于是我们直接看其deserialize
方法:
public java.util.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return _parseDate(p, ctxt);
}
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
throws IOException
{
if (_customFormat != null) {
if (p.hasToken(JsonToken.VALUE_STRING)) {
String str = p.getText().trim();
if (str.length() == 0) {
return (Date) getEmptyValue(ctxt);
}
synchronized (_customFormat) {
try {
// 这行代码,是入口
return _customFormat.parse(str);
} catch (ParseException e) {
return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
"expected format \"%s\"", _formatString);
}
}
}
}
return super._parseDate(p, ctxt);
}
}
这里就会根据反序列化器的_customFormat
属性(生成反序列化器时会赋值)来对请求参数进行 Format 转换。
转换完之后的值,继续调用_setter.invoke(instance, value)
方法,通过反射对指定 set 方法进行调用,从而对参数进行赋值。
至此,@JsonFormat
实现原理解析完毕,总结下整体流程:
-
在
AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(HttpInputMessage, MethodParameter,Type)
读取请求参数的值时,调用AbstractJackson2HttpMessageConverter#canRead(Type, Class<?>, MediaType)
方法,对每个请求参数生成MethodPropery
对象,该对象包括参数名称,参数类型,注解信息,反序列化器等信息。 -
接着
AbstractJackson2HttpMessageConverter#read
方法,通过MethodPropery
对象信息,对参数的值进行转化,并使用反射对请求参数进行赋值。
指定TimeZone
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8")
private Timestamp ts;
上面的样例中,JsonFormat中,指定了timezone的值,这个值,其实是String字符串,这个值,是怎么被使用的呢?
我们看下,下面的源码:
// com.fasterxml.jackson.annotation.JsonFormat.Value#getTimeZone
public TimeZone getTimeZone() {
TimeZone tz = _timezone;
if (tz == null) {
if (_timezoneStr == null) {
return null;
}
tz = TimeZone.getTimeZone(_timezoneStr);
_timezone = tz;
}
return tz;
}
可以看到,这里通过调用TimeZone.getTimeZone
,将字符串类型的_timezoneStr
,转换成了TimeZone
类型
@DateTimeFormat
当我们的请求方法不是post,而是get方法时,在服务端使用实体类来接收数据。
@ApiOperation("查询告警记录列表")
@PreAuthorize("@ss.hasPermi('iot:record:list')")
@GetMapping("/list")
public TableDataInfo list(TAlarmRecord tAlarmRecord) {
这个TAlarmRecord实体类中,有2个LocalDate类型的字段,此时如果前端传递一个日期字符串,后端使用LocalDate类型字段接收,如果只使用@JsonFormat注解,会报错:
Failed to convert from type [java.lang.String] to type [@io.swagger.annotations.ApiModelProperty @com.fasterxml.jackson.annotation.JsonFormat java.time.LocalDate]
为了解决这个问题,引入另一个注解@DateTimeFormat
,如下:
@ApiModelProperty(value = "触发时间开始,格式yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd",shape = JsonFormat.Shape.STRING)
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate triggerTimeStart;
那么@JsonFormat 和 @DateTimeFormat他们之间是什么关系呢?
@DateTimeFormat:只能约束前端到后端的时间格式,不会影响后端到前端的时间格式
@JsonFormat:既可以约束前端传入的时间类型参数格式,也可以约束后端响应前端的时间类型格式;与请求的数据类型有关,必须为json类型
一般情况下,只需要使用@JsonFormat
即可,但是如果是get方法,且后端时间字段使用了LocalDateTime等类型,那么需要额外添加@DateTimeFormat字段