每篇一句
不要總問低級的問題,這樣的人要么懶,不願意上網搜索,要么笨,一點獨立思考的能力都沒有
相關閱讀
【小家Spring】聊聊Spring中的數據綁定 --- DataBinder本尊(源碼分析)
【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor
前言
上篇文章聊了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入群"
字樣,會手動邀請入群
