每篇一句
不要总问低级的问题,这样的人要么懒,不愿意上网搜索,要么笨,一点独立思考的能力都没有
相关阅读
【小家 Spring】聊聊 Spring 中的数据绑定 — DataBinder 本尊(源码分析)
【小家 Spring】聊聊 Spring 中的数据绑定 — 属性访问器 PropertyAccessor 和实现类 DirectFieldAccessor 的使用
【小家 Spring】聊聊 Spring 中的数据绑定 — BeanWrapper 以及 Java 内省 Introspector 和 PropertyDescriptor
<center> 对 Spring 感兴趣可扫码加入 wx 群:Java 高工、架构师 3 群
(文末有二维码)</center>
前言
上篇文章聊了 DataBinder
,这篇文章继续聊聊 实际应用中 的数据绑定 主菜:WebDataBinder
。
在上文的基础上,我们先来看看 DataBinder
它的继承树:
从继承树中可以看到,web 环境统一对数据绑定 DataBinder
进行了增强。
毕竟数据绑定的实际应用场景:不夸张的说 99% 情况都是 web 环境~
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=null, age=null, flag=false, index=0, list=[], map={})
请用此打印结果对比一下上面的结果,你是会有很多发现,比如能够发现 基本类型的默认值就是它自己 。
另一个很显然的道理:若你啥都不做特殊处理,包装类型默认值那铁定都是 null 了~
了解了 WebDataBinder
后,继续看看它的一个重要子类ServletRequestDataBinder
ServletRequestDataBinder
前面说了这么多,亲有没有发现还木有聊到过我们 最为常见的Web 场景 API:javax.servlet.ServletRequest
。本类从命名上就知道,它就是为此而生。
它的目标就是:data binding from servlet request parameters to JavaBeans, including support for multipart files. 从 Servlet Request 里把参数绑定到 JavaBean 里,支持 multipart。
备注:到此类为止就已经把 web 请求限定为了 Servlet Request,和 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
的实现类~
Demo 示例
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=value2})
完美。
思考题:小伙伴可以思考为何给 Map 属性传值是如上,而不是 value 写个 json 就行呢?
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
提供一份共用的模版属性们,供以绑定~
此类基本都沿用父类的功能,比较简单,此处就不写 Demo 了(Demo 请参照父类)~
说明:
ServletRequestDataBinder
一般不会直接使用,而是使用更强的子类ExtendedServletRequestDataBinder
WebExchangeDataBinder
它是 Spring5.0
后提供的,对 Reactive
编程的 Mono 数据绑定提供支持,因此暂略~
data binding from URL query params or form data in the request data to Java objects
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
来实现具体属性值的转换(毕竟 request 传进来的都是字符串嘛~)
一般来说,像 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 提供的各种事件、日期类型的转换(当然也包括我们的自定义类型)。
因此我相信小伙伴都遇到过这样的痛点:Date、LocalDate等类型使用自动绑定老不方便了,并且还经常傻傻搞不清楚。所以最终很多都 无奈 选择了语义不是非常清晰的 时间戳 来传递
演示 Date 类型的数据绑定 Demo:
@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);
}
打印输出:
Person(name=fsx, age=null, start=Sat Jul 20 11:05:29 CST 2019, end=null, endTest=Sun Jul 21 01:00:22 CST 2019)
结果是符合我预期的: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 类型才有的权利)1. 所以本例中,到最后一步就相当于 `new Date("Sat Jul 20 11:00:22 CST 2019") `,** 因为该字符串是标准的时间日期串,所以是阔仪的,也就是 endTest 是能被正常赋值的~**
通过这个简单的步骤分析,解释了为何 end 没值,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);
}
打印输出:
Person(name=fsx, child=Child(name=fsx-son, age=null))
完美。
废话不多说,下面我通过 自定义属性编辑器 的手段,来让能够支持处理上面我们传入 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);
}
运行打印如下:
ParseException....................
Person(name=fsx, age=null, start=Sat Jul 20 11:41:49 CST 2019, end=Sat Jul 20 00:00:00 CST 2019, endTest=null)
结果符合预期。不过对此结果我仍旧抛出如下两个问题供小伙伴自行思考:
1、输出了 ParseException………………..
2、start 有值,endTest 值却为 null 了
理解这块最后我想说:通过自定义编辑器,我们可以非常自由、高度定制化的完成自定义类型的封装,可以使得我们的 Controller 更加容错、更加智能、更加简洁。有兴趣的可以运用此块知识,自行实践~
WebBindingInitializer 和 WebDataBinderFactory
WebBindingInitializer
WebBindingInitializer
:实现此接口重写 initBinder 方法注册的属性编辑器是 全局的 属性编辑器,对所有的 Controller 都有效。
可以简单粗暴的理解为:WebBindingInitializer
为编码方式,@InitBinder
为注解方式(当然注解方式还能控制到只对当前 Controller 有效,实现更细粒度的控制)
观察发现,Spring 对这个接口的命名很有意思:它用的 Binding正在进行时态~
// @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);
}
}
按照 Spring 一贯的设计,本方法实现了模板动作,子类只需要复写对应的动作即可达到效果。
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
却又是当下使用得最频繁、功能最强大的一个适配器
总结
WebDataBinder
在 SpringMVC
中使用,它不需要我们自己去创建,我们只需要向它注册参数类型对应的属性编辑器 PropertyEditor
。PropertyEditor
可以将字符串转换成其 真正的数据类型 ,它的void setAsText(String text)
方法实现数据转换的过程。
好好掌握这部分内容,这在 Spring MVC
中结合 @InitBinder
注解一起使用将有非常大的威力,能一定程度上简化你的开发,提高效率
知识交流
若文章格式混乱,可点击
:原文链接 - 原文链接 - 原文链接 - 原文链接 - 原文链接
==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被 作者本人许可的~
==
** 若对技术内容感兴趣可以加入 wx 群交流:Java 高工、架构师 3 群
。
若群二维码失效,请加 wx 号:fsx641385712
(或者扫描下方 wx 二维码)。并且备注:"java 入群"
字样,会手动邀请入群 **