天天看點

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、<mvc:annotation-driven>标簽的解析五、資料格式化

一、資料綁定

頁面送出的資料都是字元串,要想和JavaBean進行一一綁定就會牽扯到資料綁定期間的資料類型轉換、資料格式化、資料校驗的問題。

資料綁定發生在

ModelAttributeMethodProcessor

這個類中的

bindRequestParameters(binder, webRequest)

方法,這個方法将頁面送出過來的資料封裝到JavaBean的屬性中,封裝期間可能會有資料類型轉換、資料格式化、資料校驗的問題。

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
        Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
        String name = ModelFactory.getNameForParameter(parameter);
        ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;
        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        } else {
            try {
                attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
            } catch (BindException var10) {
                if (this.isBindExceptionRequired(parameter)) {
                    throw var10;
                }

                if (parameter.getParameterType() == Optional.class) {
                    attribute = Optional.empty();
                }

                bindingResult = var10.getBindingResult();
            }
        }

        if (bindingResult == null) {
        	//資料綁定器,進行綁定
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                	//将頁面送出過來的資料封裝到JavaBean的屬性中
                    this.bindRequestParameters(binder, webRequest);
                }

                this.validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }

            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }

            bindingResult = binder.getBindingResult();
        }

        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);
        return attribute;
    }

           

WebDataBinder

類是資料綁定器,繼承于

DataBinder

類。負責資料綁定工作,負責處理資料類型轉換、資料格式化、資料校驗的問題。

DataBinder

類有一個

ConversionService

接口屬性,負責資料類型的轉換以及格式化功能,

ConversionService

中有非常多的

converter

,不同類型的轉換和格式化用不同的

converter

來轉換。

@Nullable
    private ConversionService conversionService;
           
SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

validate()

方法負責資料校驗工作,其中

bindingResult

對象負責儲存以及解析資料綁定期間資料校驗産生的錯誤。

public void validate() {
        Object target = this.getTarget();
        Assert.state(target != null, "No target to validate");
        BindingResult bindingResult = this.getBindingResult();
        Iterator var3 = this.getValidators().iterator();

        while(var3.hasNext()) {
            Validator validator = (Validator)var3.next();
            validator.validate(target, bindingResult);
        }

    }

    public void validate(Object... validationHints) {
        Object target = this.getTarget();
        Assert.state(target != null, "No target to validate");
        BindingResult bindingResult = this.getBindingResult();
        Iterator var4 = this.getValidators().iterator();

        while(true) {
            while(var4.hasNext()) {
                Validator validator = (Validator)var4.next();
                if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                    ((SmartValidator)validator).validate(target, bindingResult, validationHints);
                } else if (validator != null) {
                    validator.validate(target, bindingResult);
                }
            }

            return;
        }
    }
           

二、資料綁定流程

①SpringMVC主架構将

ServletRequest

對象及目标方法的入參執行個體傳遞給

WebDataBinderFactory

執行個體,以建立

WebDataBinder

執行個體對象。

WebDataBinder

調用裝配在 SpringMVC 上下文中的

ConversionService

元件進行資料類型轉換、資料格式化工作。将

Servlet

中的請求資訊填充到入參對象中。

③調用

Validator

元件對已經綁定了請求消息的入參對象進行資料合法性校驗,并最終生成資料綁定結果

BindingData

對象。

④SpringMVC抽取

BindingResult

中的入參對象和校驗錯誤對象,将它們賦給處理方法的響應入參。

SpringMVC通過反射機制對目标處理方法進行解析,将請求消息綁定到處理方法的入參中。資料綁定的核心部件是

DataBinder

,運作機制如下:

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

三、自定義類型轉換器

ConversionService

是一個接口,裡面有

Converter

(轉換器)進行工作

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

在Spring中定義了3種類型的轉換器接口,實作任意一個轉換器接口都可以作為自定義轉換器注冊到

ConversionServiceFactoryBean

  • Convert<S,T>

    将S類型對象轉化為T類型對象。
  • ConvertFactory

    将相同系列多個“同質”

    Convert

    封裝在一起。如果将一種類型的對象轉換為另一種類型及其子類的對象,例如将

    String

    轉換為

    Number

    Number

    子類(

    Integer

    Long

    Double

    等)對象,可以使用這個轉換器工廠類。
  • GenericConverter

    會根據源類對象及目标類對象所在的宿主類的上下文資訊進行類型轉換。

首先實作

Converter

接口,寫一個自定義的類型轉換器

public class MyStringToEmployeeConverter implements Converter<String,Employee> {

    @Autowired
    private Department department;
    
    //自定義的轉換規則
    @Override
    public Employee convert(String s) {
        Employee employee = new Employee();
        if (s.contains("-")){
            String[] split = s.split("-");
            employee.setLastName(split[0]);
            employee.setEmail(split[1]);
            employee.setGender(Integer.parseInt(split[2]));
            employee.setDepartmentId(Integer.parseInt(split[3]));
        }
        return null;
    }
}
           

接下來配置

ConversionService

Converter

ConversionService

中的元件,是以需要将自定義的

Converter

放進

ConversionService

中,并且将

WebDataBinder

ConversionService

設定成包含自定義類型轉換器的

ConversionService

<!--設定conversion-service,使用自定義的類型轉換元件-->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
    
    
    <!--告訴SpringMVC不要用預設的ConversionService,而是用自定義的ConversionService,裡面包含自定義的Converter-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!--converters轉換器是一個set集合,向其中添加自定義的類型轉換器-->
        <property name="converters">
            <set>
                <bean class="component.MyStringToEmployeeConverter"></bean>
            </set>
        </property>
    </bean>
           

頁面

form

表單送出

<form action="quickAdd">
        <%--将員工的所有資訊都寫上,自動封裝對象--%>
        <input name="empinfo" value="[email protected]"/>
        <input type="submit" value="快速添加"/>
    </form>
           

Controller

控制器目标方法

@RequestMapping("/quickAdd")
    public String quickAdd(Employee employee){

        employeeService.save(employee);
        return "redirect:/emps";
    }
           

輸出結果:

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

關于自定義類型轉換器總結為三步:

  • 實作

    Converter

    接口,做一個自定義類型的轉換器
  • 将這個

    Converter

    配置在

    ConversionService

  • 設定

    conversion-service

    讓SpringMVC使用自己配置的類型轉換元件

即使使用了自己配置的轉換器,其它預設的轉換器還在,隻不過是将自定義的轉換器加入了其中,是以執行其它類型的轉換讓仍然可以。

四、

<mvc:annotation-driven>

标簽的解析

<mvc:annotation-driven>

标簽會自動注冊

RequestMappingHandlerMapping

RequestMappingHandlerAdapter

RequestMappingExceptionResolver

這三個Bean。

除此之外還将支援:

  • 支援使用

    ConversionService

    執行個體對表單參數進行類型轉換
  • 支援使用

    @NumberFormat

    注解、

    @DateTimeFormat

    注解完成資料類型的格式化
  • 支援使用

    @Valid

    注解對JavaBean執行個體進行JSR 303驗證
  • 支援使用

    @RequestBody

    @ResponseBody

    注解

SpringMVC會解析這個标簽添加了哪些元件

public BeanDefinition parse(Element element, ParserContext context) {
		Object source = context.extractSource(element);
		XmlReaderContext readerContext = context.getReaderContext();

		CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
		context.pushContainingComponent(compDefinition);

		RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);

		RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
		handlerMappingDef.setSource(source);
		handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		handlerMappingDef.getPropertyValues().add("order", 0);
		handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

		if (element.hasAttribute("enable-matrix-variables")) {
			Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
			handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
		}

		configurePathMatchingProperties(handlerMappingDef, element, context);
		readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);

		RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
		handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);

		RuntimeBeanReference conversionService = getConversionService(element, source, context);
		RuntimeBeanReference validator = getValidator(element, source, context);
		RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

		RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
		bindingDef.setSource(source);
		bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		bindingDef.getPropertyValues().add("conversionService", conversionService);
		bindingDef.getPropertyValues().add("validator", validator);
		bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

		ManagedList<?> messageConverters = getMessageConverters(element, source, context);
		ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
		ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
		String asyncTimeout = getAsyncTimeout(element);
		RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
		ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
		ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");

		RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
		handlerAdapterDef.setSource(source);
		handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
		handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
		handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
		addRequestBodyAdvice(handlerAdapterDef);
		addResponseBodyAdvice(handlerAdapterDef);

		if (element.hasAttribute("ignore-default-model-on-redirect")) {
			Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
			handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
		}
		if (argumentResolvers != null) {
			handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
		}
		if (returnValueHandlers != null) {
			handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
		}
		if (asyncTimeout != null) {
			handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
		}
		if (asyncExecutor != null) {
			handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
		}

		handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
		handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
		readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);

		RootBeanDefinition uriContributorDef =
				new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
		uriContributorDef.setSource(source);
		uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
		uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
		String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
		readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);

		RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
		csInterceptorDef.setSource(source);
		csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
		RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
		mappedInterceptorDef.setSource(source);
		mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
		mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
		String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);

		RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
		methodExceptionResolver.setSource(source);
		methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
		methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
		methodExceptionResolver.getPropertyValues().add("order", 0);
		addResponseBodyAdvice(methodExceptionResolver);
		if (argumentResolvers != null) {
			methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
		}
		if (returnValueHandlers != null) {
			methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
		}
		String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);

		RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
		statusExceptionResolver.setSource(source);
		statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		statusExceptionResolver.getPropertyValues().add("order", 1);
		String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);

		RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
		defaultExceptionResolver.setSource(source);
		defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		defaultExceptionResolver.getPropertyValues().add("order", 2);
		String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);

		context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
		context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
		context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
		context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
		context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
		context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
		context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));

		// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
		MvcNamespaceUtils.registerDefaultComponents(context, source);

		context.popAndRegisterContainingComponent();

		return null;
	}
           

一般來說,隻要請求出現問題加上

<mvc:annotation-driven/>

就可以解決大多數問題。

  • 對于

    <mvc:default-servlet-handler></mvc:default-servlet-handler>

    <mvc:annotation-driven></mvc:annotation-driven>

    這兩個标簽,如果都沒有配置,動态資源(

    @RequestMapping

    映射的資源)都能通路,但是靜态資源(.html, .js, .img)都不能通路。

handlerMappings

handlerMappings

裡面儲存了請求映射資訊,其中是

DefaultAnnotationHandlerMapping

在工作,請求一過來就會挨個周遊所有的

HandlerMapping

看哪個儲存了發送來的請求的映射資訊。先檢視

BeanNameURLHandlerMapping

,而

BeanNameURLHandlerMapping

裡面是

{}

空的,是以就檢視下一個

DefaultAnnotationHandlerMapping

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

DefaultAnnotationHandlerMapping

裡面有一個

handlerMap

,儲存了哪個請求對應哪個類可以處理,比對成功就可以處理請求。如果處理成功,就說明動态資源能夠通路,能通路的原因就是

DefaultAnnotationHandlerMapping

中的

handlerMap

儲存了每一個請求的對應的處理類,即儲存了每一個資源的映射資訊。

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

但是靜态資源不能通路,原因是

handlerMap

中并沒有儲存靜态資源請求的映射資訊,是以當js或者html請求過來時,并不知道哪個處理器類可以處理。

handlerAdapters

handlerAdapters

是方法執行的擴充卡,其中

AnnotationMethodHandlerAdapter

幫我們來執行目标方法

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化
  • 如果隻配置

    <mvc:default-servlet-handler></mvc:default-servlet-handler>

    靜态資源可以通路,但是動态資源無法通路。

HandlerMappings

對比兩個标簽都沒有配置的情況,

HandlerMappings

中多了一個

SimpleUrlHandlerMapping

,卻少了儲存動态請求映射的

DefaultAnnotationHandlerMapping

,是以就無法通路動态資源。

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

SimpleUrlHandlerMapping

handlerMap

/**

是把所有的請求都交給了

DefaultServletHttpRequestHandler

,也就是都交給了Tomcat來處理。

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化
SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

是以,

SimpleUrlHandlerMapping

替換了

DefaultAnnotationHandlerMapping

,将所有的請求直接交給了Tomcat處理,而一些動态資源的請求映射并沒有在Tomcat中配置,是以就隻能通路靜态資源,動态資源無法通路。

handlerAdapters

少了

AnnotationMethodHandlerAdapter

,是以也就不會運作目标方法去處理請求

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化
  • 如果兩個配置都加上,靜态和動态資源就都能通路。

HandlerMappings

如果兩個配置都加上,

HandlerMappings

中就有3個

handlerMapping

SimpleUrlHandlerMapping

将請求直接交給Tomcat,是以可以通路靜态資源。

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

而多了一個

RequestMappingHandlerMapping

并放在第一位,它裡面有一個

urlMap

,儲存了請求的相關資訊。

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

RequestMappingHandlerMapping

裡面還有一個

handlerMethods

,儲存了每一個請求用哪個方法來處理,是以動态資源可以映射到,進而也就可以通路動态資源了。

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

handlerAdapters

多了

RequestMappingHandlerAdapter

,替代了原來的

AnnotationMethodHandlerAdapter

SpringMVC資料綁定一、資料綁定二、資料綁定流程三、自定義類型轉換器四、&lt;mvc:annotation-driven&gt;标簽的解析五、資料格式化

五、資料格式化

日期格式化

如果頁面送出的資料格式不正确,我們希望報400錯誤,并且制定日期格式為

yyyy-MM-dd

@DateTimeFormat(pattern = "yyyy-MM-dd")
	private Date birth;
           

ConversionServiceFactoryBean

建立的

ConversionService

元件是沒有格式化器存在的,要麼去掉自定義類型轉換器,使用預設的轉換器;要麼使用

FormattingConversionServiceFactoryBean

<!--設定conversion-service,使用自定義的類型轉換元件-->
    <mvc:annotation-driven conversion-service="formattingConversionService"></mvc:annotation-driven>


    <!--告訴SpringMVC不要用預設的ConversionService,而是用自定義的ConversionService,裡面包含自定義的Converter-->
    <bean id="formattingConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <!--converters轉換器是一個set集合,向其中添加自定義的類型轉換器-->
        <property name="converters">
            <set>
                <bean class="component.MyStringToEmployeeConverter"></bean>
            </set>
        </property>
    </bean>
           

數值格式化

可以指定頁面輸出數值的格式

@NumberFormat(pattern = "#,###,###.####")
	private Double salary;
           

繼續閱讀