上一篇,我们探讨了@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
请求参数对象即QueryParams
,objectName
请求参数对象实例为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
对象集合,保存了属性的名称以及get
、set
方法。
然后遍历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
之后才有的值,我们默认是没有开启的,所以为null
,originalValue
为属性原值(转换前的),那么我们看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,请求参数get
、set
方法,请求参数名称,生成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
方法,该方法会需找参数字段以及get
、set
方法上的注解信息,并赋值给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
为要转化值的类型,这里是String
,typeDescriptor
就是上面生成含有@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
实现原理已经讲解完毕,让我们来总结一下:
- 请求参数的名称以及值封装为
MutablePropertyValues
对象。 - 获取
ConfigurablePropertyAccessor
配置属性访问类,根据MutablePropertyValues
对象生成PropertyValue
对象集合。 - 遍历
PropertyValue
对象集合,执行设置操作。 - 为每个请求参数生成
BeanPropertyHandler
Bea属性处理器对象。 - 获取请求参数上的注解信息,并生成
TypeDescriptor
(含有注解信息)。 - 根据生成的
TypeDescriptor
类型描述器对请求参数进行转化,随后利用反射机制执行参数的set
方法,从而对参数进行赋值。