天天看点

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;
           

继续阅读