美文网首页
@DateTimeFormat 实现原理

@DateTimeFormat 实现原理

作者: habit_learning | 来源:发表于2019-06-08 17:43 被阅读0次

上一篇,我们探讨了@JsonFormat的实现原理,本篇我们来一起探讨和它功能一样的注解@DateTimeFormat的实现原理。

前端Content-Type 为application/json的请求时,我们使用@JsonFormat来进行转化,如果为表单,则应该使用@DateTimeFormat
现在我们修改下上一篇文章的栗子:
Controller层:

@RestController
public class HelloWorldController {

    @GetMapping("/hello-world")
    public QueryParams helloWorld(QueryParams queryParams){
        return queryParams;
    }
}

实体类QueryParams

public class QueryParams {

    /**
     * 开始时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH")
    private Date startTime;

    /**
     * 名称
     */
    private String name;

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

现在是典型的 form 表单接收参数的形式了,我们来看看@DateTimeFormat是如何实现对时间的转化的。

由于我们是表单接收参数的形式,于是在处理请求参数的逻辑是在ServletModelAttributeMethodProcessor#resolveArgument方法上:

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        String name = ModelFactory.getNameForParameter(parameter);
        ...
        Object attribute = null;
        BindingResult bindingResult = null;
        if (bindingResult == null) {
            // Bean property binding and validation;
            // skipped in case of binding failure on construction.
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    bindRequestParameters(binder, webRequest);
                }
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
            // Value type adaptation, also covering java.util.Optional
            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }
            bindingResult = binder.getBindingResult();
        }

        // Add resolved attribute and BindingResult at the end of the model
        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return attribute;
    }

对请求参数进行绑定的操作是在bindRequestParameters(binder, webRequest)方法,于是我们直接看ServletModelAttributeMethodProcessor#bindRequestParameters方法:

protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
        Assert.state(servletRequest != null, "No ServletRequest");
        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
        servletBinder.bind(servletRequest);
    }

public void bind(ServletRequest request) {
        MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
        if (multipartRequest != null) {
            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
        }
        addBindValues(mpvs, request);
        doBind(mpvs);
    }

该方法会把请求参数的名称以及值封装为MutablePropertyValues对象,此时MutablePropertyValues的值是转化前的。然后调用doBind(mpvs)方法进行绑定:

public class WebDataBinder extends DataBinder {

protected void doBind(MutablePropertyValues mpvs) {
        checkFieldDefaults(mpvs);
        checkFieldMarkers(mpvs);
        super.doBind(mpvs);
    }
}

public class DataBinder implements PropertyEditorRegistry, TypeConverter {
    protected void doBind(MutablePropertyValues mpvs) {
        checkAllowedFields(mpvs);
        checkRequiredFields(mpvs);
        applyPropertyValues(mpvs);
    }
}

经过一系列对参数进行校验之后,调用applyPropertyValues(mpvs)方法根据MutablePropertyValues对象对请求参数进行赋值:

protected void applyPropertyValues(MutablePropertyValues mpvs) {
        try {
            // Bind request parameters onto target object.
            getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
        }
        catch (PropertyBatchUpdateException ex) {
            // Use bind error processor to create FieldErrors.
            for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
                getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
            }
        }
    }

protected ConfigurablePropertyAccessor getPropertyAccessor() {
        return getInternalBindingResult().getPropertyAccessor();
    }

首先获取ConfigurablePropertyAccessor配置属性访问类,我们直接看getInternalBindingResult()方法:

public class DataBinder implements PropertyEditorRegistry, TypeConverter {

    private final Object target;

    private final String objectName;

    @Nullable
    private AbstractPropertyBindingResult bindingResult;  

    ...  

    protected AbstractPropertyBindingResult getInternalBindingResult() {
        if (this.bindingResult == null) {
            initBeanPropertyAccess();
        }
        return this.bindingResult;
    }

    public void initBeanPropertyAccess() {
        Assert.state(this.bindingResult == null,
                "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
        this.bindingResult = createBeanPropertyBindingResult();
    }

    protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
        BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),
                getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());

        if (this.conversionService != null) {
            result.initConversion(this.conversionService);
        }
        if (this.messageCodesResolver != null) {
            result.setMessageCodesResolver(this.messageCodesResolver);
        }

        return result;
    }
}

首先判断DataBinder#bindingResult是否有值,如果没有则初始化一个。一开始DataBinder#bindingResult是没赋值的,只知道target请求参数对象即QueryParamsobjectName请求参数对象实例为queryParams。于是会生成一个BeanPropertyBindingResult,然后调用BeanPropertyBindingResult#getPropertyAccessor方法ConfigurablePropertyAccessor配置属性访问类:

public final ConfigurablePropertyAccessor getPropertyAccessor() {
        if (this.beanWrapper == null) {
            this.beanWrapper = createBeanWrapper();
            this.beanWrapper.setExtractOldValueForEditor(true);
            this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
            this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
        }
        return this.beanWrapper;
    }

该方法生成了BeanWrapperImpl对象,作为配置属性方法类,然后调用其父类AbstractPropertyAccessor#setPropertyValues(propertyValues, ignoreUnknown, ignoreInvalid)方法给属性赋值:

public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
            throws BeansException {

        List<PropertyAccessException> propertyAccessExceptions = null;
        List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
                ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
        for (PropertyValue pv : propertyValues) {
            try {
                // This method may throw any BeansException, which won't be caught
                // here, if there is a critical failure such as no matching field.
                // We can attempt to deal only with less serious exceptions.
                setPropertyValue(pv);
            }
            catch (NotWritablePropertyException ex) {
                if (!ignoreUnknown) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            }
            catch (NullValueInNestedPathException ex) {
                if (!ignoreInvalid) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            }
            catch (PropertyAccessException ex) {
                if (propertyAccessExceptions == null) {
                    propertyAccessExceptions = new LinkedList<>();
                }
                propertyAccessExceptions.add(ex);
            }
        }

        // If we encountered individual exceptions, throw the composite exception.
        if (propertyAccessExceptions != null) {
            PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
            throw new PropertyBatchUpdateException(paeArray);
        }
    }

该方法将PropertyValues对象转化为单个请求参数对象PropertyValue的集合,然后遍历该集合,逐个调用BeanWrapperImpl#setPropertyValue(pv)方法对请求参数进行赋值:

public void setPropertyValue(PropertyValue pv) throws BeansException {
        PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
        if (tokens == null) {
            String propertyName = pv.getName();
            AbstractNestablePropertyAccessor nestedPa;
            try {
                nestedPa = getPropertyAccessorForPropertyPath(propertyName);
            }
            catch (NotReadablePropertyException ex) {
                throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                        "Nested property in path '" + propertyName + "' does not exist", ex);
            }
            tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
            if (nestedPa == this) {
                pv.getOriginalPropertyValue().resolvedTokens = tokens;
            }
            nestedPa.setPropertyValue(tokens, pv);
        }
        else {
            setPropertyValue(tokens, pv);
        }
    }

首先会判断PropertyValue#resolvedTokens是否有值,显然一开始PropertyValue只有属性名称和属性值,没有resolvedTokens,于是会生成一个PropertyTokenHolder对象,该对象只保存属性名称,如果属性名称含有[]符号时,会拆分属性名称并赋值PropertyTokenHolder#keys。然后调用nestedPa.setPropertyValue(tokens, pv)方法进行参数赋值操作:

protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
        if (tokens.keys != null) {
            processKeyedProperty(tokens, pv);
        }
        else {
            processLocalProperty(tokens, pv);
        }
    }

private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
        ...
        Object oldValue = null;
        try {
            Object originalValue = pv.getValue();
            Object valueToApply = originalValue;
            if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                if (pv.isConverted()) {
                    valueToApply = pv.getConvertedValue();
                }
                else {
                    if (isExtractOldValueForEditor() && ph.isReadable()) {
                        try {
                            oldValue = ph.getValue();
                        }
                        catch (Exception ex) {
                            if (ex instanceof PrivilegedActionException) {
                                ex = ((PrivilegedActionException) ex).getException();
                            }
                            if (logger.isDebugEnabled()) {
                                logger.debug("Could not read previous value of property '" +
                                        this.nestedPath + tokens.canonicalName + "'", ex);
                            }
                        }
                    }
                    valueToApply = convertForProperty(
                            tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
                }
                pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
            }
            ph.setValue(valueToApply);
        }
        ...

    }

该方法会判断PropertyTokenHolder#keys是否有值,显然我们的参数没有[]符号,则直接走processLocalProperty(tokens, pv)方法,第一步,它会调用getLocalPropertyHandler(tokens.actualName)方法生成PropertyHandler属性处理器对象:

protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
        PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
        return (pd != null ? new BeanPropertyHandler(pd) : null);
    }

private CachedIntrospectionResults getCachedIntrospectionResults() {
        if (this.cachedIntrospectionResults == null) {
            this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
        }
        return this.cachedIntrospectionResults;
    }

public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {

    @Nullable
    Object wrappedObject;

    private String nestedPath = "";

    @Nullable
    Object rootObject;

public final Class<?> getWrappedClass() {
        return getWrappedInstance().getClass();
    }

public final Object getWrappedInstance() {
        Assert.state(this.wrappedObject != null, "No wrapped object");
        return this.wrappedObject;
    }
}

首先调用getWrappedClass()获取封装实体类 Class,即QueryParams的 Class,然后调用CachedIntrospectionResults.forClass(getWrappedClass())方法,生成CachedIntrospectionResults缓存的内省结果:

public class CachedIntrospectionResults {
    /** The BeanInfo object for the introspected bean class */
    private final BeanInfo beanInfo;

    /** PropertyDescriptor objects keyed by property name String */
    private final Map<String, PropertyDescriptor> propertyDescriptorCache;

    static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
        CachedIntrospectionResults results = strongClassCache.get(beanClass);
        if (results != null) {
            return results;
        }
        results = softClassCache.get(beanClass);
        if (results != null) {
            return results;
        }

        results = new CachedIntrospectionResults(beanClass);
        ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;

        if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
                isClassLoaderAccepted(beanClass.getClassLoader())) {
            classCacheToUse = strongClassCache;
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
            }
            classCacheToUse = softClassCache;
        }

        CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
        return (existing != null ? existing : results);
    }

    private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
        try {
            ...
            this.propertyDescriptorCache = new LinkedHashMap<>();

            // This call is slow so we do it once.
            PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor pd : pds) {
                if (Class.class == beanClass &&
                        ("classLoader".equals(pd.getName()) ||  "protectionDomain".equals(pd.getName()))) {
                    // Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those
                    continue;
                }
            
                pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
                this.propertyDescriptorCache.put(pd.getName(), pd);
            }
      ...
    }

该方法把QueryParams类的 Class 对象传给CachedIntrospectionResults的构造器,从而生成CachedIntrospectionResults对象,同时会取该对象的GenericBeanInfo#properties的值,其类型是PropertyDescriptor对象集合,保存了属性的名称以及getset 方法。
然后遍历PropertyDescriptor集合,通过buildGenericTypeAwarePropertyDescriptor(beanClass, pd)方法,为每个属性生成一个GenericTypeAwarePropertyDescriptor对象,并放入属性描述器缓存CachedIntrospectionResults#propertyDescriptorCache,其 key 为参数名称,value 为 GenericTypeAwarePropertyDescriptor对象。

拿到CachedIntrospectionResults对象之后,接着调用CachedIntrospectionResults#getPropertyDescriptor方法,来获取PropertyDescriptor对象:

PropertyDescriptor getPropertyDescriptor(String name) {
        PropertyDescriptor pd = this.propertyDescriptorCache.get(name);
        if (pd == null && StringUtils.hasLength(name)) {
            // Same lenient fallback checking as in Property...
            pd = this.propertyDescriptorCache.get(StringUtils.uncapitalize(name));
            if (pd == null) {
                pd = this.propertyDescriptorCache.get(StringUtils.capitalize(name));
            }
        }
        return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
                buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd));
    }

直接根据属性名称从属性描述器缓存中取PropertyDescriptor,显然我们上面已经保存了,于是直接拿到的是GenericTypeAwarePropertyDescriptor对象。然后把GenericTypeAwarePropertyDescriptor对象作为BeanPropertyHandler构造器的参数,生成BeanPropertyHandler对象,从而完成AbstractNestablePropertyAccessor#processLocalProperty的第一步操作。

然后就可以对参数进行转换操作了AbstractNestablePropertyAccessor#convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor())tokens.canonicalName就是属性名,oldValue为开启了安全管理器SecurityManager之后才有的值,我们默认是没有开启的,所以为nulloriginalValue为属性原值(转换前的),那么我们看ph.toTypeDescriptor()方法:

public TypeDescriptor toTypeDescriptor() {
            return new TypeDescriptor(property(this.pd));
        }

private Property property(PropertyDescriptor pd) {
        GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
        return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
    }

public TypeDescriptor(Property property) {
        Assert.notNull(property, "Property must not be null");
        this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter());
        this.type = this.resolvableType.resolve(property.getType());
        this.annotatedElement = new AnnotatedElementAdapter(property.getAnnotations());
    }

这里根据BeanPropertyHandler#pd属性PropertyDescriptor对象的值,有请求参数所在类 Class,请求参数getset方法,请求参数名称,生成Property对象,然后根据Property对象生成TypeDescriptor类型描述器对象,我们主要看property.getAnnotations()方法:

public final class Property {

    private static Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();

    private final Class<?> objectType;

    @Nullable
    private final Method readMethod;

    @Nullable
    private final Method writeMethod;

    private final String name;

    private final MethodParameter methodParameter;

    @Nullable
    private Annotation[] annotations;

    Annotation[] getAnnotations() {
        if (this.annotations == null) {
            this.annotations = resolveAnnotations();
        }
        return this.annotations;
    }

    private Annotation[] resolveAnnotations() {
        Annotation[] annotations = annotationCache.get(this);
        if (annotations == null) {
            Map<Class<? extends Annotation>, Annotation> annotationMap = new LinkedHashMap<>();
            addAnnotationsToMap(annotationMap, getReadMethod());
            addAnnotationsToMap(annotationMap, getWriteMethod());
            addAnnotationsToMap(annotationMap, getField());
            annotations = annotationMap.values().toArray(new Annotation[0]);
            annotationCache.put(this, annotations);
        }
        return annotations;
    }
}

由于刚才创建的Property对象没有给annotations赋值,于是直接走resolveAnnotations方法,该方法会需找参数字段以及getset方法上的注解信息,并赋值给Property#annotations。于是startTime字段上的@DateTimeFormat注解信息存入了该Property对象中。随后将注解信息赋值给TypeDescriptor对象。

我们继续看AbstractNestablePropertyAccessor#convertForProperty方法:

protected Object convertForProperty(
            String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td)
            throws TypeMismatchException {

        return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
    }

private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue,
            @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td)
            throws TypeMismatchException {

        Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
        try {
            return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
        }
        catch (ConverterNotFoundException | IllegalStateException ex) {
            PropertyChangeEvent pce =
                    new PropertyChangeEvent(getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
            throw new ConversionNotSupportedException(pce, requiredType, ex);
        }
        catch (ConversionException | IllegalArgumentException ex) {
            PropertyChangeEvent pce =
                    new PropertyChangeEvent(getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
            throw new TypeMismatchException(pce, requiredType, ex);
        }

public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
            @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

        // Custom editor for this type?
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

        ConversionFailedException conversionAttemptEx = null;

        // No custom editor but custom ConversionService specified?
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
                catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    conversionAttemptEx = ex;
                }
            }
        }

        Object convertedValue = newValue;
        ...
        return convertedValue;
    }

如果conversionService.canConvert(sourceTypeDesc, typeDescriptor)条件成立,即时间格式能被转化,则执行转化操作conversionService.convert(newValue, sourceTypeDesc, typeDescriptor)newValue就是要转化的值,sourceTypeDesc为要转化值的类型,这里是StringtypeDescriptor就是上面生成含有@DateTimeFormat注解信息的TypeDescriptor类型描述器。

转化完成之后,则执行AbstractNestablePropertyAccessor#processLocalProperty方法的第三步,给PropertyHandler赋值ph.setValue(valueToApply)

public void setValue(final @Nullable Object value) throws Exception {
            final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
                    ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
                    this.pd.getWriteMethod());
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    ReflectionUtils.makeAccessible(writeMethod);
                    return null;
                });
                try {
                    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
                            writeMethod.invoke(getWrappedInstance(), value), acc);
                }
                catch (PrivilegedActionException ex) {
                    throw ex.getException();
                }
            }
            else {
                ReflectionUtils.makeAccessible(writeMethod);
                writeMethod.invoke(getWrappedInstance(), value);
            }
        }

显然,这里用反射机制,调用参数的set方法,从而将转化后的值赋值给参数。

至此,@DateTimeFormat 实现原理已经讲解完毕,让我们来总结一下:

  1. 请求参数的名称以及值封装为MutablePropertyValues对象。
  2. 获取ConfigurablePropertyAccessor配置属性访问类,根据MutablePropertyValues对象生成PropertyValue对象集合。
  3. 遍历PropertyValue对象集合,执行设置操作。
  4. 为每个请求参数生成BeanPropertyHandlerBea属性处理器对象。
  5. 获取请求参数上的注解信息,并生成TypeDescriptor(含有注解信息)。
  6. 根据生成的TypeDescriptor类型描述器对请求参数进行转化,随后利用反射机制执行参数的set方法,从而对参数进行赋值。

相关文章

网友评论

      本文标题:@DateTimeFormat 实现原理

      本文链接:https://www.haomeiwen.com/subject/xggixctx.html