在Spring MVC应用程序中使用Bean Validation 1.1获得更好的错误消息

在许多新功能中, Bean Validation 1.1引入了使用统一表达式语言(EL)表达式的错误消息插值。 这允许基于条件逻辑来定义错误消息,还可以启用高级格式化选项 。 添加到Spring MVC应用程序后,您可以非常简单地显示更友好的错误消息。

在本文的第一部分中,我将简短描述使用EL表达式的消息插值,在第二部分中,我们将使用Spring Boot和Thymeleaf构建一个运行在Tomcat 8上的简单Web应用程序。

消息中的EL表达式–示例

为了可视化用EL表达式更好地进行消息内插的一些可能性,我将使用以下类:

public class Bid {
    private String bidder;
    private Date expiresAt;
    private BigDecimal price;
}

示例1:当前验证的值

验证引擎将当前已验证的值在EL上下文中提供为validatedValue

@Size(min = 5, message = "\"${validatedValue}\" is too short.")
private String bidder;

对于等于“约翰”的竞标者,错误消息将是:

“约翰”太短了。

示例2:条件逻辑

错误消息中可能带有EL表达式的条件逻辑。 在以下示例中,如果经过验证的竞标者的长度短于2,我们将显示不同的消息:

@Size(min = 5, message = "\"${validatedValue}\" is ${validatedValue.length() < 2 ? 'way' : ''} too short.")
private String bidder;

当投标人等于“ J”时,消息将是:

“ J”太短了。

当出价者等于“ John”时,消息将是:

“约翰”太短了。

示例3:格式化程序

验证引擎使formatter对象在EL上下文中可用。 formatter行为为java.util.Formatter.format(String format, Object... args) 。 在下面的示例中,日期格式为ISO日期:

@Future(message = "The value \"${formatter.format('%1$tY-%1$tm-%1$td', validatedValue)}\" is not in future!")
private Date expiresAt;

当到期日期等于2001-01-01时,消息将是:

值“ 2001-01-01”不在将来!

请注意,在此示例java.util.Date使用了java.util.Date 。 Hibernate Validator 5.1.1尚不支持对新的Date-Time类型的验证。 它将在Hibernate Validator 5.2中引入。 请参见Hibernate Validator路线图

创建Spring MVC应用程序

为了可视化如何将Bean Validation 1.1与Spring MVC一起使用,我们将使用Spring Boot构建一个简单的Web应用程序。

首先,我们需要创建一个Spring Boot项目。 我们可以从Spring Initializr开始,并生成具有以下特征的项目:

  • :pl.codeleak.beanvalidation11-demo
  • 工件 :beanvalidation11-demo
  • 名称 :Bean验证1.1演示
  • 软件包名称:pl.codeleak.demo
  • 风格 :网,胸腺
  • 类型 :Maven项目
  • 包装 :战争
  • Java版本 :1.8
  • 语言 :Java

单击生成后,将下载文件。 生成的项目的结构如下:

src
├───main
│   ├───java
│   │   └───pl
│   │       └───codeleak
│   │           └───demo
│   └───resources
│       ├───static
│       └───templates
└───test
    └───java
        └───pl
            └───codeleak
                └───demo

截至2014年6月,生成的POM如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>pl.codeleak.beanvalidation11-demo</groupId>
    <artifactId>beanvalidation11-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Bean Validation 1.1 Demo</name>
    <description></description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.1.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>pl.codeleak.demo.Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

太快了! Spring Initializr真的很方便! 生成项目后,可以将其导入到您喜欢的IDE中。

修改项目属性

Bean验证1.1由Hibernate Validator 5.x实现 。 我们将使用Hibernate Validator 5.1.1,因此我们需要将其添加到我们的项目中,并且在Spring Boot 1.1.1中,RELEASE使用Hibernate Validator 5.0.3时,我们将需要修改POM属性之一:

<properties>
    <hibernate-validator.version>5.1.1.Final</hibernate-validator.version>
</properties>

在该项目中,我们将使用Tomcat8。但是为什么不能使用Tomcat 7? Hibernate Validator 5.x需要Expression EL API 2.2.4及其实现。 并且该实现在Tomcat 8中提供。要在Tomcat 8上运行Spring Boot应用程序,我们将需要添加另一个属性:

<properties>
    <tomcat.version>8.0.8</tomcat.version>
</properties>

创建出价:控制器

为了创建出价,我们需要一个控制器。 控制器有两种方法:显示表单和创建出价:

@Controller
public class BidController {

    @RequestMapping(value = "/")
    public String index(Model model) {
        model.addAttribute("bid", new Bid("John", new Date(), BigDecimal.valueOf(5.00)));
        return "index";
    }

    @RequestMapping(value = "/", method = RequestMethod.POST)
    public String create(@ModelAttribute @Valid Bid bid, Errors errors) {
        if (errors.hasErrors()) {
            return "index";
        }

        // create a bid here

        return "redirect:/";
    }
}

最终的Bid类代码如下。 请注意,邮件没有在Bid类中直接指定。 我将它们移动到ValidationMessages捆绑文件( src/main/resources ValidationMessages.properties )。

public class Bid {

    @Size.List({
        @Size(min = 5, message = "{bid.bidder.min.message}"),
        @Size(max = 10, message = "{bid.bidder.max.message}")
    })
    private String bidder;

    @NotNull
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    @Future(message = "{bid.expiresAt.message}")
    private Date expiresAt;

    @NotNull
    @DecimalMin(value = "10.00", message = "{bid.price.message}")
    @NumberFormat(style = NumberFormat.Style.CURRENCY)
    private BigDecimal price;

    protected Bid() {}

    public Bid(String bidder, Date expiresAt, BigDecimal price) {
        this.bidder = bidder;
        this.expiresAt = expiresAt;
        this.price = price;
    }

    public String getBidder() {
        return bidder;
    }
    public Date getExpiresAt() {
        return expiresAt;
    }
    public BigDecimal getPrice() {
        return price;
    }

    public void setBidder(String bidder) {
        this.bidder = bidder;
    }
    public void setExpiresAt(Date expiresAt) {
        this.expiresAt = expiresAt;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

创建出价:视图

现在,我们将在Thymeleaf中创建一个简单的页面,其中包含我们的出价表单。 该页面将为index.html ,并将转到src/main/resources/templates

<form 
    class="form-narrow form-horizontal" method="post" 
    th:action="@{/}" th:object="${bid}">

[...]

</form>

如果发生验证错误,我们将显示一条常规消息:

<th:block th:if="${#fields.hasErrors('${bid.*}')}">
    <div class="alert alert-dismissable" th:classappend="'alert-danger'">
        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
        <span th:text="Form contains errors. Please try again.">Test</span>
    </div>
</th:block>

每个表单字段都将标记为红色,并显示相应的消息:

<div class="form-group" 
    th:classappend="${#fields.hasErrors('bidder')}? 'has-error'">
    <label for="bidder" class="col-lg-4 control-label">Bidder</label>
    <div class="col-lg-8">
        <input type="text" class="form-control" id="bidder" th:field="*{bidder}" />
        <span class="help-block" 
            th:if="${#fields.hasErrors('bidder')}" 
            th:errors="*{bidder}">
            Incorrect
        </span>
    </div>
</div>

创建一些测试

在这个阶段,我们可以运行该应用程序,但是我们将创建一些测试来检查验证是否按预期工作。 为此,我们将创建BidControllerTest

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class BidControllerTest {

    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }
}

测试存根已准备就绪。 现在该进行一些测试了。 首先,通过验证模型是否包含出价对象以及视图名称是否等于index检查表单是否正确“显示”:

@Test
public void displaysABidForm() throws Exception {
    this.mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(model().attribute("bid", any(Bid.class)))
            .andExpect(view().name("index"));
}

在下一个测试中,我们将验证,如果输入了正确的数据,则表格中不包含错误消息(快乐流程情况)。 请注意,使用Thymeleaf作为视图引擎,我们可以简单地验证生成的视图。

@Test
public void postsAValidBid() throws Exception {
    this.mockMvc.perform(post("/")
            .param("bidder", "John Smith")
            .param("expiresAt", "2020-01-01")
            .param("price", "11.88"))
            .andExpect(content().string(
                not(
                        containsString("Form contains errors. Please try again.")
                    )
                )
            );
}

在接下来的几个测试中,我们将检查某些对象的有效性。 测试的名称应具有足够的描述性,因此无需进一步说明。 看一下代码:

@Test
public void postsABidWithBidderTooShort() throws Exception {
    this.mockMvc.perform(post("/").param("bidder", "John")) // too short
            .andExpect(content().string(
                allOf(
                        containsString("Form contains errors. Please try again."),
                        containsString(""John" is too short. Should not be shorter than 5")
                    )
                )
            );
}

@Test
public void postsABidWithBidderWayTooShort() throws Exception {
    this.mockMvc.perform(post("/").param("bidder", "J")) // way too short
            .andExpect(content().string(
                allOf(
                        containsString("Form contains errors. Please try again."),
                        containsString(""J" is way too short. Should not be shorter than 5")
                    )
                )
            );
}

@Test
public void postsABidWithBidderTooLong() throws Exception {
    this.mockMvc.perform(post("/").param("bidder", "John S. Smith")) // too long
            .andExpect(content().string(
                allOf(
                        containsString("Form contains errors. Please try again."),
                        containsString(""John S. Smith" is too long. Should not be longer than 10")
                    )
                )
            );
}

@Test
public void postsABidWithBidderWayTooLong() throws Exception {
    this.mockMvc.perform(post("/").param("bidder", "John The Saint Smith"))
            .andExpect(content().string(
                allOf(
                        containsString("Form contains errors. Please try again."),
                        containsString(""John The Saint Smith" is way too long. Should not be longer than 10")
                    )
                )
            );
}

@Test
public void postsABidWithExpiresAtInPast() throws Exception {
    this.mockMvc.perform(post("/").param("expiresAt", "2010-01-01"))
            .andExpect(content().string(
                allOf(
                        containsString("Form contains errors. Please try again."),
                        containsString("Value "2010-01-01" is not in future!")
                    )
                )
            );
}

@Test
public void postsABidWithPriceLowerThanFive() throws Exception {
    this.mockMvc.perform(post("/").param("price", "4.99"))
            .andExpect(content().string(
                allOf(
                        containsString("Form contains errors. Please try again."),
                        containsString("Value "4.99" is incorrect. Must be greater than or equal to 10.00")
                    )
                )
            );
}

很简单。

运行应用程序

由于应用程序的包装类型为war ,因此您可能需要下载Tomcat 8.0.8服务器,使用mvn clean package创建一个软件包,然后将应用程序部署到服务器。

要使用嵌入式Tomcat运行程序,您需要将包装类型更改为jar ,并在pom.xml中将spring-boot-starter-tomcat依赖范围设置为default( compile ):

[...]

<packaging>jar</packaging>

[...]

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

[...]

现在,您可以使用mvn clean package创建一个包,并使用java -jar命令运行生成的jar文件。 当然,您也可以通过运行pl.codeleak.demo.Application类从IDE中运行项目。

摘要

如果您有兴趣查看所提供示例的完整源代码,请检查我的GitHub存储库: spring-mvc-beanvalidation11-demo

阅读本文后,您应该知道:

  • 如何在带有Tomcat 8的Spring MVC应用程序中使用Bean验证1.1
  • 如何使用EL表达式改善错误消息
  • 如何使用Spring Boot从头开始构建应用程序
  • 如何使用Spring Test测试验证

您可能对我以前的有关使用Thymeleaf和Maven引导Spring MVC应用程序的帖子感兴趣: HOW-TO:使用Maven引导Spring MVC 和Thymeleaf

您可能还想看看我过去写的一些其他有关验证的文章:

翻译自: https://www.javacodegeeks.com/2014/06/better-error-messages-with-bean-validation-1-1-in-spring-mvc-application.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值