javascript
SpringMVC常见组件之DataBinder数据绑定器分析
關聯博文:
SpringMVC常見組件之HandlerAdapter分析
SpringMVC常見組件之HandlerMapping分析
SpringMVC常見組件之HandlerMethodArgumentResolver解析
SpringMVC常見組件之HandlerMethodReturnValueHandler解析
SpringMVC常見組件之DataBinder數據綁定器分析
SpringMVC常見組件之ViewResolver分析
SpringMVC常見組件之View分析
SpringMVC常見組件之HandlerExceptionResolver分析
什么是數據綁定?簡單一句話就是把請求中參數信息綁定到目標方法的參數上。數據綁定是參數解析過程中的一部分。SpringMVC通過反射機制對目標處理方法進行解析,將請求消息綁定到處理方法的入參中。如下圖所示:
那么這里我們就要研究一下數據綁定相關的那些組件。
【1】綁定工廠WebDataBinderFactory
工廠嘛,使用了工廠方法設計模式,只有一個抽象方法用來讓子類實現以創建一個WebDataBinder實例。
public interface WebDataBinderFactory {WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName)throws Exception; }- NativeWebRequest:當前請求
- target:數據綁定器的目標對象,如果是為簡單類型創建,則target為null;
- objectName:target的名字
WebDataBinderFactory家族圖示(典型的工廠方法設計模式)
① DefaultDataBinderFactory
這里我們先看一下其createBinder方法,也是核心入口方法。
@Override @SuppressWarnings("deprecation") public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception { // 提供了默認實現,也可以讓子類覆蓋 WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest); if (this.initializer != null) {this.initializer.initBinder(dataBinder, webRequest); } // 空方法,讓子類實現 initBinder(dataBinder, webRequest); return dataBinder; }如下所示,其創建了一個WebRequestDataBinder實例。WebRequestDataBinder主要是用來將web request parameters綁定到JavaBeans,支持 multipart files。
protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception {return new WebRequestDataBinder(target, objectName); }其initBinder方法是個空方法,讓子類InitBinderDataBinderFactory實現。
這里我們關注一下this.initializer.initBinder(dataBinder, webRequest);。也就是初始化器首先對數據綁定器進行初始化,這里最終會走到ConfigurableWebBindingInitializer#initBinder,如下所示,在后面使用databinder進行類型轉換、格式校驗時用的就是這里填充進去的“功能屬性對象”。
@Override public void initBinder(WebDataBinder binder) {binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);if (this.directFieldAccess) {// 設置directFieldAccess=truebinder.initDirectFieldAccess();}// 設置messageCodesResolver ,默認是DefaultMessageCodesResolverif (this.messageCodesResolver != null) {binder.setMessageCodesResolver(this.messageCodesResolver);}// 設置BindingErrorProcessor,默認是DefaultBindingErrorProcessorif (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);}// 設置屬性編輯注冊器,能夠向binder注冊一系列編輯器if (this.propertyEditorRegistrars != null) {for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {propertyEditorRegistrar.registerCustomEditors(binder);}} }② InitBinderDataBinderFactory
InitBinderDataBinderFactory是通過@InitBinder方法向WebDataBinder 添加初始化行為。其并沒有重寫父類的創建DataBinder方法,但是提供了初始化DataBinder方法。
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {for (InvocableHandlerMethod binderMethod : this.binderMethods) {if (isBinderMethodApplicable(binderMethod, dataBinder)) {Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);if (returnValue != null) {throw new IllegalStateException("@InitBinder methods must not return a value (should be void): " + binderMethod);}}} }這里我們需要多說一點,在進行參數解析的時候,如果binderFactory不為null且是DefaultDataBinderFactory或子類InitBinderDataBinderFactory。那么其createBinder會進行初始化initBinder(dataBinder, webRequest);。而InitBinderDataBinderFactory的initBinder方法如上所示,會首先判斷當前binderMethods是否有符合當前WebDataBinder的@InitBinder方法binderMethod。如果有合適的binderMethod,那么將會在初始化綁定器的過程中反射調用該方法。
如下圖所示,能夠看到InvocableHandlerMethod#invokeForRequest方法被調用了兩次,第二次的時候providedArgs不再為空。
③ ServletRequestDataBinderFactory
其創建了一個ServletRequestDataBinder(子類ExtendedServletRequestDataBinder),ServletRequestDataBinder可以將請求的參數綁定到目標對象,如Query String Parameters、form-data的參數,并支持multipart files的綁定。
具體子類是ExtendedServletRequestDataBinder,ExtendedServletRequestDataBinder將 URI template variables(如路徑變量/getUser/{id}中的id)解析提供給數據綁定使用。
@Override protected ServletRequestDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest request) throws Exception {return new ExtendedServletRequestDataBinder(target, objectName); }【2】數據綁定器DataBinder
數據綁定器是為目標對象綁定屬性值,同時支持屬性校驗與綁定結果分析。可以通過BindingResult拿到綁定結果。
① 校驗器處理
DataBinder提供了一些校驗器處理方法如addValidators(添加校驗器)、replaceValidators(替換校驗器)、getValidators(獲取校驗器)、getValidato(獲取第一個校驗器)r以及核心的validate方法。
public void validate(Object... validationHints) {Object target = getTarget();Assert.state(target != null, "No target to validate");BindingResult bindingResult = getBindingResult();// Call each validator with the same binding resultfor (Validator validator : getValidators()) {if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {((SmartValidator) validator).validate(target, bindingResult, validationHints);}else if (validator != null) {validator.validate(target, bindingResult);}} }代碼主要含義就是首先獲取taget(要綁定的目標對象)、bindingResult與Validators,然后遍歷Validators并進行校驗,將校驗結果放到bingdingResult中。
一個校驗通過的bingdingResult如下所示
如果校驗不通過,那么其errors屬性將會保存錯誤信息,如下所示
② 轉換器處理
數據綁定過程中,獲取到請求中的數據后向目標對象進行綁定,那么這個階段可能涉及到類型轉換,如String轉換為Integer。DataBinder實現了TypeConverter接口,提供了系列重載convertIfNecessary方法。
@Override @Nullable public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException {return getTypeConverter().convertIfNecessary(value, requiredType); }@Override @Nullable public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable MethodParameter methodParam) throws TypeMismatchException {return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); }@Override @Nullable public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)throws TypeMismatchException {return getTypeConverter().convertIfNecessary(value, requiredType, field); }@Nullable @Override public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {return getTypeConverter().convertIfNecessary(value, requiredType, typeDescriptor); }這里面我們可以看一下其getTypeConverter方法:
protected TypeConverter getTypeConverter() {if (getTarget() != null) {return getInternalBindingResult().getPropertyAccessor();}else {return getSimpleTypeConverter();} }也就是說如果target不為null(復雜類型比如SysUser),那么就獲取一個BeanWrapperImpl實例。如果是一個簡單類型如Integer age,那么就獲取一個SimpleTypeConverter。
額外說明的是,在類型轉換的過程中,都離不開一個conversionService實例(默認是DefaultFormattingConversionService)。該實例內部擁有系統的converters,正是這些converts(有100多個)實現了類型的轉換。從下圖可以看到,二者都是TypeConverterSupport的子類,實現了TypeConverter、PropertyEditorRegistry接口。PropertyEditorRegistry接口給自定義屬性編輯器注冊提供了入口。
WebDataBinder 使用registerCustomEditor應用
③ 綁定結果BindingResult
數據綁定過程中很可能因為數據類型轉換異常(或其他錯誤)綁定失敗,這些錯誤信息就保存在了BindingResult中。我們可以通過該接口獲取到錯誤信息進行對應處理。這里我們可以簡單看一下其家族樹,不展開分析。詳情參考博文SpringMVC中使用JSR303進行數據校驗實踐詳解
其創建BindingResult的兩個方法
獲取BindingResult的核心方法
// 根據directFieldAccess 獲取DirectFieldBindingResult或者BeanPropertyBindingResult protected AbstractPropertyBindingResult getInternalBindingResult() {if (this.bindingResult == null) {this.bindingResult = (this.directFieldAccess ?createDirectFieldBindingResult(): createBeanPropertyBindingResult());}return this.bindingResult; }如下實例方法三個參數分別為pojo、Errors以及map
這里我們解析得到的參數實例為:
④ 核心綁定方法
也就是把給定的屬性-值綁定到目標對象上,這個過程可能會拋出“required”或者類型轉換異常等錯誤。
public void bind(PropertyValues pvs) {MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));doBind(mpvs); } protected void doBind(MutablePropertyValues mpvs) {checkAllowedFields(mpvs);checkRequiredFields(mpvs);applyPropertyValues(mpvs); }可以看到doBind是一個模板方法:
- checkAllowedFields方法是檢測被允許的field,如果發現有不被允許的field,則移除掉。* checkRequiredFields方法是檢測是那些聲明了“required”的filed是否都存在,如果有不存在的將會把錯誤信息放到BindingResult中。
- applyPropertyValues則是核心的屬性-值綁定到目標對象的方法。
【3】WebDataBinder
WebDataBinder是DataBinder的子類,其覆蓋了父類的doBind方法。
@Override protected void doBind(MutablePropertyValues mpvs) {checkFieldDefaults(mpvs);checkFieldMarkers(mpvs);adaptEmptyArrayIndices(mpvs);super.doBind(mpvs); }其同樣是個模板方法,在處理屬性默認值(字段以“!”開頭,提供默認值代替空值)、屬性標記(字段以"_"開頭,如果表單提交沒有該字段對應的值,則重置該字段)、字段名稱去掉"[]"(如果字段名稱以[]結尾)后,調用了父類的doBind方法。
如下演示!的使用:
public static void main(String[] args){SysUser sysUser = new SysUser();WebDataBinder binder = new WebDataBinder(sysUser, "sysUser");// 設置屬性(此處演示一下默認值)MutablePropertyValues pvs = new MutablePropertyValues();// 使用!來模擬各個字段手動指定默認值pvs.add("!name", "jane");pvs.add("age", 18);pvs.add("!age", 10); // 上面有確切的值了,默認值不會再生效binder.bind(pvs);System.out.println(sysUser);}打印結果:SysUser{name='jane', sex='null', age=18}
值得一提的是,其提供了方法bindMultipart供子類調用,該方法用來處理multipart files:
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);}}); }【4】ServletRequestDataBinder
ServletRequestDataBinder繼承自WebDataBinder,有唯一子類ExtendedServletRequestDataBinder。此類把web請求限定為了Servlet Request,和Servlet規范強綁定。
Special {@link org.springframework.validation.DataBinder} to perform data binding from servlet request parameters to JavaBeans, including support for multipart files.
ServletRequestDataBinder可以將請求的參數綁定到目標對象,如Query String Parameters、form-data的參數,并支持multipart files的綁定。
子類ExtendedServletRequestDataBinder將 URI template variables(如路徑變量/getUser/{id}中的id)解析提供給數據綁定使用。
① ServletRequestDataBinder的bind方法
ServletRequestDataBinder提供了方法bind,其對MultipartRequest 請求做了支持可以綁定文件域(通過調用父類WebDataBinder的bindMultipart方法)。
public void bind(ServletRequest request) {// 從request中獲取屬性-值MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);// 判斷當前請求是否為MultipartRequest MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);if (multipartRequest != null) {// 調用父類webDataBinder方法bindMultipart(multipartRequest.getMultiFileMap(), mpvs);}else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class);if (httpServletRequest != null) {StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles());}}addBindValues(mpvs, request);// 調用父類方法doBind(mpvs); }- ① 這里嘗試獲取mpvs,然后判斷是否為MultipartRequest或者StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")&&httpServletRequest != null之后調用對應的bindMultipart或者bindParts方法。如http://localhost:8081/testUser/1?name=jane得到的mpvs如下圖所示:
- ② addBindValues是一步兼容,從request中獲取屬性HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE對應的值,然后放入mpvs。這時候的mpvs可能如下(可以看到多了一個URI Template Variable id=1):
- ③ 調用父類核心的doBind方法,將mpvs中的屬性-值綁定到目標target上
這里比較有意思的是addBindValues方法是一個空方法,且用protected修飾。很明顯其是想讓子類實現(ExtendedServletRequestDataBinder子類進行了實現)。
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { }如下所示,執行doBind(mpvs);方法前的DataBinder對象。
如下所示,執行doBind(mpvs);方法后的DataBinder對象。可以看到從mpvs中找到SysUser擁有的屬性給sysUser實例對象賦值(通常是反射調用set方法,如setName)。
當然如果BindingResult不是BeanPropertyBindingResult而是DirectFieldBindingResult則是另一種方式。
② ExtendedServletRequestDataBinder
ExtendedServletRequestDataBinder實現/覆蓋了父類的addBindValues方法,主要是從request中獲取URL路徑變量集合,然后找到符合當前請求的屬性-值放入MutablePropertyValues mpvs中。
@Override protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {// HandlerMapping.class.getName() + ".uriTemplateVariables"String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;@SuppressWarnings("unchecked")Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);if (uriVars != null) {uriVars.forEach((name, value) -> {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);}});} }此屬性放入的第一個地方是:AbstractUrlHandlerMapping.lookupHandler() --> chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); --> preHandle()方法 -> exposeUriTemplateVariables(this.uriTemplateVariables, request); -> request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
第二個地方是:AbstractHandlerMethodMapping.lookupHandlerMethod()->RequestMappingInfoHandlerMapping.handleMatch()–>RequestMappingInfoHandlerMapping.extractMatchDetails()–>request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
如下所示,一個請求對象的attributes中包含了一個org.springframework.web.servlet.HandlerMapping.uriTemplateVariables屬性:
我們也順便多看一眼當一個請求發起到找到對應的HandlerMethod時,請求屬性里面放入了什么?
③ 表單提交綁定過程
這里描述的背景為填寫表單對象,傳遞到后臺(參數為SysUser sysUser,也就是常見的實體對象)。
① SpringMVC框架將ServletRequest對象以及目標方法的入參實例(objectName,attribute) 傳遞給WebDataBinderFactory實例,以創建DataBinder實例對象。
- objectName為@ModelAttribute注解的value值或者根據參數類型自動創建的key
- 其中attribute會根據目標方法參數類型從ModelAndViewContainer 獲取或者創建-這是Value
② DataBinder調用裝配在SpringMVC上下文中的ConversionService組件進行數據類型轉換、數據格式化工作并將請求信息填充到創建的入參對象中。
③ 調用Validator 組件對已經綁定了請求消息的入參對象(創建的)進行數據合法性校驗,并最終生成數據結果(BindingResult)
④ SpringMVC 抽取 BindingResult 中的入參對象(getTarget)和校驗錯誤對象(BindingResult),將他們賦給處理方法的相應入參
【5】WebRequestDataBinder
這個與不同的是其是把web request parameters 綁定到 JavaBeans,同樣支持multipart files。也就是說其跳出了Servlet規范,額外做了支持。其主要對org.springframework.web.context.request.WebRequest做處理。
如下代碼所示,WebRequestDataBinder首先從request中獲取參數值封裝為MutablePropertyValues 。然后分別判斷是否為MultipartRequest、或者請求頭Content-Type以multipart/開頭兩種情況進行處理,最后調用父類的doBind方法。
public void bind(WebRequest request) {MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());if (request instanceof NativeWebRequest) {MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);if (multipartRequest != null) {bindMultipart(multipartRequest.getMultiFileMap(), mpvs);}else if (StringUtils.startsWithIgnoreCase(request.getHeader("Content-Type"), "multipart/")) {HttpServletRequest servletRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);if (servletRequest != null) {StandardServletPartUtils.bindParts(servletRequest, mpvs, isBindEmptyMultipartFiles());}}}doBind(mpvs); }總結
以上是生活随笔為你收集整理的SpringMVC常见组件之DataBinder数据绑定器分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 飞行航线调配Matlab模型,嵌入式代码
- 下一篇: 【小家Spring】聊聊Spring中的