Spring MVC -- 数据绑定和表单标签库中我们已经见证了数据绑定的威力,并学习了如何使用表单标签库中的标签。但是,Spring的数据绑定并非没有任何限制。有案例表明,Spring在如何正确绑定数据方面是杂乱无章的。下面举两个例子:

1)Spring MVC -- 数据绑定和表单标签库中的tags-demo应用中,如果在/input-book页面输入一个非数字的价格,然后点击”Add book“,将会跳转到/save-book页面:

Spring MVC -- 转换器和格式化

然而事实上/save-book页面并不会加载成功:
Spring MVC -- 转换器和格式化

这主要是因为无法将表单输入的价格从String类型绑定到Model属性"book"所对应的Book对象的price属性上(表单输入价格是445edfg,price是BigDecimal类型,类型转换失败)。

五月 09, 2019 8:47:23 上午 org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver logException
警告: Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'book' on field 'price': rejected value [445edfg]; 
codes [typeMismatch.book.price,typeMismatch.price,typeMismatch.java.math.BigDecimal,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [book.price,price];
arguments []; default message [price]];
default message [Failed to convert property value of type 'java.lang.String' to required type 'java.math.BigDecimal' for property 'price';
nested exception is java.lang.NumberFormatException]]

2)Spring总是试图用默认的语言区域将日期绑定到java.util.Data。假设想让Spring使用不同的日期样式,就需要使用一个Converter(转换器)或者Formatter(格式化)来协助Spring完成。

本篇博客将会讨论Converter和Formatter的内容。这两者均可用于将一个对象的类型转换成另一种类型。Converter是通用元件,可以在应用程序的任意层中使用,而Formatter则是专门为Web层设计的。

本篇博客有两个示例程序:converter-demo和formatter-demo。两者都使用一个messageSource bean来帮助显示受控的错误消息,这个bean的功能在本篇博客只会简单的提到,后面的博客会详细介绍。

一 Converter接口

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

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

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.convert.converter;

import org.springframework.lang.Nullable;

/**
 * A converter converts a source object of type {@code S} to a target of type {@code T}.
 *
 * <p>Implementations of this interface are thread-safe and can be shared.
 *
 * <p>Implementations may additionally implement {@link ConditionalConverter}.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <S> the source type
 * @param <T> the target type
 */
@FunctionalInterface
public interface Converter<S, T> {

    /**
     * Convert the source object of type {@code S} to target type {@code T}.
     * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
     * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
     * @throws IllegalArgumentException if the source cannot be converted to the desired target type
     */
    @Nullable
    T convert(S source);

}

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

public class LongToLocalDateConverter implements Converter<Long, LocalDate> {
}

在类实体中,需要编写一个来自Converter接口的convert方法实现,这个方法的签名如下:

    T convert(S source);

二 converter-demo范例

本小节将会创建一个converter-demo的web应用。用来演示Converter的使用。

1、目录结构

Spring MVC -- 转换器和格式化

2、Controller类

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 {
    
    //访问URL:/add-employee  添加员工信息
    @RequestMapping(value="/add-employee")
    public String inputEmployee(Model model) {
        model.addAttribute("employee",new Employee());
        return "EmployeeForm";
    }

    //访问URL:/save-employee  保存并显示信息   将表单提交的数据绑定到employee对象的字段上
    //@ModelAttribute 会将employee对象添加到Model对象上,用于视图显示
    @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():对应着动作/add-employee,该函数执行完毕,加载EmployeeForm.jsp页面;
  • saveEmployee():对应着动作/save-employee,该函数执行完毕,加载EmployeeDetails.jsp页面;

注意:saveEmployee()方法的BindingResult参数中放置了Spring的所有绑定错误。该方法利用BindingResult记录所有绑定错误。绑定错误也可以利用errors标签显示在一个表单中,如EmployeeForm.jsp页面所示。

3、视图、Employee类

EmployeeForm.jsp:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Add Employee Form</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>

<div id="global">
<form:form modelAttribute="employee" action="save-employee" method="post">
    <fieldset>
        <legend>Add an employee</legend>
        <p>
            <label for="firstName">First Name: </label>
            <form:input path="firstName" tabindex="1"/>
        </p>
        <p>
            <label for="lastName">Last Name: </label>
            <form:input path="lastName" tabindex="2"/>
        </p>
        <p>
            <form:errors path="birthDate" cssClass="error"/>
        </p>
        <p>
            <label for="birthDate">Date Of Birth (MM-dd-yyyy): </label>
            <form:input path="birthDate" tabindex="3" />
        </p>
        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Add Employee">
        </p>
    </fieldset>
</form:form>
</div>
</body>
</html>

可以看到表单数据被绑定到了模型属性"employee"上,"employee"属性保存着一个Employee对象,其中输入出生日期信息的input标签被绑定到了Employee对象的birthDate属性上。

此外,我们使用了表单标签errors:

  <form:errors path="birthDate" cssClass="error"/>

errors标签用于渲染一个或多个HTML的<span></span>元素。代码中errors标签显示了一个与表单支持对象的birthDate属性相关的字段错误,并且设置span的class属性为"error",从而可以通过class选择器设置该元素的样式

EmployeeDetails.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Save Employee</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
    <h4>The employee details have been saved.</h4>
    <p>
        <h5>Details:</h5>
        First Name: ${employee.firstName}<br/>
        Last Name: ${employee.lastName}<br/>
        Date of Birth: ${employee.birthDate}
    </p>
</div>
</body>
</html>

main.css:

#global {
    text-align: left;
    border: 1px solid #dedede;
    background: #efefef;
    width: 560px;
    padding: 20px;
    margin: 30px auto;
}

form {
  font:100% verdana;
  min-width: 500px;
  max-width: 600px;
  width: 560px;
}

form fieldset {
  border-color: #bdbebf;
  border-width: 3px;
  margin: 0;
}

legend {
    font-size: 1.3em;
}

form label { 
    width: 250px;
    display: block;
    float: left;
    text-align: right;
    padding: 2px;
}

#buttons {
    text-align: right;
}
#errors, li {
    color: red;
}
.error {
    color: red;
    font-size: 9pt;    
}
View Code

相关文章:

  • 2021-10-17
  • 2021-05-20
  • 2021-11-20
  • 2021-09-08
  • 2021-06-30
  • 2021-11-29
  • 2022-12-23
  • 2022-01-01
猜你喜欢
  • 2022-12-23
  • 2021-12-09
  • 2021-03-31
  • 2021-07-19
  • 2021-11-05
  • 2022-12-23
  • 2022-01-18
相关资源
相似解决方案