WebDataBinderServletRequestDataBinderWebBindingInitializer

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebDataBinderServletRequestDataBinderWebBindingInitializer相关的知识,希望对你有一定的参考价值。

前言

上篇文章聊了​​DataBinder​​,这篇文章继续聊聊实际应用中的数据绑定主菜:​​WebDataBinder​​。

在上文的基础上,我们先来看看​​DataBinder​​它的继承树:

WebDataBinder、ServletRequestDataBinder、WebBindingInitializer_spring


从继承树中可以看到,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​​来说,它对父类进行了增强,提供的增强能力如下:

  1. 支持对属性名以​​_​​打头的默认值处理(自动挡,能够自动处理所有的Bool、Collection、Map等)
  2. 支持对属性名以​​!​​打头的默认值处理(手动档,需要手动给某个属性赋默认值,自己控制的灵活性很高)
  3. 提供方法,支持把​​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()​​ --&gt; ​​chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));​​ --&gt; ​​preHandle()方法​​ -&gt; ​​exposeUriTemplateVariables(this.uriTemplateVariables, request);​​ -&gt; ​​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&lt;String, Object&gt;​​类型的目标对象的绑定,它并非一个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​​为何会有值呢???
此处我简单解释一下处理步骤:

  1. ​BeanWrapper​​调用​​setPropertyValue()​​给属性赋值,传入的value值都会交给​​convertForProperty()​​方法根据get方法的返回值类型进行转换~(比如此处为Date类型)
  2. 委托给​​this.typeConverterDelegate.convertIfNecessary​​进行类型转换(比如此处为string-&gt;Date类型)
  3. 先​​this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);​​找到一个合适的​​PropertyEditor​​(显然此处我们没有自定义Custom处理Date的PropertyEditor,返回null)
  4. 回退到使用​​ConversionService​​,显然此处我们也没有设置,返回null
  5. 回退到使用默认的​​editor = findDefaultEditor(requiredType);​​(注意:此处只根据类型去找了,因为上面说了默认不处理了Date,所以也是返回null)
  6. 最终的最终,回退到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;

它的继承树如下:

WebDataBinder、ServletRequestDataBinder、WebBindingInitializer_spring_02

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​​注解一起使用将有非常大的威力,能一定程度上简化你的开发,提高效率


linux增加swap空间

Swap分区,即交换区

Swap空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用,那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap空间中,等到那些程序要运行时,再从Swap中恢复保存的数据到内存中。这样,系统总是在物理内存不够时,才进行Swap交换。

通常情况下,Swap空间应大于或等于物理内存的大小,最小不应小于64M,通常Swap空间的大小应是物理内存的2-2.5倍,Swap的调整对Linux服务器,特别是Web服务器的性能至关重要,通过调整Swap,有时可以越过系统性能瓶颈,节省系统升级费用。

1、查看已有空间

#free -m   

 

 

 2、新增swap空间
准备将交换文件放在/swap目录下,若没有的话可创建,增加3G空间,执行

#  dd if=/dev/zero of=swapfile bs=3M count=1024

 

 

 创建交换文件、启用

# mkswap swapfile

# swapon swapfile

 

 此时交换空间已增加成功,若想开机启动

修改/etc/fstab文件

# /swap/swapfile          swap                    swap    defaults        0 0

3、若想删除交换空间,先执行关闭,然后删除即可,同时删除/etc/fstab中信息

# swapoff swapfile

 需要说明一点,并不是所有从物理内存中交换出来的数据都会被放到Swap中(如果这样的话,Swap就会不堪重负),有相当一部分数据被直接交换到文件系统。例如,有的程序会打开一些文件,对文件进行读写(其实每个程序都至少要打开一个文件,那就是运行程序本身),当需要将这些程序的内存空间交换出去时,就没有必要将文件部分的数据放到Swap空间中了,而可以直接将其放到文件里去。如果是读文件操作,那么内存数据被直接释放,不需要交换出来,因为下次需要时,可直接从文件系统恢复;如果是写文件,只需要将变化的数据保存到文件中,以便恢复。但是那些用malloc和new函数生成的对象的数据则不同,它们需要Swap空间,因为它们在文件系统中没有相应的“储备”文件,因此被称作“匿名”(Anonymous)内存数据。这类数据还包括堆栈中的一些状态和变量数据等。所以说,Swap空间是“匿名”数据的交换空间。

 

 

 

 

 

以上是关于WebDataBinderServletRequestDataBinderWebBindingInitializer的主要内容,如果未能解决你的问题,请参考以下文章