不要总问低级的问题,这样的人要么懒,不肯意上网搜索,要么笨,一点独立思考的能力都没有java
【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)web
【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用spring
【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor编程
上篇文章聊了DataBinder
,这篇文章继续聊聊实际应用中的数据绑定主菜:WebDataBinder
。json
在上文的基础上,咱们先来看看DataBinder
它的继承树:
从继承树中能够看到,web环境统一对数据绑定DataBinder
进行了加强。数组
毕竟数据绑定的实际应用场景:不夸张的说99%状况都是web环境~架构
它的做用就是从web request
里(注意:这里指的web请求,并不必定就是ServletRequest请求哟~)把web请求的parameters
绑定到JavaBean
上~app
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
来讲,它对父类进行了加强,提供的加强能力以下:ide
_
打头的默认值处理(自动挡,可以自动处理全部的Bool、Collection、Map等)!
打头的默认值处理(手动档,须要手动给某个属性赋默认值,本身控制的灵活性很高)MultipartFile
绑定到JavaBean
的属性上~下面以一个示例来演示使用它加强的这些功能:
@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
前面说了这么多,亲有没有发现还木有聊到过咱们最为常见的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
的实现类~
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就行呢?
此类代码很少但也不容小觑,它是对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
它是Spring5.0
后提供的,对Reactive
编程的Mono数据绑定提供支持,所以暂略~
data binding from URL query params or form data in the request data to Java objects
它位于org.springframework.data.web
是和Spring-Data相关,专门用于处理target
是Map<String, Object>
类型的目标对象的绑定,它并不是一个public类~
它用的属性访问器是
MapPropertyAccessor
:一个继承自AbstractPropertyAccessor
的私有静态内部类~(也支持到了SpEL哦)
它是用于处理Spring本身定义的org.springframework.web.context.request.WebRequest
的,旨在处理和容器无关的web请求数据绑定,有机会详述到这块的时候,再详细说~
自定义类型
数据绑定?经过前面的分析咱们知道了,数据绑定这一块最终会依托于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
,显然此处咱们也没有设置,返回nulleditor = findDefaultEditor(requiredType);
(注意:此处只根据类型去找了,由于上面说了默认不处理了Date,因此也是返回null)Array、Collection、Map
的默认值处理问题,最终如果String类型,都会调用BeanUtils.instantiateClass(strCtor, convertedValue)
也就是有参构造进行初始化~~~(请注意这必须是String类型才有的权利)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
便可:
一、经过继承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...................."); } } }
二、注册进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)
结果符合预期。不过对此结果我仍旧抛出以下两个问题供小伙伴自行思考:
一、输出了ParseException....................
二、start有值,endTest值却为null了
理解这块最后我想说:经过自定义编辑器,咱们能够很是自由、高度定制化的完成自定义类型的封装,可使得咱们的Controller更加容错、更加智能、更加简洁。有兴趣的能够运用此块知识,自行实践~
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
等一块儿使用~
顾名思义它就是来创造一个WebDataBinder
的工厂。
// @since 3.1 注意:WebDataBinder 但是1.2就有了~ public interface WebDataBinderFactory { // 此处使用的是Spring本身的NativeWebRequest 后面两个参数就不解释了 WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception; }
它的继承树以下:
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一向的设计,本方法实现了模板动做,子类只须要复写对应的动做便可达到效果。
它继承自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())); } }
它继承自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入群"
字样,会手动邀请入群