【720科技SpringMVC】第六课:转换器和格式化、验证器

内容关键词:转换、格式化、验证器

知识来源 720科技(张森鹏)

一、知识笔记

Spring Converter 是可以将一种类型转换成另一种类型的一个对象。 例如,用户输入的日期可能有许多种形式,如“December 252014”“12/25/2014”和“2014-12-25”,这些都表示同一个日期。默认情况下, Spring 会期待用户输入的日期样式与当前语言区域的日期样式相同。例如,对于美国的用户而言,就是月//年格式。如果希望 Spring 在将输入的日期字符串绑定到 LocalDate 时,使用不同的日期样式,则需要编写一个 Converter,才能将字符串转换成日期。 java.time.LocalDate 类是 Java 8 的一个新类型,用来替代 java.util.Date。 还需使用新的 Date/Time API 来替换旧有的 Date Calendar 类。

Formatter 就像 Converter 一样,也是将一种类型转换成另一种类型。但是, Formatter 的源类型必须是一个 String,而 Converter 则适用于任意的源类型。 Formatter 更适合 Web 层,而Converter 则可以用在任意层中。为了转换 Spring MVC 应用程序表单中的用户输入,始终应该选择 Formatter,而不是 Converter

输入验证是 Spring 处理的最重要 Web 开发任务之一。在 Spring MVC 中,有两种方式可以验证输入,即利用 Spring 自带的验证框架,或者利用 JSR 303 实现。 验证器作用于对象级。它决定某一个对象中的所有字段是否均是有效的,以及是否遵循某些规则。 如果一个应用程序中既使用了 Formatter,又有 validator(验证器),那么,应用中的事件顺序是这样的:在调用 Controller 期间,将会有一个或者多个 Formatter,试图将输入字符串转换成 domain 对象中的 field 值。一旦格式化成功,验证器就会介入。例如, Order 对象可能会有一个 shippingDate 属性(其类型显然为 LocalDate),它的值绝对不可能早于今天的日期。当调用 OrderController 时, DateFormatter 会将字符串转化成 Date,并将它赋予 Order 对象的 shippingDate 属性。如果转换失败,用户就会被转回到前一个表单。如果转换成功,则会调用验证器,查看 shippingDate 是否早于今天的日期。


二、重要知识

1、Converter

为了创建 Converter,必须编写实现 org.springframework.core.convert.converter. Converter接口的一个 Java 类。这个接口的声明如下:

public interface Converter<S, T>

这里的 S 表示源类型, T 表示目标类型。例如,为了创建一个可以将 Long 转换成 DateConverter,要像下面这样声明 Converter 类:

public class MyConverter implements Converter<Long, LocalDate> {
}
在类实体中,需要编写一个来自 Converter 接口的 convert 方法实现。这个方法的签名
如下:
T convert(S source)
例如,清单 6.1 展示了一个适用于任意日期样式的 Converter
清单 6.1 String To LocalDate Converter
package converter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import org.springframework.core.convert.converter.Converter;
public class StringToLocalDateConverter implements Converter<String,
LocalDate> {
private String datePattern;
public StringToLocalDateConverter(String datePattern) {
this.datePattern = datePattern;
}
@Override
public LocalDate convert(String s) {
try {
return LocalDate.parse(s, DateTimeFormatter.ofPattern(
datePattern));
} catch (DateTimeParseException e) {
// the error message will be displayed in <form:errors>
throw new IllegalArgumentException(
"invalid date format. Please use this pattern\""
+ datePattern + "\"");
}
}
}

注意清单 6.1 中的 Converter 方法,它利用传给构造器的日期样式,将一个 String 转换成LocalDate。为了使用 Spring MVC 应用程序中定制的 Converter,需要 在 Spring MVC 配置文件中编写一个名为 conversionService beanbean 的类名称必须为 org.springframework.context.support.ConversionServiceFactoryBean。这个 bean 必须包含一个 converters 属性,它将列出要在应用程序中使用的所有定制 Converter。例如,下面的 bean 声明在清单 6.1 中注册了StringToDateConverter
<bean id="conversionService" class="org.springframework.context.support.
ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="converter.StringToLocalDateConverter">
<constructor-arg type="java.lang.String"
value="MM-dd-yyyy"/>
</bean>
</list>
</property>
</bean>
随后,要给 annotation-driven 元素的 conversion-service 属性赋值 bean 名称(本例中是conversionService),如下所示:
<mvc:annotation-driven
conversion-service="conversionService"/>
converter-demo 是一个范例应用程序,它利用 StringToLocalDateConverter String 转换成Employee 对象的 birthDate 属性。 Employee 类如清单 6.2 所示。
清单 6.2 Employee
package domain;
import java.io.Serializable;
import java.time.LocalDate;
public class Employee implements Serializable {
private static final long serialVersionUID = -908L;
private long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
private int salaryLevel;

// getters and setters not shown
}
清单 6.3 中的 EmployeeController 类是 domain 对象 Employee 的控制器。
清单 6.3 converter-demo 中的 EmployeeController
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import domain.Employee;
@Controller
public class EmployeeController {
@RequestMapping(value="/add-employee")
public String inputEmployee(Model model) {
model.addAttribute(new Employee());
return "EmployeeForm";
}
@RequestMapping(value="/save-employee")
public String saveEmployee(@ModelAttribute Employee employee,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
return "EmployeeForm";
}
// save employee here
model.addAttribute("employee", employee);
return "EmployeeDetails";
}
}
EmployeeController 类 有 inputEmployee saveEmployee 两 个 处 理 请求 的方 法 。inputEmployee 方法返回清单 6.4 中的 EmployeeForm.jsp 页面。 saveEmployee 方法取出在提交mployee 表单时创建的一个 Employee 对象。有了 StringToLocalDateConverter converter,就不需要劳驾控制器类将字符串转换成日期了。

2、Formatter

为了创建 Formatter,要编写一个实现 org.springframework.format.Formatter 接口的 Java 类。下面是这个接口的声明:
public interface Formatter<T>
这里的 T 表示输入字符串要转换的目标类型。该接口有 parse print 两个方法,所有实现都必须覆盖它们。
T parse(String text, java.util.Locale locale)
String print(
T object, java.util.Locale locale)
parse 方法利用指定的 Locale 将一个 String 解析成目标类型。 print 方法与之相反,它返回目标对象的字符串表示法。

例如, formatter-demo 应用程序中用一个 LocalDateFormatter String 转换成 Date。其作用与 converter-demo 中的 StringToLocalDateConverter 一样。

DateFormatter 类如清单 6.5 所示。

清单 6.5 Local DateFormatter
package formatter;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Locale;
import org.springframework.format.Formatter;
public class LocalDateFormatter implements Formatter<LocalDate> {
private DateTimeFormatter formatter;
private String datePattern;
public LocalDateFormatter(String datePattern) {
this.datePattern = datePattern;
formatter= DateTimeFormatter.ofPattern(datePattern);
}
@Override
public String print(LocalDate date, Locale locale) {
return date.format(formatter);
}
@Override
public LocalDate parse(String s, Locale locale)
throws ParseException {
try {
return LocalDate.parse(s,
DateTimeFormatter.ofPattern(datePattern));
} catch (DateTimeParseException e) {
// the error message will be displayed in <form:errors>
throw new IllegalArgumentException(
"invalid date format. Please use this pattern\""
+ datePattern + "\"");

}
}
}
为了在 Spring MVC 应用程序中使用 Formatter,需要利用名为 conversionService bean对它进行注册。 bean 的类名称必须为 org.springframework.format.support.FormattingConversionServiceFactoryBean。这与 converter-demo 中用于注册 converter 的类不同。这个 bean 可以用一个 formatters 属性注册 formatter,用一个 converters 属性注册 converter。清单 6.6 展示了formatter-demo Spring 配置文件。
清单 6.6 formatter-demo Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="controller"/>
<context:component-scan base-package="formatter"/>
<mvc:annotation-driven conversion-service="conversionService"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/*.html" location="/"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.
InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="conversionService"
class="org.springframework.format.support.
FormattingConversionServiceFactoryBean">
<property name="formatters">

<set>
<bean class="formatter.LocalDateFormatter">
<constructor-arg type="java.lang.String"
value="MM-dd-yyyy" />
</bean>
</set>
</property>
</bean>
</beans>
注意,还需要给这个 Formatter 添加一个 component-scan 元素。

3、用Registrar注册Formatter

注册 Formatter 的另一种方法是使用 Registrar。例如,清单 6.7 就是注册 DateFormatter 的一个例子。
清单 6.7 MyFormatterRegistrar
package formatter;
import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry;
public class MyFormatterRegistrar implements FormatterRegistrar {
private String datePattern;
public MyFormatterRegistrar(String datePattern) {
this.datePattern = datePattern;
}
@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateFormatter(datePattern));
// register more formatters here
}
}
有了 Registrar,就不需要在 Spring MVC 配置文件中注册任何 Formatter 了,只在 Spring配置文件中注册 Registrar 就可以了,如清单 6.8 所示。
清单 6.8 springmvc-config.xml 文件中注册 Registrar
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/springcontext.xsd">
<context:component-scan base-package="controller" />
<context:component-scan base-package="formatter" />
<mvc:annotation-driven conversion-service="conversionService" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/*.html" location="/" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.
InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="conversionService"
class="org.springframework.format.support.
FormattingConversionServiceFactoryBean">
<property name="formatterRegistrars">
<set>
<bean class="formatter.MyFormatterRegistrar">
<constructor-arg type="java.lang.String"
value="MM-dd-yyyy" />
</bean>
</set>
</property>
</bean>
</beans>

4、选择 Converter, 还是 Formatter

Converter 是一般工具,可以将一种类型转换成另一种类型。例如,将 String 转换成LocalDate,或者将 Long 转换成 LocalDateConverter 既可以用在 Web 层,也可以用在其他层中。
Formatter 只能将 String 转换成另一种 Java 类型。例如,将 String 转换成 LocalDate,但它不能将 Long 转换成 LocalDate。因此, Formatter 适用于 Web 层。为此,在 Spring MVC 应用程序中,选择 Formatter 比选择 Converter 更合适。


5、Spring 的 Validator 范例

spring-validator 应用程序中包含一个名为 ProductValidator 的验证器,用于验证 Product

象。 Spring-validator Product 类如清单 7.2 所示。 ProductValidator 类如清单 7.3 所示。

清单 

a、 Product 类

package domain;
import java.io.Serializable;
import java.math,BigDecimal;
import java.time.LocalDate;
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String description;
private BigDecimal price;
private LocalDate productionDate;
//getters and setters methods not shown
}
b、 ProductValidator 类
package validator;
import java.math,BigDecimal;
import java.time.LocalDate;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import domain.Product;
public class ProductValidator implements Validator {
@Override
public boolean supports(Class<?> klass) {
return Product.class.isAssignableFrom(klass);
}
@Override
public void validate(Object target, Errors errors) {
Product product = (Product) target;
ValidationUtils.rejectIfEmpty(errors, "name",
"productname.required");
ValidationUtils.rejectIfEmpty(errors, "price","price.required");
ValidationUtils.rejectIfEmpty(errors, "productionDate",
"productiondate.required");
BigDecimal price = product.getPrice();
if (price != null && price.compareTo(BigDecimal.ZERO) < 0) {
errors.rejectValue("price", "price.negative");
}
Local Date productionDate = product.getProductionDate();
if (productionDate != null) {
if (productionDate.isAfter(LocalDate.now())) {
errors.rejectValue("productionDate",
"productiondate.invalid");
}
}
}
}
ProductValidator 验证器是一个非常简单的验证器。它的 validate 方法会检验 Product 是否

有名称和价格,并且价格是否不为负数。它还会确保生产日期不晚于今天。

三、学习参考

https://m.2cto.com/kf/201707/654979.html

https://blog.csdn.net/slowly_come_faster

SpringMVC学习指南



猜你喜欢

转载自blog.csdn.net/qq_41950122/article/details/80101914