聊聊Spring中的数据绑定DataBinder等
数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容错性以及更高的编码效率。它提供的能力是:把字符串形式的参数转换成服务端真正需要的类型的转换(当然可能还包含校验)。
DataBinder
此类所在的包是org.springframework.validation,所以可想而知,它不仅仅完成数据的绑定,还会和数据校验有关。
DataBinder的继承树:
先看一个简单Demo,体验一把直接使用DataBinder进行数据绑定:
public static void main(String[] args) throws BindException {
Person person = new Person();
DataBinder binder = new DataBinder(person, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "fsx");
pvs.add("age", 18);
binder.bind(pvs);
Map<?, ?> close = binder.close();
System.out.println(person);
System.out.println(close);
}
源码分析
public class DataBinder implements PropertyEditorRegistry, TypeConverter {}
它是个实现类,直接实现了PropertyEditorRegistry和TypeConverter这两个接口,因此它可以注册java.beans.PropertyEditor,并且能完成类型转换(TypeConverter)。
具体源码如下:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/** Default object name used for binding: "target". */
public static final String DEFAULT_OBJECT_NAME = "target";
/** Default limit for array and collection growing: 256. */
public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
@Nullable
private final Object target;
private final String objectName; // 默认值是target
// BindingResult:绑定错误、失败的时候会放进这里来~
@Nullable
private AbstractPropertyBindingResult bindingResult;
//类型转换器,会注册最为常用的那么多类型转换Map<Class<?>, PropertyEditor> defaultEditors
@Nullable
private SimpleTypeConverter typeConverter;
// 默认忽略不能识别的字段~
private boolean ignoreUnknownFields = true;
// 不能忽略非法的字段(比如我要Integer,你给传aaa,那肯定就不让绑定了,抛错)
private boolean ignoreInvalidFields = false;
// 默认是支持级联的~~~
private boolean autoGrowNestedPaths = true;
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
// 这三个参数 都可以自己指定~~ 允许的字段、不允许的、必须的
@Nullable
private String[] allowedFields;
@Nullable
private String[] disallowedFields;
@Nullable
private String[] requiredFields;
// 转换器ConversionService
@Nullable
private ConversionService conversionService;
// 状态码处理器~
@Nullable
private MessageCodesResolver messageCodesResolver;
// 绑定出现错误的处理器~
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
// 校验器(这个非常重要)
private final List<Validator> validators = new ArrayList<>();
// objectName没有指定,就用默认的
public DataBinder(@Nullable Object target) {
this(target, DEFAULT_OBJECT_NAME);
}
public DataBinder(@Nullable Object target, String objectName) {
this.target = ObjectUtils.unwrapOptional(target);
this.objectName = objectName;
}
... // 省略所有属性的get/set方法
// 提供一些列的初始化方法,供给子类使用 或者外部使用 下面两个初始化方法是互斥的
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;
}
// 你会发现,初始化DirectFieldAccess的时候,校验的也是bindingResult ~~~~
public void initDirectFieldAccess() {
Assert.state(this.bindingResult == null, "DataBinder is already initialized - call initDirectFieldAccess before other configuration methods");
this.bindingResult = createDirectFieldBindingResult();
}
protected AbstractPropertyBindingResult createDirectFieldBindingResult() {
DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths());
if (this.conversionService != null) {
result.initConversion(this.conversionService);
}
if (this.messageCodesResolver != null) {
result.setMessageCodesResolver(this.messageCodesResolver);
}
return result;
}
...
// 把属性访问器返回,PropertyAccessor(默认直接从结果里拿),子类MapDataBinder有复写
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
// 可以看到简单的转换器也是使用到了conversionService的,可见conversionService它的效用
protected SimpleTypeConverter getSimpleTypeConverter() {
if (this.typeConverter == null) {
this.typeConverter = new SimpleTypeConverter();
if (this.conversionService != null) {
this.typeConverter.setConversionService(this.conversionService);
}
}
return this.typeConverter;
}
... // 省略众多get方法
// 设置指定的可以绑定的字段,默认是所有字段~~~
// 例如,在绑定HTTP请求参数时,限制这一点以避免恶意用户进行不必要的修改。
// 简单的说:我可以控制只有指定的一些属性才允许你修改~~~~
// 注意:它支持xxx*,*xxx,*xxx*这样的通配符 支持[]这样子来写~
public void setAllowedFields(@Nullable String... allowedFields) {
this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);
}
public void setDisallowedFields(@Nullable String... disallowedFields) {
this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
}
// 注册每个绑定进程所必须的字段。
public void setRequiredFields(@Nullable String... requiredFields) {
this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);
if (logger.isDebugEnabled()) {
logger.debug("DataBinder requires binding of required fields [" + StringUtils.arrayToCommaDelimitedString(requiredFields) + "]");
}
}
...
// 注意:这个是set方法,后面是有add方法的~
// 注意:虽然是set,但是引用是木有变的~~~~
public void setValidator(@Nullable Validator validator) {
// 判断逻辑在下面:你的validator至少得支持这种类型呀 哈哈
assertValidators(validator);
// 因为自己手动设置了,所以先清空 再加进来~~~
// 这步你会发现,即使validator是null,也是会clear的哦~ 符合语意
this.validators.clear();
if (validator != null) {
this.validators.add(validator);
}
}
private void assertValidators(Validator... validators) {
Object target = getTarget();
for (Validator validator : validators) {
if (validator != null && (target != null && !validator.supports(target.getClass()))) {
throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + target);
}
}
}
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
// 效果同set
public void replaceValidators(Validator... validators) {
assertValidators(validators);
this.validators.clear();
this.validators.addAll(Arrays.asList(validators));
}
// 返回一个,也就是primary默认的校验器
@Nullable
public Validator getValidator() {
return (!this.validators.isEmpty() ? this.validators.get(0) : null);
}
// 只读视图
public List<Validator> getValidators() {
return Collections.unmodifiableList(this.validators);
}
// since Spring 3.0
public void setConversionService(@Nullable ConversionService conversionService) {
Assert.state(this.conversionService == null, "DataBinder is already initialized with ConversionService");
this.conversionService = conversionService;
if (this.bindingResult != null && conversionService != null) {
this.bindingResult.initConversion(conversionService);
}
}
// =============下面它提供了非常多的addCustomFormatter()方法 注册进PropertyEditorRegistry里=====================
public void addCustomFormatter(Formatter<?> formatter);
public void addCustomFormatter(Formatter<?> formatter, String... fields);
public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes);
// 实现接口方法
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String field, PropertyEditor propertyEditor);
...
// 实现接口方法
// 统一委托给持有的TypeConverter~~或者是getInternalBindingResult().getPropertyAccessor();这里面的
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
// ===========上面的方法都是开胃小菜,下面才是本类最重要的方法==============
// 该方法就是把提供的属性值们,绑定到目标对象target里去~~~
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
doBind(mpvs);
}
// 此方法是protected的,子类WebDataBinder有复写~~~加强了一下
protected void doBind(MutablePropertyValues mpvs) {
// 前面两个check就不解释了,重点看看applyPropertyValues(mpvs)这个方法~
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
// allowe允许的 并且还是没有在disallowed里面的 这个字段就是被允许的
protected boolean isAllowed(String field) {
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
}
...
// protected 方法,给target赋值~~~~
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// 可以看到最终赋值 是委托给PropertyAccessor去完成的
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
// 抛出异常,交给BindingErrorProcessor一个个处理~~~
} catch (PropertyBatchUpdateException ex) {
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
// 执行校验,此处就和BindingResult 关联上了,校验失败的消息都会放进去(不是直接抛出异常哦~ )
public void validate() {
Object target = getTarget();
Assert.state(target != null, "No target to validate");
BindingResult bindingResult = getBindingResult();
// 每个Validator都会执行~~~~
for (Validator validator : getValidators()) {
validator.validate(target, bindingResult);
}
}
// 带有校验提示的校验器。SmartValidator
// @since 3.1
public void validate(Object... validationHints) { ... }
// 这一步也挺有意思:实际上就是若有错误,就抛出异常
// 若没错误 就把绑定的Model返回~~~(可以看到BindingResult里也能拿到最终值哦~~~)
// 此方法可以调用,但一般较少使用~
public Map<?, ?> close() throws BindException {
if (getBindingResult().hasErrors()) {
throw new BindException(getBindingResult());
}
return getBindingResult().getModel();
}
}
从源码的分析中,大概能总结到DataBinder它提供了如下能力:
- 把属性值PropertyValues绑定到target上(bind()方法,依赖于PropertyAccessor实现~)
- 提供校验的能力:提供了public方法validate()对各个属性使用Validator执行校验~
- 提供了注册属性编辑器(PropertyEditor)和对类型进行转换的能力(TypeConverter)
需要注意的是:
- initBeanPropertyAccess和initDirectFieldAccess两个初始化PropertyAccessor方法是互斥的
- initBeanPropertyAccess()创建的是BeanPropertyBindingResult,内部依赖BeanWrapper
- initDirectFieldAccess创建的是DirectFieldBindingResult,内部依赖DirectFieldAccessor
- 这两个方法内部没有显示的调用,但是Spring内部默认使用的是initBeanPropertyAccess(),具体可以参照getInternalBindingResult()方法
WebDataBinder
实际应用中的数据绑定常用的是WebDataBinder。
它的作用就是从web request里(注意:这里指的web请求,并不一定就是ServletRequest请求哟)把web请求的parameters绑定到JavaBean上。
Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型。若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参数。
// @since 1.2
public class WebDataBinder extends DataBinder {
// 此字段意思是:字段标记 比如name -> _name
// 这对于HTML复选框和选择选项特别有用。
public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
// !符号是处理默认值的,提供一个默认值代替空值~~~
public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
@Nullable
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
@Nullable
private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
// 默认也会绑定空的文件流~
private boolean bindEmptyMultipartFiles = true;
// 完全沿用父类的两个构造~~~
public WebDataBinder(@Nullable Object target) {
super(target);
}
public WebDataBinder(@Nullable Object target, String objectName) {
super(target, objectName);
}
... // 省略get/set
// 在父类的基础上,增加了对_和!的处理~~~
@Override
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
protected void checkFieldDefaults(MutablePropertyValues mpvs) {
String fieldDefaultPrefix = getFieldDefaultPrefix();
if (fieldDefaultPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
// 若你给定的PropertyValue的属性名确实是以!打头的 那就做处理如下:
// 如果JavaBean的该属性可写 && mpvs不存在去掉!后的同名属性,那就添加进来表示后续可以使用了(毕竟是默认值,没有精确匹配的高的)
// 然后把带!的给移除掉(因为默认值以已经转正了~~~)
// 其实这里就是说你可以使用!来给个默认值。比如!name表示若找不到name这个属性的时,就取它的值~~~
// 也就是说你request里若有穿!name保底,也就不怕出现null值啦~
if (pv.getName().startsWith(fieldDefaultPrefix)) {
String field = pv.getName().substring(fieldDefaultPrefix.length());
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
mpvs.add(field, pv.getValue());
}
mpvs.removePropertyValue(pv);
}
}
}
}
// 处理_的步骤
// 若传入的字段以_打头
// JavaBean的这个属性可写 && mpvs木有去掉_后的属性名字
// getEmptyValue(field, fieldType)就是根据Type类型给定默认值。
// 比如Boolean类型默认给false,数组给空数组[],集合给空集合,Map给空map 可以参考此类:CollectionFactory
// 当然,这一切都是建立在你传的属性值是以_打头的基础上的,Spring才会默认帮你处理这些默认值
protected void checkFieldMarkers(MutablePropertyValues mpvs) {
String fieldMarkerPrefix = getFieldMarkerPrefix();
if (fieldMarkerPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
if (pv.getName().startsWith(fieldMarkerPrefix)) {
String field = pv.getName().substring(fieldMarkerPrefix.length());
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
mpvs.add(field, getEmptyValue(field, fieldType));
}
mpvs.removePropertyValue(pv);
}
}
}
}
// @since 5.0
@Nullable
public Object getEmptyValue(Class<?> fieldType) {
try {
if (boolean.class == fieldType || Boolean.class == fieldType) {
// Special handling of boolean property.
return Boolean.FALSE;
} else if (fieldType.isArray()) {
// Special handling of array property.
return Array.newInstance(fieldType.getComponentType(), 0);
} else if (Collection.class.isAssignableFrom(fieldType)) {
return CollectionFactory.createCollection(fieldType, 0);
} else if (Map.class.isAssignableFrom(fieldType)) {
return CollectionFactory.createMap(fieldType, 0);
}
} catch (IllegalArgumentException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to create default value - falling back to null: " + ex.getMessage());
}
}
// 若不在这几大类型内,就返回默认值null呗~~~
// 但需要说明的是,若你是简单类型比如int,
// Default value: null.
return null;
}
// 单独提供的方法,用于绑定org.springframework.web.multipart.MultipartFile类型的数据到JavaBean属性上~
// 显然默认是允许MultipartFile作为Bean一个属性 参与绑定的
// Map<String, List<MultipartFile>>它的key,一般来说就是文件们啦~
protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
multipartFiles.forEach((key, values) -> {
if (values.size() == 1) {
MultipartFile value = values.get(0);
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
mpvs.add(key, value);
}
}
else {
mpvs.add(key, values);
}
});
}
}
单从WebDataBinder来说,它对父类进行了增强,提供的增强能力如下:
- 支持对属性名以 _ 打头的默认值处理(自动挡,能够自动处理所有的Bool、Collection、Map等)
- 支持对属性名以 ! 打头的默认值处理(手动档,需要手动给某个属性赋默认值,自己控制的灵活性很高)
- 提供方法,支持把MultipartFile绑定到JavaBean的属性上~
Demo示例
@Getter
@Setter
@ToString
public class Person {
public String name;
public Integer age;
// 基本数据类型
public Boolean flag;
public int index;
public List<String> list;
public Map<String, String> map;
}
演示使用 ! 手动精确控制字段的默认值:
public static void main(String[] args) {
Person person = new Person();
WebDataBinder binder = new WebDataBinder(person, "person");
// 设置属性(此处演示一下默认值)
MutablePropertyValues pvs = new MutablePropertyValues();
// 使用!来模拟各个字段手动指定默认值
//pvs.add("name", "fsx");
pvs.add("!name", "不知火舞");
pvs.add("age", 18);
pvs.add("!age", 10); // 上面有确切的值了,默认值不会再生效
binder.bind(pvs);
System.out.println(person);
//Person(name=不知火舞, age=18, flag=null, index=0, list=null, map=null)
}
ServletRequestDataBinder
目标是从Servlet Request里把参数绑定到JavaBean里,支持multipart。
到此类为止就已经把web请求限定为了ServletRequest,和Servlet规范强绑定了。
public class ServletRequestDataBinder extends WebDataBinder {
... // 沿用父类构造
// 注意这个可不是父类的方法,是本类增强的~~~~意思就是kv都从request里来~~当然内部还是适配成了一个MutablePropertyValues
public void bind(ServletRequest request) {
// 内部最核心方法是它:WebUtils.getParametersStartingWith() 把request参数转换成一个Map
// request.getParameterNames()
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
// 调用父类的bindMultipart方法,把MultipartFile都放进MutablePropertyValues里去~~~
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
// 这个方法是本类流出来的一个扩展点~~~子类可以复写此方法自己往里继续添加
// 比如ExtendedServletRequestDataBinder它就复写了这个方法,进行了增强(下面会说) 支持到了uriTemplateVariables的绑定
addBindValues(mpvs, request);
doBind(mpvs);
}
// 这个方法和父类的close方法类似,很少直接调用
public void closeNoCatch() throws ServletRequestBindingException {
if (getBindingResult().hasErrors()) {
throw new ServletRequestBindingException("Errors binding onto object '" + getBindingResult().getObjectName() + "'", new BindException(getBindingResult()));
}
}
}
下面就以MockHttpServletRequest为例作为Web 请求实体,演示一个使用的小Demo。说明:MockHttpServletRequest它是HttpServletRequest的实现类~
public static void main(String[] args) {
Person person = new Person();
ServletRequestDataBinder binder = new ServletRequestDataBinder(person, "person");
// 构造参数,此处就不用MutablePropertyValues,以HttpServletRequest的实现类MockHttpServletRequest为例吧
MockHttpServletRequest request = new MockHttpServletRequest();
// 模拟请求参数
request.addParameter("name", "fsx");
request.addParameter("age", "18");
// flag不仅仅可以用true/false 用0和1也是可以的?
request.addParameter("flag", "1");
// 设置多值的
request.addParameter("list", "4", "2", "3", "1");
// 给map赋值(Json串)
// request.addParameter("map", "{'key1':'value1','key2':'value2'}"); // 这样可不行
request.addParameter("map['key1']", "value1");
request.addParameter("map['key2']", "value2");
//一次性设置多个值(传入Map)
//request.setParameters(new HashMap<String, Object>() {{
// put("name", "fsx");
// put("age", "18");
//}});
binder.bind(request);
System.out.println(person);
//Person(name=fsx, age=18, flag=true, index=0, list=[4, 2, 3, 1], map={key1=value1, key2=valu
}
ExtendedServletRequestDataBinder
它是对ServletRequestDataBinder的一个增强,它用于把URI template variables参数添加进来用于绑定。它会去从request的HandlerMapping.class.getName() + ".uriTemplateVariables";这个属性里查找到值出来用于绑定~~~
比如我们熟悉的@PathVariable它就和这相关:它负责把参数从url模版中解析出来,然后放在attr上,最后交给ExtendedServletRequestDataBinder进行绑定~~~
AbstractUrlHandlerMapping.lookupHandler() --> chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); --> preHandle()方法 -> exposeUriTemplateVariables(this.uriTemplateVariables, request); -> request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
// @since 3.1
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
... // 沿用父类构造
//本类的唯一方法
@Override
@SuppressWarnings("unchecked")
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
// 它的值是:HandlerMapping.class.getName() + ".uriTemplateVariables";
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
// 注意:此处是attr,而不是parameter
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
if (uriVars != null) {
uriVars.forEach((name, value) -> {
// 若已经存在确切的key了,不会覆盖~~~~
if (mpvs.contains(name)) {
if (logger.isWarnEnabled()) {
logger.warn("Skipping URI variable '" + name + "' because request contains bind value with same name.");
}
} else {
mpvs.addPropertyValue(name, value);
}
});
}
}
}
可见,通过它我们亦可以很方便的做到在每个ServletRequest提供一份共用的模版属性们,供以绑定~
说明:ServletRequestDataBinder一般不会直接使用,而是使用更强的子类ExtendedServle
tRequestDataBinder
WebExchangeDataBinder
它是Spring5.0后提供的,对Reactive编程的Mono数据绑定提供支持。
MapDataBinder
它位于org.springframework.data.web是和Spring-Data相关,专门用于处理target是Map<String, Object>类型的目标对象的绑定,它并非一个public类~
用的属性访问器是MapPropertyAccessor:一个继承自AbstractPropertyAccessor的私有静态内部类~(也支持到了SpEL)
WebRequestDataBinder
它是用于处理Spring自己定义的org.springframework.web.context.request.WebRequest的,旨在处理和容器无关的web请求数据绑定。
注册自己的PropertyEditor来实现自定义类型数据绑定
通过前面的分析我们知道了,数据绑定这一块最终会依托于PropertyEditor来实现具体属性值的转换。
一般来说,像String, int, long会自动绑定到参数都是能够自动完成绑定的,因为前面有说,默认情况下Spring是给我们注册了N多个解析器的:
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
@Nullable
private Map<Class<?>, PropertyEditor> defaultEditors;
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
...
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
...
// 这里就部全部枚举出来了
}
}
虽然默认注册支持的Editor众多,但是依旧发现它并没有对Date类型、以及Jsr310提供的各种事件、日期类型的转换(当然也包括我们的自定义类型)。
@Getter
@Setter
@ToString
public class Person {
public String name;
public Integer age;
// 以Date类型为示例
private Date start;
private Date end;
private Date endTest;
}
public static void main(String[] args) {
Person person = new Person();
DataBinder binder = new DataBinder(person, "person");
// 设置属性
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "fsx");
// 事件类型绑定
pvs.add("start", new Date());
pvs.add("end", "2019-07-20");
// 试用试用标准的事件日期字符串形式~
pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019");
binder.bind(pvs);
System.out.println(person);
}
结果是符合我预期的:start有值,end没有,endTest却有值。
可能小伙伴对start、end都可以理解,最诧异的是endTest为何会有值呢?
此处我简单解释一下处理步骤:
- BeanWrapper调用setPropertyValue()给属性赋值,传入的value值都会交给convertForProperty()方法根据get方法的返回值类型进行转换~(比如此处为Date类型)
- 委托给this.typeConverterDelegate.convertIfNecessary进行类型转换(比如此处为string->Date类型)
- 先this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);找到一个合适的PropertyEditor(显然此处我们没有自定义Custom处理Date的PropertyEditor,返回null)
- 回退到使用ConversionService,显然此处我们也没有设置,返回null
- 回退到使用默认的editor = findDefaultEditor(requiredType);(注意:此处只根据类型去找了,因为上面说了默认不处理了Date,所以也是返回null)
- 最终回退到Spring对Array、Collection、Map的默认值处理问题,最终若是String类型,都会调用BeanUtils.instantiateClass(strCtor, convertedValue)也就是有参构造进行初始化~~~(请注意这必须是String类型才有的权利)
所以本例中,到最后一步就相当于new Date("Sat Jul 20 11:00:22 CST 2019"),因为该字符串是标准的时间日期串,所以是可以的,也就是endTest是能被正常赋值的~
其实通过回退到的最后一步处理,我们还可以对此做巧妙的应用。比如我给出如下的一个巧用例子:
@Getter
@Setter
@ToString
public class Person {
private String name;
// 备注:child是有有一个入参的构造器的
private Child child;
}
@Getter
@Setter
@ToString
public class Child {
private String name;
private Integer age;
public Child() {
}
public Child(String name) {
this.name = name;
}
}
public static void main(String[] args) {
Person person = new Person();
DataBinder binder = new DataBinder(person, "person");
// 设置属性
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "fsx");
// 给child赋值,其实也可以传一个字符串就行了 非常的方便 Spring会自动给我们new对象
pvs.add("child", "fsx-son");
binder.bind(pvs);
System.out.println(person);
}
下面我通过自定义属性编辑器的手段,来让能够支持处理上面我们传入2019-07-20 这种非标准的时间字符串。
我们知道DataBinder本身就是个PropertyEditorRegistry,因此我只需要自己注册一个自定义的PropertyEditor即可:
1、通过继承PropertyEditorSupport实现一个自己的处理Date的编辑器:
public class MyDatePropertyEditor extends PropertyEditorSupport {
private static final String PATTERN = "yyyy-MM-dd";
@Override
public String getAsText() {
Date date = (Date) super.getValue();
return new SimpleDateFormat(PATTERN).format(date);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
super.setValue(new SimpleDateFormat(PATTERN).parse(text));
} catch (ParseException e) {
System.out.println("ParseException....................");
}
}
}
2、注册进DataBinder并运行
public static void main(String[] args) {
Person person = new Person();
DataBinder binder = new DataBinder(person, "person");
binder.registerCustomEditor(Date.class, new MyDatePropertyEditor());
//binder.registerCustomEditor(Date.class, "end", new MyDatePropertyEditor(
// 设置属性
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "fsx
// 事件类型绑定
pvs.add("start", new Date());
pvs.add("end", "2019-07-20");
// 试用试用标准的事件日期字符串形式~
pvs.add("endTest", "Sat Jul 20 11:00:22 CST 2019
binder.bind(pvs);
System.out.println(person);
}
WebBindingInitializer和WebDataBinderFactory
WebBindingInitializer
WebBindingInitializer:实现此接口重写initBinder方法注册的属性编辑器是全局的属性编辑器,对所有的Controller都有效。
可以简单粗暴的理解为:WebBindingInitializer为编码方式,@InitBinder为注解方式(当然注解方式还能控制到只对当前Controller有效,实现更细粒度的控制)
// @since 2.5 Spring在初始化WebDataBinder时候的回调接口,给调用者自定义~
public interface WebBindingInitializer {
// @since 5.0
void initBinder(WebDataBinder binder);
// @deprecated as of 5.0 in favor of {@link #initBinder(WebDataBinder)}
@Deprecated
default void initBinder(WebDataBinder binder, WebRequest request) {
initBinder(binder);
}
}
此接口它的内建唯一实现类为:ConfigurableWebBindingInitializer,若你自己想要扩展,建议继承它~
public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
private boolean autoGrowNestedPaths = true;
private boolean directFieldAccess = false; // 显然这里是false
// 下面这些参数,不就是WebDataBinder那些可以配置的属性们吗?
@Nullable
private MessageCodesResolver messageCodesResolver;
@Nullable
private BindingErrorProcessor bindingErrorProcessor;
@Nullable
private Validator validator;
@Nullable
private ConversionService conversionService;
// 此处使用的PropertyEditorRegistrar来管理的,最终都会被注册进PropertyEditorRegistry嘛
@Nullable
private PropertyEditorRegistrar[] propertyEditorRegistrars;
... // 省略所有get/set
// 它做的事无非就是把配置的值都放进去而已~~
@Override
public void initBinder(WebDataBinder binder) {
binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
if (this.directFieldAccess) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
// 可以看到对校验器这块 内部还是做了容错的
if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
if (this.conversionService != null) {
binder.setConversionService(this.conversionService);
}
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
propertyEditorRegistrar.registerCustomEditors(binder);
}
}
}
}
此实现类主要是提供了一些可配置项,方便使用。注意:此接口一般不直接使用,而是结合InitBinderDataBinderFactory、WebDataBinderFactory等一起使用~
WebDataBinderFactory
顾名思义它就是来创造一个WebDataBinder的工厂。
// @since 3.1 注意:WebDataBinder 可是1.2就有了~
public interface WebDataBinderFactory {
// 此处使用的是Spring自己的NativeWebRequest 后面两个参数就不解释了
WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
}
它的继承树如下:
DefaultDataBinderFactory
public class DefaultDataBinderFactory implements WebDataBinderFactory {
@Nullable
private final WebBindingInitializer initializer;
// 注意:这是唯一构造函数
public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {
this.initializer = initializer;
}
// 实现接口的方法
@Override
@SuppressWarnings("deprecation")
public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
// 可见WebDataBinder 创建好后,此处就会回调(只有一个)
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
// 空方法 子类去实现,比如InitBinderDataBinderFactory实现了词方法
initBinder(dataBinder, webRequest);
return dataBinder;
}
// 子类可以复写,默认实现是WebRequestDataBinder
// 比如子类ServletRequestDataBinderFactory就复写了,使用的new ExtendedServletRequestDataBinder(target, objectName)
protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception
return new WebRequestDataBinder(target, objectName);
}
}
InitBinderDataBinderFactory
它继承自DefaultDataBinderFactory,主要用于处理标注有@InitBinder的方法做初始绑定~
// @since 3.1
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
// 需要注意的是:`@InitBinder`可以标注N多个方法~ 所以此处是List
private final List<InvocableHandlerMethod> binderMethods;
// 此子类的唯一构造函数
public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
super(initializer);
this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
}
// 上面知道此方法的调用方法生initializer.initBinder之后
// 所以使用注解它生效的时机是在直接实现接口的后面的~
@Override
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
// 判断@InitBinder是否对dataBinder持有的target对象生效~~~(根据name来匹配的)
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
// 关于目标方法执行这块,可以参考另外一篇@InitBinder的原理说明~
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
// 标注@InitBinder的方法不能有返回值
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
//@InitBinder有个Value值,它是个数组。它是用来匹配dataBinder.getObjectName()是否匹配的 若匹配上了,现在此注解方法就会生效
// 若value为空,那就对所有生效~~~
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
Assert.state(ann != null, "No InitBinder annotation");
String[] names = ann.value();
return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
}
}
ServletRequestDataBinderFactory
它继承自InitBinderDataBinderFactory,作用就更明显了。既能够处理@InitBinder,而且它使用的是更为强大的数据绑定器:ExtendedServletRequestDataBinder
// @since 3.1
public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
@Override
protected ServletRequestDataBinder createBinderInstance(
@Nullable Object target, String objectName, NativeWebRequest request) throws Exception {
return new ExtendedServletRequestDataBinder(target, objectName);
}
}
此工厂是RequestMappingHandlerAdapter这个适配器默认使用的一个数据绑定器工厂,而RequestMappingHandlerAdapter却又是当下使用得最频繁、功能最强大的一个适配器
属性访问器PropertyAccessor
Spring数据访问、绑定体系中一个非常重要的组成: 属性访问器(PropertyAccessor)。
属性访问器PropertyAccessor接口的作用是存/取Bean对象的属性。所有Spring创建的Bean对象都使用该接口存取Bean属性值
PropertyAccessor
它是可以访问命名属性named properties(例如对象的bean属性或对象中的字段)的类的公共接口。大名鼎鼎的BeanWrapper接口也继承自它,它所在包是org.springframework.beans(BeanWrapper也在此包)。
// @since 1.1 出现得非常早
public interface PropertyAccessor {
// 简单的说就是级联属性的分隔符。
// 比如foo.bar最终会调用getFoo().getBar()两个方法
String NESTED_PROPERTY_SEPARATOR = ".";
char NESTED_PROPERTY_SEPARATOR_CHAR = '.';
// 代表角标index的符号 如person.addresses[0] 这样就可以把值放进集合/数组/Map里了
String PROPERTY_KEY_PREFIX = "[";
char PROPERTY_KEY_PREFIX_CHAR = '[';
String PROPERTY_KEY_SUFFIX = "]";
char PROPERTY_KEY_SUFFIX_CHAR = ']';
// 此属性是否可读。若属性不存在 返回false
boolean isReadableProperty(String propertyName);
// 此出行是否可写。若属性不存在,返回false
boolean isWritableProperty(String propertyName);
// 读方法
@Nullable
Class<?> getPropertyType(String propertyName) throws BeansException;
@Nullable
TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;
@Nullable
Object getPropertyValue(String propertyName) throws BeansException;
// 写方法
void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
void setPropertyValue(PropertyValue pv) throws BeansException;
// 批量设置值
void setPropertyValues(Map<?, ?> map) throws BeansException;
// 说明:PropertyValues和PropertyValue关系特别像PropertySources和PropertySource的关系
void setPropertyValues(PropertyValues pvs) throws BeansException;
// 可控制是否接受非法的字段、value值扽 ignoreUnknown/ignoreInvalid分别对应非法属性和非法value值的处理策略~
void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException;
void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException;
}
它的继承树如下:
最终的实现类主要有DirectFieldAccessor和BeanWrapperImpl。
说明一下:DirectFieldAccessFallbackBeanWrapper它在spring-data-commons这个jar里面,所以若你没有使用spring-data-xxx是木有此实现类的~~~
ConfigurablePropertyAccessor
可配置的PropertyAccessor。它是一个子接口,提供了可配置的能力,并且它还继承了PropertyEditorRegistry、TypeConverter等接口。
// @since 2.0
public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {
// 设置一个ConversionService ,用于对value值进行转换
// 它是Spring3.0后推出来替代属性编辑器PropertyEditors的方案~
void setConversionService(@Nullable ConversionService conversionService);
@Nullable
ConversionService getConversionService();
// 设置在将属性编辑器应用于属性的新值时是**否提取旧属性值**。
void setExtractOldValueForEditor(boolean extractOldValueForEditor);
boolean isExtractOldValueForEditor();
// 设置此实例是否应尝试“自动增长”包含null的嵌套路径。
// true:为null的值会自动被填充为一个默认的value值,而不是抛出异常NullValueInNestedPathException
void setAutoGrowNestedPaths(boolean autoGrowNestedPaths);
boolean isAutoGrowNestedPaths();
}
按照Spring的设计,对此接口提供了一个抽象实现:AbstractPropertyAccessor。
AbstractPropertyAccessor
实现了部分父类的接口以及提供一些模版实现。
// @since 2.0 它继承自TypeConverterSupport 相当于实现了TypeConverter以及PropertyEditorRegistry的所有内容
public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
// 这两个属性上面已经解释了~~~
private boolean extractOldValueForEditor = false;
private boolean autoGrowNestedPaths = false;
... // 省略get/set方法
// setPropertyValue是抽象方法~~~
@Override
public void setPropertyValue(PropertyValue pv) throws BeansException {
setPropertyValue(pv.getName(), pv.getValue());
}
@Override
public void setPropertyValues(Map<?, ?> map) throws BeansException {
setPropertyValues(new MutablePropertyValues(map));
}
// MutablePropertyValues和MutablePropertySources特别像,此处就不再介绍了
// 此方法把Map最终包装成了一个MutablePropertyValues,它还有个web子类:ServletRequestParameterPropertyValues
@Override
public void setPropertyValues(Map<?, ?> map) throws BeansException {
setPropertyValues(new MutablePropertyValues(map));
}
// 当然也可以直接传入一个PropertyValues 这里传入fasle,表示默认要求属性和value值必须都合法否则抛出异常
@Override
public void setPropertyValues(PropertyValues pvs) throws BeansException {
setPropertyValues(pvs, false, false);
}
@Override
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException {
setPropertyValues(pvs, ignoreUnknown, false);
}
// 此抽象类最重要的实现方法~~~
@Override
public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {
List<PropertyAccessException> propertyAccessExceptions = null;
// 显然绝大多数情况下,都是MutablePropertyValues~~~~ 直接拿即可
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
// 遍历一个一个执行,批量设置值最终也还是调用的单个的~~~~
// 这里面是否要抛出异常,ignoreUnknown和ignoreInvalid就生效了。分别对应NotWritablePropertyException和NullValueInNestedPathException两个异常
for (PropertyValue pv : propertyValues) {
try {
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 ArrayList<>();
}
// 把异常收集,因为是for循环,最终一次性抛出
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);
}
}
// 子类AbstractNestablePropertyAccessor重写了此方法
// Redefined with public visibility.
@Override
@Nullable
public Class<?> getPropertyType(String propertyPath) {
return null;
}
// 抽象方法 相当于具体的get/set方法由子类去实现的~~
@Override
@Nullable
public abstract Object getPropertyValue(String propertyName) throws BeansException;
@Override
public abstract void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException;
}
它主要完成了对PropertyEditorRegistry和TypeConverter等接口的间接实现,然后完成了批量操作的模版操作,但是很明显最终的落地的get/set留给子类来实现~
getPropertyValue和setPropertyValue是分别用于获取和设置bean的属性值的。
AbstractNestablePropertyAccessor
一个典型的实现,为其它所有使用案例提供必要的基础设施。nestable:可嵌套的,支持嵌套的
// @since 4.2
public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
@Nullable
Object wrappedObject;
private String nestedPath = "";
@Nullable
Object rootObject;
/** Map with cached nested Accessors: nested path -> Accessor instance. */
@Nullable
private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;
// 默认是注册默认的属性编辑器的:defaultEditors 它几乎处理了所有的Java内置类型 包括基本类型、包装类型以及对应数组类型~~~
protected AbstractNestablePropertyAccessor() {
this(true);
}
protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
if (registerDefaultEditors) {
registerDefaultEditors();
}
this.typeConverterDelegate = new TypeConverterDelegate(this);
}
protected AbstractNestablePropertyAccessor(Object object) {
registerDefaultEditors();
setWrappedInstance(object);
}
protected AbstractNestablePropertyAccessor(Class<?> clazz) {
registerDefaultEditors();
// 传的Clazz 那就会反射先创建一个实例对象
setWrappedInstance(BeanUtils.instantiateClass(clazz));
}
protected AbstractNestablePropertyAccessor(Object object, String nestedPath, Object rootObject) {
registerDefaultEditors();
setWrappedInstance(object, nestedPath, rootObject);
}
// parent:不能为null
protected AbstractNestablePropertyAccessor(Object object, String nestedPath, AbstractNestablePropertyAccessor parent) {
setWrappedInstance(object, nestedPath, parent.getWrappedInstance());
setExtractOldValueForEditor(parent.isExtractOldValueForEditor());
setAutoGrowNestedPaths(parent.isAutoGrowNestedPaths());
setAutoGrowCollectionLimit(parent.getAutoGrowCollectionLimit());
setConversionService(parent.getConversionService());
}
// wrappedObject:目标对象
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
this.wrappedObject = ObjectUtils.unwrapOptional(object);
Assert.notNull(this.wrappedObject, "Target object must not be null");
this.nestedPath = (nestedPath != null ? nestedPath : "");
// 此处根对象,若nestedPath存在的话,是可以自定义一个rootObject的~~~
this.rootObject = (!this.nestedPath.isEmpty() ? rootObject : this.wrappedObject);
this.nestedPropertyAccessors = null;
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
}
public final Object getWrappedInstance() {
Assert.state(this.wrappedObject != null, "No wrapped object");
return this.wrappedObject;
}
public final String getNestedPath() {
return this.nestedPath;
}
// 显然rootObject和NestedPath相关,默认它就是wrappedObject
public final Object getRootInstance() {
Assert.state(this.rootObject != null, "No root object");
return this.rootObject;
}
... // 简单的说,它会处理.逻辑以及[0]等逻辑 [0]对应着集合和数组都可
}
此访问器将集合和数组值转换为相应的目标集合或数组,当然还解决了级联属性(嵌套属性)的问题~
需要特别注意的是:AbstractNestablePropertyAccessor这个抽象类在Spring4.2后才提供
DirectFieldAccessor
它继承自AbstractNestablePropertyAccessor,所以它肯定也就可以处理级联属性和集合数组值了。(请注意,在Spring4.2之后支持,之前是不支持的~)
// @since 2.0 出现得可比父类`AbstractNestablePropertyAccessor`要早哦~~~注意:父类的构造函数都是protected的
public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
// 缓存着每个字段的处理器FieldPropertyHandler
// ReflectionUtils.findField()根据String去找到Field对象的
private final Map<String, FieldPropertyHandler> fieldMap = new HashMap<>();
public DirectFieldAccessor(Object object) {
super(object);
}
// 这个构造器也是protected 的 所以若你想自己指定nestedPath和parent,你可以继承此类~~~
protected DirectFieldAccessor(Object object, String nestedPath, DirectFieldAccessor parent) {
super(object, nestedPath, parent);
}
...
// 实现父类的抽象方法,依旧使用DirectFieldAccessor去处理~~~
@Override
protected DirectFieldAccessor newNestedPropertyAccessor(Object object, String nestedPath) {
return new DirectFieldAccessor(object, nestedPath, this);
}
// 字段field属性处理器,使用内部类实现PropertyHandler ~~~
private class FieldPropertyHandler extends PropertyHandler {
private final Field field;
// 从此处可以看出`DirectFieldAccessor`里的field默认都是可读、可写的~~~~
public FieldPropertyHandler(Field field) {
super(field.getType(), true, true);
this.field = field;
}
...
}
}
它的功能是直接操作Bean的属性值,而代替使用get/set方法去操作Bean。它的实现原理就是简单的field.get(getWrappedInstance())和field.set(getWrappedInstance(), value)等。
它处理级联属性的大致步骤是:
- 遇上级联属性,先找出canonicalName
- 根据此canonicalName调用其field.get()拿到此字段的值~
- 若不为null(有初始值),那就继续解析此类型,循而往复即可~
PropertyAccessor使用Demo
本文以DirectFieldAccessor为例,介绍属性访问器PropertyAccessor的使用。
两个普通的JavaBean:
苹果Apple
@ToString
public class Apple {
private String color;
// 复杂类型
private Size size = new Size(); // 苹果的尺寸。 存在级联
private String[] arrStr = new String[1];
private List<String> listStr = new ArrayList<>();
private Map<Integer, String> map = new HashMap<>();
// 更为复杂的类型
private List<List<String>> listList = new ArrayList<>();
private List<Map<Integer, String>> listMap = new ArrayList<>();
public Apple() {
super();
listList.add(new ArrayList<>());
listMap.add(new HashMap<>());
}
}
尺寸Size
@ToString
public class Size {
private Integer height;
private Integer width;
}
类Apple属性丰富,并且统一都没有提供get/set方法。使用DirectFieldAccessor直接的属性访问器给其赋值:
public static void main(String[] args) {
Apple apple = new Apple();
PropertyAccessor accessor = new DirectFieldAccessor(apple);
// 设置普通属性
accessor.setPropertyValue("color", "红色");
// 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
// 否则报错:Value of nested property 'size' is null 下同~)
accessor.setPropertyValue("size.height", 10);
// 设置集合/数组属性
accessor.setPropertyValue("arrStr[0]", "arrStr");
accessor.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
accessor.setPropertyValue("listStr[0]", "listStr");
accessor.setPropertyValue("listStr[0]", "listStr1"); // 如果角标index一样,后面覆盖前面的
// 虽然listStr是String的List,但是反射绕过了泛型 可以set进去,但一get就报错~~~需要注意这一点
//accessor.setPropertyValue("listStr[0]", new Size());
//accessor.setPropertyValue("listStr[1]", 20);
//System.out.println(apple.getListStr().get(0)); //Cannot convert value of type 'com.fsx.bean.Size' to required type 'java.lang.String'
// 设置Map:key只能是数值才行,否则是不好使的~~~~
//accessor.setPropertyValue("map['aaa']","myValue1"); //Caused by: java.lang.NumberFormatException: For input string: "aaa"
accessor.setPropertyValue("map[1]", "myValue2");
// 设置listList这种集合里的集合
accessor.setPropertyValue("listList[0][0]", "listList00");
accessor.setPropertyValue("listList[0][1]", "listList01");
//accessor.setPropertyValue("listList[1][0]","listList10"); //IndexOutOfBoundsException: Index: 1, Size: 1
//accessor.setPropertyValue("listList[1][1]","listList11"); //IndexOutOfBoundsException: Index: 1, Size: 1
// 设置listMap这种集合里面放Map
accessor.setPropertyValue("listMap[0][0]", "listMap00");
//accessor.setPropertyValue("listMap[0]['myKey']","listMapkey"); //For input string: "myKey"
// =========打印输出
System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[listStr1], map={1=myValue2}, listList=[[listList00, listList01]], listMap=[{0=listMap00}])
}
从结果中是能够看出来的,使用DirectFieldAccessor能够正确完成属性赋值。这使用DirectFieldAccessor作为实现的话有几点使用小细节需要注意:
- 若是级联属性、集合数组等复杂属性,初始值不能为null
- 使用它给属性赋值无需提供get、set方法(侧面意思是:它不会走你的get/set方法逻辑)
当然若你希望null值能够被自动初始化也是可以的,请设值:accessor.setAutoGrowNestedPaths(true);这样数组、集合、Map等都会为null时候给你初始化(其它Bean请保证有默认构造函数)
在实际开发中,DirectFieldAccessor使用的场景相对较少,若我们开发中只是单纯的想直接获取属性值,不妨可以使用它,形如这样:new DirectFieldAccessor(client).getPropertyValue("redisURI")非常的方便~~~
BeanWrapper
BeanWrapper可以简单的把它理解为:一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:
- PropertyEditor:隶属于Java Bean规范。PropertyEditor只提供了String <-> Object的转换。
- ConversionService:Spring自3.0之后提供的替代PropertyEditor的机制(BeanWrapper在Spring的第一个版本就存在了~)
按照Spring官方文档的说法,当容器内没有注册ConversionService的时候,会退回使用PropertyEditor机制。言外之意:首选方案是ConversionService
//@since 13 April 2001 很清晰的看到,它也是个`PropertyAccessor`属性访问器
public interface BeanWrapper extends ConfigurablePropertyAccessor {
// @since 4.1
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
int getAutoGrowCollectionLimit();
Object getWrappedInstance();
Class<?> getWrappedClass();
// 获取属性们的PropertyDescriptor 获取属性们
PropertyDescriptor[] getPropertyDescriptors();
// 获取具体某一个属性~
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}
BeanWrapper相当于一个代理器,Spring委托BeanWrapper完成Bean属性的填充工作。关于此接口的实现类,简单的说它只有唯一实现类:BeanWrapperImpl
BeanWrapperImpl
它作为BeanWrapper接口的默认实现,它足以满足所有的典型应用场景,它会缓存Bean的内省结果而提高效率。
在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂:PropertyAccessorFactory帮助第三方框架能快速获取到一个实例
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
// 缓存内省结果~
@Nullable
private CachedIntrospectionResults cachedIntrospectionResults;
// The security context used for invoking the property methods.
@Nullable
private AccessControlContext acc;
// 构造方法都是沿用父类的~
public BeanWrapperImpl() {
this(true);
}
...
private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {
super(object, nestedPath, parent);
setSecurityContext(parent.acc);
}
// @since 4.3 设置目标对象~~~
public void setBeanInstance(Object object) {
this.wrappedObject = object;
this.rootObject = object;
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
// 设置内省的clazz
setIntrospectionClass(object.getClass());
}
// 复写父类的方法 增加内省逻辑
@Override
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
super.setWrappedInstance(object, nestedPath, rootObject);
setIntrospectionClass(getWrappedClass());
}
// 如果cachedIntrospectionResults它持有的BeanClass并不是传入的clazz 那就清空缓存 重新来~~~
protected void setIntrospectionClass(Class<?> clazz) {
if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
this.cachedIntrospectionResults = null;
}
}
private CachedIntrospectionResults getCachedIntrospectionResults() {
if (this.cachedIntrospectionResults == null) {
// forClass此方法:生成此clazz的类型结果,并且缓存了起来~~
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
}
return this.cachedIntrospectionResults;
}
...
// 获取到此属性的处理器。此处是个BeanPropertyHandler 内部类~
@Override
@Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null);
}
@Override
protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
return new BeanWrapperImpl(object, nestedPath, this);
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return getCachedIntrospectionResults().getPropertyDescriptors();
}
// 获取具体某一个属性的PropertyDescriptor
@Override
public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {
BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
String finalPath = getFinalPath(nestedBw, propertyName);
PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);
if (pd == null) {
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, "No property '" + propertyName + "' found");
}
return pd;
}
...
// 此处理器处理的是PropertyDescriptor
private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd;
// 是否可读、可写 都是由PropertyDescriptor 去决定了~
// java.beans.PropertyDescriptor~~
public BeanPropertyHandler(PropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd;
}
...
@Override
@Nullable
public Object getValue() throws Exception {
...
ReflectionUtils.makeAccessible(readMethod);
return readMethod.invoke(getWrappedInstance(), (Object[]) null);
}
...
}
}
从继承体系上,首先我们应该能看出来BeanWrapperImpl的三重身份:
- Bean包裹器
- 属性访问器(PropertyAccessor)
- 属性编辑器注册表(PropertyEditorRegistry)
从源码中继续分析还能再得出如下两个结论:
- 它给属性赋值调用的是Method方法,如readMethod.invoke和writeMethod.invoke
- 它对Bean的操作,大都委托给CachedIntrospectionResults去完成~
因此若想了解它,必然主要是要先了解java.beans.PropertyDescriptor和org.springframework.beans.CachedIntrospectionResults,首当其冲的自然还有Java内省。
Java内省Introspector
首先可以先了解下JavaBean的概念:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。
因此JavaBean都有如下几个特征:
- 属性都是私有的;
- 有无参的public构造方法;
- 对私有属性根据需要提供公有的getXxx方法以及setXxx方法;
- getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;
符合这些特征的类,被称为JavaBean;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在java.beans中,这就是内省(Introspector)。
内省和反射的区别
反射:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是任意类
内省(Introspector):是Java语言对JavaBean类属性、事件的处理方法
- 反射可以操作各种类的属性,而内省只是通过反射来操作JavaBean的属性
- 内省设置属性值肯定会调用seter方法,反射可以不用(反射可直接操作属性Field)
- 反射就像照镜子,然后能看到.class的所有,是客观的事实。内省更像主观的判断:比如看到getName()内省就会认为这个类中有name字段,但事实上并不一定会有name;通过内省可以获取bean的getter/setter
既然反射比内省强大这么多,那内省用在什么时候场景呢?下面给出一个示例来说明它的用武之地:
// 就这样简单几步,就完成了表单到User对象的封装~
public void insertUser(HttpServletRequest request) throws Exception {
User user = new User();
// 遍历:根据字段名去拿值即可(此处省略判空、类型转换等细节,不在本文讨论范围)
PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
pd.getWriteMethod().invoke(user, request.getParameter(pd.getName()));
}
}
通过内省可以很轻松的将form表单的内容填充进对象里面,比反射轻松省力多了。其实像MyBatis这种框架,底层都用到了Java的内省机制。
内省的API主要有Introspector、BeanInfo、PropertyDescriptor等,下面就以这三个为例来操作一个JavaBean:
@Getter
@Setter
@ToString
public class Child {
private String name;
private Integer age;
}
使用Introspector + BeanInfo:
public static void main(String[] args) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(Child.class);
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
// 打印
System.out.println(beanDescriptor);
System.out.println("------------------------------");
Arrays.stream(methodDescriptors).forEach(x -> System.out.println(x));
System.out.println("------------------------------");
Arrays.stream(propertyDescriptors).forEach(x -> System.out.println(x));
System.out.println("------------------------------");
}
输出内容如下:
java.beans.BeanDescriptor[name=Child; beanClass=class com.fsx.bean.Child]
------------------------------
java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.MethodDescriptor[name=getName; method=public java.lang.String com.fsx.bean.Child.getName()]
java.beans.MethodDescriptor[name=setAge; method=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.MethodDescriptor[name=setName; method=public void com.fsx.bean.Child.setName(java.lang.String)]
java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.fsx.bean.Child.getAge()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()]
java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()]
java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)]
java.beans.MethodDescriptor[name=toString; method=public java.lang.String com.fsx.bean.Child.toString()]
------------------------------
java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.fsx.bean.Child.getAge(); writeMethod=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.fsx.bean.Child.getName(); writeMethod=public void com.fsx.bean.Child.setName(java.lang.String)]
------------------------------
可以看到getMethodDescriptors()它把父类的MethodDescriptor也拿出来了。而PropertyDescriptor中比较特殊的是因为有getClass()方法,因此class也算是一个PropertyDescriptor,但是它没有writeMethod哦~
关于BeanInfo,Spring在3.1提供了一个类ExtendedBeanInfo继承自它实现了功能扩展,并且提供了BeanInfoFactory来专门生产它~~~(实现类为:ExtendedBeanInfoFactory)
但是如果只想拿某一个属性的话,使用Introspector就不是那么方便了,下面介绍更为常用的PropertyDescriptor来处理某一个属性~
PropertyDescriptor 属性描述器
属性描述符描述了Java bean通过一对访问器方法导出的一个属性。上面的示例此处用PropertyDescriptor试试:
public static void main(String[] args) throws IntrospectionException {
PropertyDescriptor age = new PropertyDescriptor("age", Child.class);
System.out.println(age.getPropertyType()); //class java.lang.Integer
System.out.println(age.getDisplayName()); //age
// 最重要的两个方法~~~
System.out.println(age.getReadMethod()); //public java.lang.Integer com.fsx.bean.Child.getAge()
System.out.println(age.getWriteMethod()); //public void com.fsx.bean.Child.setAge(java.lang.Integer)
}
可以看到它可以实现更加细粒度的控制。将PropertyDescriptor类的一些主要方法描述如下:
- getPropertyType(),获得属性的Class对象;
- getReadMethod(),获得用于读取属性值的方法;
- getWriteMethod(),获得用于写入属性值的方法;
- setReadMethod(Method readMethod),设置用于读取属性值的方法;
- setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
PropertyAccessorFactory
Spring2.5后提供的快速获取PropertyAccessor两个重要实现类的工厂。
public final class PropertyAccessorFactory {
private PropertyAccessorFactory() {
}
// 生产一个BeanWrapperImpl(最为常用)
public static BeanWrapper forBeanPropertyAccess(Object target) {
return new BeanWrapperImpl(target);
}
// 生产一个DirectFieldAccessor
public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) {
return new DirectFieldAccessor(target);
}
}
BeanWrapper使用Demo
// 省略Apple类和Size类,有需要的请参照上篇文章(加上@Getter、@Setter即可
public static void main(String[] args) {
Apple apple = new Apple();
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(apple);
// ================当作一个普通的PropertyAccessor来使用 默认情况下字段也都必须有初始值才行~===================
// 设置普通属性
beanWrapper.setPropertyValue("color", "红色"); //请保证对应字段有set方法才行,否则抛错:Does the parameter type of the setter match the return type of the getter?
// 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
// 否则报错:Value of nested property 'size' is null 下同~)
beanWrapper.setPropertyValue("size.height", 10);
// 设置集合/数组属性
beanWrapper.setPropertyValue("arrStr[0]", "arrStr");
beanWrapper.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
// =========打印输出
System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[], map={}, listList=[[]], listMap=[{}])
// 当作BeanWrapper使用
PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
PropertyDescriptor color = beanWrapper.getPropertyDescriptor("color");
System.out.println(propertyDescriptors.length); // 8
System.out.println(color); //org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=color]
System.out.println(beanWrapper.getWrappedClass()); //class com.fsx.bean.Apple
System.out.println(beanWrapper.getWrappedInstance()); //Apple(color=红色, size=Size(height=10...
}
参考: |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-11-23 介绍Spring的FactoryBean接口