在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 herereturn "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 {@Autowiredprivate WebApplicationContext wac;private MockMvc mockMvc;@Beforepublic 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 。

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

  • Spring MVC中的验证组
  • Spring中的方法级别验证
  • 在Spring MVC中请求正文验证

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363501.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Google Android 平台正式开源

Google 推出移动设备软件平台 Android 之时&#xff0c;曾向开发者开放 SDK 包&#xff0c;并许诺将在开源许可模式下开放其全部代码&#xff0c;今天&#xff0c;Google 与其合作伙伴&#xff0c;在 Open Handset Alliance 兑现了其承诺&#xff0c;用户现在可以正式下载 Andr…

JSP彩色验证码

产生验证码图片的文件-----image.jsp <% page contentType"image/jpeg" import"java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*" %><%!Color getRandColor(int fc,int bc){//给定范围获得随机颜色 Random random new Random()…

自定义Windows右击菜单调用Winform程序

U9_Git中ignore文件处理 背景 U9代码中有许多自动生成的文件&#xff0c;不需要上传Git必须BE Entity中的.target文件 .bak 文件 Enum.cs结尾的文件&#xff0c;还有许多 extand文件。 这些文件都不需要上传Git。 但是这些文件太多了&#xff0c;不可能手动加入到ignore文件中。…

替换富文本里的px为rem

var content 23pxcontent content.replace(/(\d )px/g, function(s, t) {s s.replace(px, );var value parseInt(s) * 0.001; // 100px 1remreturn value "rem"; //0.23rem }); 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

centos7服务器文件同步,centos7文件实时同步工具lsyncd

1.本地目录同步# yum install lsyncd# vi /etc/lsyncd.conf------ User configuration file for lsyncd.---- Simple example for default rsync, but executing moves through on the target.---- For more examples, see /usr/share/doc/lsyncd*/examples/---- (注释掉这行)s…

Gradle Introduction

目录 Compileing development ProcessOld compile MothedModern compile MothedWhat is GradleGradle EffectWhat is GroovyGroovy syntax relesStructure ScriptPeojectAttributeTaskExample #1applyExample #1Example #2Dependency ManagementExample #1Example #2Multiple P…

命名空间的引用问题

1、using System.Data; 2、using System.Data.SqlClient; 添加引用的时候&#xff0c;只要添加System.Data “类型的初始值设定引发异常” “未能加载程序集或它的某一个依赖项”-------这种错误可能跟&#xff08;命名空间&#xff09;程序集的引用有关系 转载于:https://www.…

使用LinkedHashMap的Code4ReferenceList最近使用(LRU)实现

最近&#xff0c;我偶然发现了Java面试问题之一&#xff1a; “使用Java集合类实现最近使用的列表&#xff08;LRU&#xff09;缓存吗&#xff1f;” 如果您以前曾处理过类似的问题&#xff0c;那么对您来说真的很容易。 否则&#xff0c;您将开始考虑实现LRU缓存的最佳收集类…

sha1.js

function encodeUTF8(s) {var i, r [], c, x;for (i 0; i < s.length; i)if ((c s.charCodeAt(i)) < 0x80) r.push(c);else if (c < 0x800) r.push(0xC0 (c >> 6 & 0x1F), 0x80 (c & 0x3F));else {if ((x c ^ 0xD800) >> 10 0) //对四字节…

angular 获取ng-repeat完成状态 $last

$index $first $middle $last $odd $even html <ul><li ng-repeat"item in data" repeat-finish&#xff1d;"renderFinish()">{{item.str}}</li> </ul> 指令 app.directive(repeatFinish,function(){return {link: function(sco…

博客园-我的新的开始

励志学会,python,php-由于必须150字所以水一下 python: Python 是由 Guido van Rossum 在八十年代末和九十年代初&#xff0c;在荷兰国家数学和计算机科学研究所设计出来的。 Python 本身也是由诸多其他语言发展而来的,这包括 ABC、Modula-3、C、C、Algol-68、SmallTalk、Unix …

Selenium备忘手册 [转]

最近的项目准备用Selenium作一部分的Regression Test。在SpringSide里参考了一下&#xff0c;又下了个Selenium IDE玩玩&#xff0c;觉得还蛮容易上手&#xff0c;基本上不需要手动写测试代码。 但实操起来时面对各种复杂的页面情况遇到不少麻烦。感觉Selenium 的offical d…

js中7种继承的实现和优缺点

原型链继承 function Parent(){this.name kevin }Parent.prototype.getName function(){console.log(this.name) }function Child(){} Child.prototype new Parent()var child new Child() console.log(child.name) //kevin优点&#xff1a; 简单&#xff0c;容易实现缺点&…

u8系统怎么连接服务器,用友U8 怎么连接远程服务器

用友U8 怎么连接远程服务器 内容精选换一换配置应用系统的跨云热备容灾方案如图1所示。在如图1所示的方案中&#xff0c;用户的生产数据中心的应用系统使用MySQL作为数据库&#xff0c;应用系统与MySQL均热备容灾到华为云上。用户的生产数据中心与华为云之间使用专线进行网络连…

使用Java 8 Streams进行编程对算法性能的影响

多年来&#xff0c;使用Java进行多范例编程已经成为可能&#xff0c;它支持面向服务&#xff0c;面向对象和面向方面的编程的混合。 带有lambda和java.util.stream.Stream类的Java 8是个好消息&#xff0c;因为它使我们可以将功能性编程范例添加到混合中。 确实&#xff0c;lam…

bind搭建(二)反向解析

我们在上一节已经知道了怎么建立DNS的服务器端&#xff0c;可以实现了域名到IP之间的转换。那么好我们现在就来了解一下如何实现反向的DNS解析&#xff0c;也就是IP到域名的映射。 步骤如下&#xff1a; l 在/etc/named中声明反向区域 l 在/var/named/chroot/var/named/中创建…

js求渐升数的第100位

我弟考了道数学竞赛题&#xff0c;问我能不能用代码算结果.. 题目是这样的 用 1、2、3、4、5 组合数字&#xff0c;然后排列大小&#xff0c;从小到大&#xff0c;求排在第100位的数值大小 function foo(chars) {var count 0;if (!chars.length) return;var _foo function(c…

[导入]商业智能2.0?(BI 2.0 from Timo Elliott)

译者注: 关于BI2.0的说法很多&#xff0c;不尽一致&#xff0c;目的只是想多了解一些&#xff1b;译文并不代表译者认可原文观点&#xff0c;只是顺便译了以方便不喜欢E文的朋友。本文是一篇充满探讨及疑问的文章&#xff0c;来自Timo Elliott(Business Objects历史上的第8号员…

poj 3259 Wormholes : spfa 双端队列优化 判负环 O(k*E)

1 /**2 problem: http://poj.org/problem?id32593 spfa判负环&#xff1a;4 当有个点被松弛了n次&#xff0c;则这个点必定为负环中的一个点&#xff08;n为点的个数&#xff09;5 spfa双端队列优化&#xff1a;6 维护队列使其dist小的点优先处理7 **/8 #include<stdio.h&g…

编写干净的测试–用特定领域的语言替换断言

很难为干净的代码找到一个好的定义&#xff0c;因为我们每个人都有自己的单词clean的定义。 但是&#xff0c;有一个似乎是通用的定义&#xff1a; 干净的代码易于阅读。 这可能会让您感到有些惊讶&#xff0c;但是我认为该定义也适用于测试代码。 使测试尽可能具有可读性是我…