Bean Validation 1.1 , среди многих новых функций, ввел интерполяцию сообщений об ошибках с использованием выражений Unified Expression Language (EL). Это позволяет определять сообщения об ошибках на основе условной логики, а также включает расширенные параметры форматирования . Добавленный в приложение Spring MVC, позволяет отображать более понятные сообщения об ошибках довольно просто.
В первой части этой статьи я кратко опишу интерполяцию сообщений с помощью выражений EL, во второй части мы создадим простое веб-приложение с Spring Boot и Thymeleaf, которое работает на Tomcat 8.
EL выражения в сообщениях — примеры
Чтобы визуализировать некоторые возможности лучшей интерполяции сообщений с помощью выражений EL, я буду использовать следующий класс:
1
2
3
4
5
|
public class Bid { private String bidder; private Date expiresAt; private BigDecimal price; } |
Пример 1: текущее проверенное значение
Механизм проверки делает текущее проверенное значение доступным в контексте EL как validatedValue
:
1
2
|
@Size (min = 5 , message = "\"${validatedValue}\" is too short." ) private String bidder; |
Сообщение об ошибке, когда для участника, равного «Джон», будет:
«Джон» слишком короткий.
Пример 2: условная логика
Условная логика с выражением EL возможна в сообщениях об ошибках. В приведенном ниже примере, если длина подтвержденного участника торгов меньше 2, мы отображаем другое сообщение:
1
2
|
@Size (min = 5 , message = "\"${validatedValue}\" is ${validatedValue.length() < 2 ? 'way' : ''} too short." ) private String bidder; |
Когда претендент равен «J», сообщение будет:
«J» слишком короткий.
Когда претендент равен «Джон», сообщение будет:
«Джон» слишком короткий.
Пример 3: Форматер
Механизм проверки делает объект formatter
доступным в контексте EL. formatter
ведет себя java.util.Formatter.format(String format, Object... args)
. В приведенном ниже примере дата отформатирована в ISO Date:
1
2
|
@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
. Hibernate Validator 5.1.1 пока не поддерживает проверку новых типов даты и времени. Он будет представлен в Hibernate Validator 5.2. См. Дорожная карта валидатора Hibernate .
Создание приложения Spring MVC
Чтобы представить, как Bean Validation 1.1 можно использовать с Spring MVC, мы создадим простое веб-приложение с использованием Spring Boot.
Во-первых, нам нужно создать проект Spring Boot. Мы можем начать с Spring Initializr и создать проект со следующими характеристиками:
- Группа : pl.codeleak.beanvalidation11-демо
- Артефакт : beanvalidation11-demo
- Название : Bean Validation 1.1 Демо
- Название пакета : pl.codeleak.demo
- Стили : паутина, тимелист
- Тип : Maven Project
- Упаковка : война
- Версия Java : 1.8
- Язык : Java
После нажатия кнопки «Создать» файл будет загружен. Структура сгенерированного проекта следующая:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
src ├───main │ ├───java │ │ └───pl │ │ └───codeleak │ │ └───demo │ └───resources │ ├─── static │ └───templates └───test └───java └───pl └───codeleak └───demo |
По состоянию на июнь 2014 года сгенерированный POM выглядел следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
<? 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" < 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 Validation 1.1 реализован в Hibernate Validator 5.x. Мы будем использовать Hibernate Validator 5.1.1, поэтому нам нужно будет добавить его в наш проект, и, поскольку Spring Boot 1.1.1. RELEASE использует Hibernate Validator 5.0.3, нам потребуется изменить одно из свойств POM:
1
2
3
|
< properties > < hibernate-validator.version >5.1.1.Final</ hibernate-validator.version > </ properties > |
В проекте мы будем использовать Tomcat 8. Но почему мы не можем работать с Tomcat 7? Hibernate Validator 5.x требует Expression EL API 2.2.4 и его реализации. А реализация предоставлена в Tomcat 8. Чтобы запустить приложение Spring Boot на Tomcat 8, нам нужно добавить еще одно свойство:
1
2
3
|
< properties > < tomcat.version >8.0.8</ tomcat.version > </ properties > |
Создание ставки: Контроллер
Для создания ставки нам понадобится контроллер. Контроллер имеет два метода: отобразить форму и создать ставку:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
@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
( ValidationMessages.properties
в src/main/resources
).
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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
.
1
2
3
4
5
6
7
|
< form class = "form-narrow form-horizontal" method = "post" th:action = "@{/}" th:object = "${bid}" > [...] </ form > |
В случае ошибки проверки мы выведем общее сообщение:
1
2
3
4
5
6
|
< 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 > |
Каждое поле формы будет помечено красным цветом и будет отображено соответствующее сообщение:
01
02
03
04
05
06
07
08
09
10
11
12
|
< 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
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@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
:
1
2
3
4
5
6
7
|
@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 в качестве движка просмотра мы можем просто проверить сгенерированное представление.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@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." ) ) ) ); } |
В следующих нескольких тестах мы будем проверять валидацию определенных объектов. Названия тестов должны быть достаточно наглядными, поэтому дальнейшее объяснение не требуется. Посмотрите на код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
@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
и установить область зависимостей spring-boot-starter-tomcat
по умолчанию ( compile
) в pom.xml
:
01
02
03
04
05
06
07
08
09
10
11
12
|
[...] < packaging >jar</ packaging > [...] < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-tomcat</ artifactId > </ dependency > [...] |
Теперь вы можете создать пакет с помощью пакета mvn clean package
и запустить сгенерированный файл jar с помощью команды java -jar
. Конечно, вы также можете запустить проект из IDE, запустив класс pl.codeleak.demo.Application
.
Резюме
Если вы заинтересованы в том, чтобы увидеть полный исходный код представленного примера, проверьте мой репозиторий GitHub: spring-mvc-beanvalidation11-demo .
После прочтения этой статьи вы должны знать:
- Как использовать Bean Validation 1.1 в приложении Spring MVC с Tomcat 8
- Как улучшить сообщения об ошибках, используя выражения EL
- Как создать приложение с нуля с помощью Spring Boot
- Как проверить валидацию с помощью Spring Test
Возможно, вас заинтересует мой предыдущий пост о начальной загрузке приложения Spring MVC с Thymeleaf и Maven: HOW-TO: Spring Boot и Thymeleaf с Maven .
Вы также можете посмотреть другие посты о валидации, которые я написал в прошлом:
Ссылка: | Лучшие сообщения об ошибках с Bean Validation 1.1 в приложении Spring MVC от нашего партнера по JCG Рафаля Боровца в блоге Codeleak.pl . |