Administrator
发布于 2023-02-14 / 71 阅读
0
0

Date Type Field---Json Format Annotation

@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来接收请求参数的,于是HandlerMethodArgumentResolverRequestResponseBodyMethodProcessor;如果是表单接收,则是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中,只有MappingJackson2HttpMessageConverterGenericHttpMessageConverter,于是我们直接看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);
        ...
    }

上面的流程图,如下:

image-20230213161707524

image-20230213161740663

image-20230213161803604

image-20230213161819595

以下是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集合,SettableBeanPropertyMethodProperty的父类。然后调用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,故进入了判断条件。

image-20230213161036367

关键部分

随后通过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注解,以便解析,由于我们的propertySettableBeanProperty类型,而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参数的反序列化过程,propMethodProperty,我们看其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实现原理解析完毕,总结下整体流程:

  1. AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(HttpInputMessage, MethodParameter,Type)读取请求参数的值时,调用AbstractJackson2HttpMessageConverter#canRead(Type, Class<?>, MediaType)方法,对每个请求参数生成MethodPropery对象,该对象包括参数名称,参数类型,注解信息,反序列化器等信息。

  2. 接着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字段


评论