在许多新功能中, 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