使用 Spring 的表单标签
通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显
form 标签
• 一般情况下,通过 GET 请求获取表单页面,而通过POST 请求提交表单页面,因此获取表单页面和提交表单页面的 URL 是相同的。只要满足该最佳条件的规则,<form:form> 标签就无需通过 action 属性指定表单提交的 URL
• 可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,则默认从 request 域对象中读取
command 的表单 bean,如果该属性值也不存在,则会发生错误。
• SpringMVC 提供了多个表单组件标签,如
<form:input/>、<form:select/> 等,用以绑定表单字段的属性值,它们的共有属性如下:
path:表单字段,对应 html 元素的 name 属性,支持级联属性
–htmlEscape:是否对表单值的 HTML 特殊字符进行转换,默认值为 true
–cssClass:表单组件对应的 CSS 样式类名
–cssErrorClass:表单组件的数据存在错误时,采取的 CSS 样式
form:input、form:password、form:hidden、form:textarea
:对应 HTML 表单的 text、password、hidden、textarea 标签
form:radiobutton:单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中
• form:radiobuttons:单选框组标签,用于构造多个单选框
– items:可以是一个 List、String[] 或 Map
– itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
– itemLabel:指定 radio 的 label 值
– delimiter:多个单选框可以通过 delimiter 指定分隔符
• form:checkbox:复选框组件。用于构造单个复选框
• form:checkboxs:用于构造多个复选框。使用方式同 form:radiobuttons 标签
• form:select:用于构造下拉框组件。使用方式同 form:radiobuttons 标签
• form:option:下拉框选项组件标签。使用方式同 form:radiobuttons 标签
• form:errors:显示表单组件或数据校验所对应的错误
– <form:errors path= “ *” /> :显示表单所有的错误
– <form:errors path= “ user*” /> :显示所有以 user 为前缀的属性对应的错误
– <form:errors path= “ username” /> :显示特定表单对象属性的错误
处理静态资源
• 一般 REST 风格的资源URL 不希望带 .html 或 .do 等后缀
• 若将 DispatcherServlet 请求映射配置为 /,则 Spring MVC 将捕获 WEB 容器的所有请求,包括静态资源的请求, SpringMVC 会将他们当成一个普通请求处理,因找不到对应处理器将导致错误。
• 可以在 SpringMVC 的配置文件中配置 <mvc:default-servlethandler/> 的方式解决静态资源的问题:
– <mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB
应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由
DispatcherServlet 继续处理
– 一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 defaultservlet-name 属性显式指定
• 1. Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
• 2. DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
• 3. 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果
BindingData 对象
• 4. Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
• Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是
DataBinder,运行机制如下:
数据转换
• Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。
• ConversionService converters =
– java.lang.Boolean -> java.lang.String :
org.springframework.core.convert.support.ObjectToStringConverter@f874ca
– java.lang.Character-> java.lang.Number : CharacterToNumberFactory@f004c9
– java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961
– java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a
– java.lang.Number->java.lang.Character: NumberToCharacterConverter@1482ac5
– java.lang.Number->java.lang.Number: NumberToNumberConverterFactory@126c6f
– java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8
– java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626
– java.lang.String->java.lang.Character: StringToCharacterConverter@1143800
– java.lang.String-> java.lang.Enum : StringToEnumConverterFactory@1bba86e
– java.lang.String->java.lang.Number: StringToNumberConverterFactory@18d2c12
– java.lang.String -> java.util.Locale : StringToLocaleConverter@3598e1
– java.lang.String->java.util.Properties: StringToPropertiesConverter@c90828
– java.lang.String -> java.util.UUID : StringToUUIDConverter@a42f23
– java.util.Locale -> java.lang.String : ObjectToStringConverter@c7e20a
– java.util.Properties->java.lang.String: PropertiesToStringConverter@367a7f
– java.util.UUID -> java.lang.String : ObjectToStringConverter@112b07f ……
自定义类型转换器
• ConversionService 是 Spring 类型转换体系的核心接口。
• 可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService. Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及
Spring MVC 处理方法入参绑定等场合使用它进行数据的转换
• 可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器
Spring 支持的转换器
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到
ConversionServiceFactroyBean 中:
– Converter<S,T>:将 S 类型对象转为 T 类型对象
– ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类
(Integer、Long、Double 等)对象)可使用该转换器工厂类
– GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
自定义转换器示例
<mvc:annotation-driven conversion-service= “conversionService”/> 会将自定义的 ConversionService 注册到 Spring MVC 的上下文中
关于 mvc:annotation-driven
• <mvc:annotation-driven />会自动注册RequestMappingHandlerMapping
、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver 三个bean。
• 还将提供以下支持:
– 支持使用 ConversionService 实例对表单参数进行类型转换
– 支持使用@NumberFormat annotation、@DateTimeFormat注解完成数据类型的格式化
– 支持使用@Valid注解对JavaBean实例进行JSR303验证
– 支持使用@RequestBody和@ResponseBody注解
既没有配置<mvc:default-servlet-handler/>也没有配置<mvc:annotation-driven/>
@InitBinder
• 由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
• @InitBinder方法不能有返回值,它必须声明为void。
• @InitBinder方法的参数通常是是 WebDataBinder
数据绑定流程
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是
DataBinder,运行机制如下:
数据格式化
• 对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
• Spring 在格式化模块中定义了一个实现ConversionService 接口的FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能
• FormattingConversionService 拥有一个
FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者
• FormattingConversionServiceFactroyBean 内部已经注册了 :
– NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
– JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解
• 装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/> 默认创建的 ConversionService 实例即为
FormattingConversionServiceFactroyBean
日期格式化
@DateTimeFormat 注解可对 java.util.Date、 java.util.Calendar、java.long.Long 时间类型进行标注:
– pattern 属性:类型为字符串。指定解析/格式化字段数据的模式,如:”yyyy-MM-dd hh:mm:ss”
– iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用)-默认、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
– style 属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、-:忽略日期或时间格式
数值格式化
@NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性:
– style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT(
百分数类型)
– pattern:类型为 String,自定义样式,如patter="#,###";
JSR 303
• JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 中 .
• JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
Spring MVC 数据校验
• Spring 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。
• Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验
• Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要
在 Spring 容器中定义了一个 LocalValidatorFactoryBean
,即可将其注入到需要数据校验的 Bean 中。
• Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者的 jar 包放到类路径下。
• <mvc:annotation-driven/> 会默认装配好一个
LocalValidatorFactoryBean,通过在处理方法的入参上标注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作
• 在已经标注了 JSR303 注解的表单/命令对象前标注一个
@Valid,Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验
• Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中
,这个保存校验结果的入参必须是 BindingResult 或
Errors 类型,这两个类都位于
org.springframework.validation 包中
• 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参
• Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)
• BindingResult 扩展了 Errors 接口
在目标方法中获取校验结果
• 在表单/命令对象类的属性中标注校验注解,在处理方法对
应的入参前添加 @Valid,Spring MVC 就会实施校验并将校验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。
• 常用方法:
– FieldError getFieldError(String field)
– List<FieldError> getFieldErrors()
– Object getFieldValue(String field)
– Int getErrorCount()
在页面上显示错误
• Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”
• 即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。
• 隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息
• 在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息
提示消息的国际化
• 每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。
• 当一个属性校验失败后,校验框架会为该属性生成 4 个消
息代码,这些代码以校验注解类名为前缀,结合
modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如 User 类中的 password 属性标准了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:
– Pattern.user.password
– Pattern.password
– Pattern.java.lang.String
• 当(–)使用S(Pattern) pring MVC 标签显示错误消息时, Spring MVC 会查看
WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
处理 JSON
• 1. 加入 jar 包:
• 2. 编写目标方法,使其返回 JSON 对应的对象或集合
3.在方法上添加 @ResponseBody 注解
HttpMessageConverter<T>
• HttpMessageConverter<T> 是 Spring的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T
)输出为响应信息
• HttpMessageConverter<T>接口定义的方法:
– Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器
可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对
象,同时指定支持 MIME 类型(text/html,applaiction/json等)
– Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在
MediaType 中定义。
– LIst<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。
– T read(Class<? extends T> clazz,HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。
– void write(T t,MediaType contnetType,HttpOutputMessgae
outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。
HttpMessageConverter<T> 的实现类
HttpMessageConverter<T>
DispatcherServlet 默认装配
RequestMappingHandlerAdapter ,而
RequestMappingHandlerAdapter 默认装配如下
HttpMessageConverter:
加入Jackson jar包后,RequestMappingHandlerAdapter 装配的 HttpMessageConverter 如下:
使用 HttpMessageConverter<T>
• 使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
– 使用 @RequestBody / @ResponseBody 对处理方法进行标注
– 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值
• 当控制器处理方法使用到 @RequestBody/@ResponseBody 或
HttpEntity<T>/ResponseEntity<T> 时, Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或
泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的
HttpMessageConverter 将报错
• @RequestBody 和 @ResponseBody 不需要成对出现
@RequestBody、@ResponseBody 示例
国际化概述
• 默认情况下,SpringMVC 根据 Accept-Language 参数判断客户端的本地化类型。
• 当接受到请求时,SpringMVC 会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。
• SpringMVC 还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。
SessionLocaleResolver & LocaleChangeInterceptor
本地化解析器和本地化拦截器
• AcceptHeaderLocaleResolver:根据 HTTP 请求头的
Accept-Language 参数确定本地化类型,如果没有显式定义本地化解析器, SpringMVC 使用该解析器。
• CookieLocaleResolver:根据指定的 Cookie 值确定本地化类型
• SessionLocaleResolver:根据 Session 中特定的属性确定本地化类型
• LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型。
文件上传
• Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个
MultipartResolver 实现类:CommonsMultipartResovler
• Spring MVC 上下文中默认没有装配 MultipartResovler,因
此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver
配置 MultipartResolver
• defaultEncoding: 必须和用户 JSP 的 pageEncoding 属性一致,以便正确解析表单的内容
• 为了让 CommonsMultipartResovler 正确工作,必须先将 Jakarta Commons FileUpload 及 Jakarta Commons io 的类包添加到类路径下。
文件上传示例
自定义拦截器
Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口
– preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
– postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
– afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
拦截器方法执行顺序
配置自定义拦截器
异常处理
• Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
• SpringMVC 提供的 HandlerExceptionResolver 的实现类
HandlerExceptionResolver
DispatcherServlet 默认装配的 HandlerExceptionResolver :
– 没有使用 <mvc:annotation-driven/> 配置:
– 使用了 <mvc:annotation-driven/> 配置:
ExceptionHandlerExceptionResolver
• 主要处理 Handler 中用 @ExceptionHandler 注解定义的方法。
• @ExceptionHandler 注解定义的方法优先级问题:例如发生的是NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler
注解方法,即标记了 RuntimeException 的方法
• ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话,会找
@ControllerAdvice 中的@ExceptionHandler 注解方法
ResponseStatusExceptionResolver
ExceptionHandlerExceptionResolver 不解析述异常。由于触发的异常 UnauthorizedException 带有@ResponseStatus 注解。因此会被ResponseStatusExceptionResolver 解析到。最后响应HttpStatus.UNAUTHORIZED 代码给客户端。 HttpStatus.UNAUTHORIZED 代表响应码401,无权限。关于其他的响应码请参考 HttpStatus 枚举类型源码。
对一些特殊的异常进行处理,比如
NoSuchRequestHandlingMethodException、HttpReques tMethodNotSupportedException、HttpMediaTypeNotSuppo rtedException、HttpMediaTypeNotAcceptableException 等。
SimpleMappingExceptionResolver
如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
Bean 被创建两次?
Spring 的 IOC 容器不应该扫描 SpringMVC 中的 bean, 对应的SpringMVC 的 IOC 容器不应该扫描 Spring 中的 bean
在 Spring MVC 配置文件中引用业务层的 Bean
• 多个 Spring IOC 容器之间可以设置为父子关系,以实现良好的解耦。
• Spring MVC WEB 层容器可作为 “业务层” Spring 容器的子容器:即 WEB 层容器可以引用业务层容器的 Bean,而业务层容器却访问不到 WEB 层容器的 Bean