1. Обзор
Проверка никогда не бывает такой простой, как мы ожидаем. И, конечно же, проверка значений, введенных пользователем в приложение, очень важна для сохранения целостности наших данных.
В контексте веб-приложения ввод данных обычно выполняется с использованием HTML-форм и требует проверки как на стороне клиента, так и на стороне сервера.
В этом руководстве мы рассмотрим реализацию проверки ввода формы на стороне клиента с использованием AngularJS и проверки на стороне сервера с использованием среды Spring MVC .
Эта статья посвящена Spring MVC. В нашей статье « Проверка в Spring Boot » описывается, как выполнять проверки в Spring Boot.
2. Зависимости Maven
Для начала добавим следующие зависимости:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
Последние версии spring-webmvc , hibernate- validator и jackson-databind можно загрузить с Maven Central.
3. Проверка с использованием Spring MVC
Приложение никогда не должно полагаться исключительно на проверку на стороне клиента, поскольку ее можно легко обойти. Чтобы предотвратить сохранение неверных или вредоносных значений или неправильное выполнение логики приложения, важно также проверять входные значения на стороне сервера.
Spring MVC предлагает поддержку проверки на стороне сервера с использованием аннотаций спецификации JSR 349 Bean Validation .
В этом примере мы будем использовать эталонную реализацию спецификации — hibernate-validator
.
3.1. Модель данных
Давайте создадим класс User
со свойствами, аннотированными соответствующими аннотациями проверки:
public class User {
@NotNull
@Email
private String email;
@NotNull
@Size(min = 4, max = 15)
private String password;
@NotBlank
private String name;
@Min(18)
@Digits(integer = 2, fraction = 0)
private int age;
// standard constructor, getters, setters
}
Используемые выше аннотации относятся к спецификации JSR 349
, за исключением @Email
и @NotBlank
, которые относятся к библиотеке hibernate-validator
.
3.2. Spring Контроллер MVC
Давайте создадим класс контроллера, определяющий конечную точку /user
, которая будет использоваться для сохранения нового объекта User в
List
.
Чтобы включить проверку объекта User
, полученного с помощью параметров запроса, перед объявлением должна стоять аннотация @Valid
, а ошибки проверки будут храниться в экземпляре BindingResult
.
Чтобы определить, содержит ли объект недопустимые значения, мы можем использовать метод hasErrors()
класса BindingResult
.
Если hasErrors()
возвращает true
, мы можем вернуть массив JSON,
содержащий сообщения об ошибках, связанных с непройденными проверками. В противном случае мы добавим объект в список:
@PostMapping(value = "/user")
@ResponseBody
public ResponseEntity<Object> saveUser(@Valid User user,
BindingResult result, Model model) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return new ResponseEntity<>(errors, HttpStatus.OK);
} else {
if (users.stream().anyMatch(it -> user.getEmail().equals(it.getEmail()))) {
return new ResponseEntity<>(
Collections.singletonList("Email already exists!"),
HttpStatus.CONFLICT);
} else {
users.add(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
}
Как видите, проверка на стороне сервера дает дополнительное преимущество, заключающееся в возможности выполнять дополнительные проверки, которые невозможны на стороне клиента.
В нашем случае мы можем проверить, существует ли уже пользователь с таким же адресом электронной почты, и вернуть статус 409 CONFLICT, если это так.
Нам также нужно определить наш список пользователей и инициализировать его несколькими значениями:
private List<User> users = Arrays.asList(
new User("ana@yahoo.com", "pass", "Ana", 20),
new User("bob@yahoo.com", "pass", "Bob", 30),
new User("john@yahoo.com", "pass", "John", 40),
new User("mary@yahoo.com", "pass", "Mary", 30));
Давайте также добавим сопоставление для получения списка пользователей в виде объекта JSON:
@GetMapping(value = "/users")
@ResponseBody
public List<User> getUsers() {
return users;
}
Последний элемент, который нам нужен в нашем контроллере Spring MVC, — это сопоставление, возвращающее главную страницу нашего приложения:
@GetMapping("/userPage")
public String getUserProfilePage() {
return "user";
}
Мы рассмотрим страницу user.html
более подробно в разделе AngularJS.
3.3. Весенняя конфигурация MVC
Давайте добавим базовую конфигурацию MVC в наше приложение:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.foreach.springmvcforms")
class ApplicationConfiguration implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
public InternalResourceViewResolver htmlViewResolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setPrefix("/WEB-INF/html/");
bean.setSuffix(".html");
return bean;
}
}
3.4. Инициализация приложения
Давайте создадим класс, реализующий интерфейс WebApplicationInitializer
для запуска нашего приложения:
public class WebInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx
= new AnnotationConfigWebApplicationContext();
ctx.register(ApplicationConfiguration.class);
ctx.setServletContext(container);
container.addListener(new ContextLoaderListener(ctx));
ServletRegistration.Dynamic servlet
= container.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
}
}
3.5. Тестирование проверки Spring Mvc с использованием Curl
Прежде чем реализовать клиентский раздел AngularJS, мы можем протестировать наш API с помощью cURL с помощью команды:
curl -i -X POST -H "Accept:application/json"
"localhost:8080/spring-mvc-forms/user?email=aaa&password=12&age=12"
Ответ представляет собой массив, содержащий сообщения об ошибках по умолчанию:
[
"not a well-formed email address",
"size must be between 4 and 15",
"may not be empty",
"must be greater than or equal to 18"
]
4. Проверка AngularJS
Проверка на стороне клиента полезна для улучшения взаимодействия с пользователем, поскольку она предоставляет пользователю информацию о том, как успешно отправить достоверные данные, и позволяет ему продолжать взаимодействовать с приложением.
Библиотека AngularJS отлично поддерживает добавление требований проверки в поля формы, обработку сообщений об ошибках и стилизацию допустимых и недопустимых форм.
Во-первых, давайте создадим модуль AngularJS, который внедряет модуль ngMessages
, который используется для сообщений проверки:
var app = angular.module('app', ['ngMessages']);
Далее давайте создадим службу и контроллер AngularJS, которые будут использовать API, созданный в предыдущем разделе.
4.1. Служба AngularJS
Наш сервис будет иметь два метода, которые вызывают методы контроллера MVC — один для сохранения пользователя и один для получения списка пользователей:
app.service('UserService',['$http', function ($http) {
this.saveUser = function saveUser(user){
return $http({
method: 'POST',
url: 'user',
params: {email:user.email, password:user.password,
name:user.name, age:user.age},
headers: 'Accept:application/json'
});
}
this.getUsers = function getUsers(){
return $http({
method: 'GET',
url: 'users',
headers:'Accept:application/json'
}).then( function(response){
return response.data;
} );
}
}]);
4.2. Контроллер AngularJS
Контроллер UserCtrl
внедряет UserService
, вызывает методы службы и обрабатывает ответы и сообщения об ошибках:
app.controller('UserCtrl', ['$scope','UserService', function ($scope,UserService) {
$scope.submitted = false;
$scope.getUsers = function() {
UserService.getUsers().then(function(data) {
$scope.users = data;
});
}
$scope.saveUser = function() {
$scope.submitted = true;
if ($scope.userForm.$valid) {
UserService.saveUser($scope.user)
.then (function success(response) {
$scope.message = 'User added!';
$scope.errorMessage = '';
$scope.getUsers();
$scope.user = null;
$scope.submitted = false;
},
function error(response) {
if (response.status == 409) {
$scope.errorMessage = response.data.message;
}
else {
$scope.errorMessage = 'Error adding user!';
}
$scope.message = '';
});
}
}
$scope.getUsers();
}]);
В приведенном выше примере мы видим, что метод службы вызывается только в том случае, если свойство $valid
формы userForm
имеет значение true. Тем не менее, в этом случае есть дополнительная проверка на дубликаты писем, которая может быть выполнена только на сервере и обрабатывается отдельно в функции error()
.
Кроме того, обратите внимание, что определена переменная submit, которая сообщит нам, была ли форма отправлена или нет .
Изначально эта переменная будет иметь значение false
, а при вызове метода saveUser()
она станет истинной
. Если мы не хотим, чтобы сообщения проверки показывались до того, как пользователь отправит форму, мы можем использовать представленную
переменную, чтобы предотвратить это.
4.3. Форма с использованием проверки AngularJS
Чтобы использовать библиотеку AngularJS и наш модуль AngularJS, нам нужно добавить скрипты на нашу страницу user.html
:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js">
</script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-messages.js">
</script>
<script src="js/app.js"></script>
Затем мы можем использовать наш модуль и контроллер, установив свойства ng-app
и ng-controller :
<body ng-app="app" ng-controller="UserCtrl">
Давайте создадим нашу HTML-форму:
<form name="userForm" method="POST" novalidate
ng-class="{'form-error':submitted}" ng-submit="saveUser()" >
...
</form>
Обратите внимание, что мы должны установить атрибут novalidate
в форме, чтобы предотвратить проверку HTML5 по умолчанию и заменить ее нашей собственной.
Атрибут ng-
class динамически добавляет CSS -класс form-error в форму, если
переданная
переменная имеет значение true
.
Атрибут ng-submit
определяет функцию контроллера AngularJS, которая будет вызываться при отправке формы. Использование ng-submit
вместо ng-click
имеет то преимущество, что оно также отвечает на отправку формы с помощью клавиши ENTER.
Теперь давайте добавим четыре поля ввода для атрибутов пользователя:
<label class="form-label">Email:</label>
<input type="email" name="email" required ng-model="user.email" class="form-input"/>
<label class="form-label">Password:</label>
<input type="password" name="password" required ng-model="user.password"
ng-minlength="4" ng-maxlength="15" class="form-input"/>
<label class="form-label">Name:</label>
<input type="text" name="name" ng-model="user.name" ng-trim="true"
required class="form-input" />
<label class="form-label">Age:</label>
<input type="number" name="age" ng-model="user.age" ng-min="18"
class="form-input" required/>
Каждое поле ввода имеет привязку к свойству пользовательской
переменной через атрибут ng-model .
Для установки правил проверки мы используем обязательный
атрибут HTML5 и несколько атрибутов, специфичных для AngularJS: ng-minglength, ng-maxlength, ng-min
и ng-trim
.
Для поля электронной почты мы также используем атрибут
type
со значением электронной почты
для проверки электронной почты на стороне клиента.
Чтобы добавить сообщения об ошибках, соответствующие каждому полю , AngularJS предлагает директиву ng-messages
, которая перебирает входной объект $errors
и отображает сообщения на основе каждого правила проверки.
Давайте добавим директиву для поля электронной почты
сразу после определения ввода:
<div ng-messages="userForm.email.$error"
ng-show="submitted && userForm.email.$invalid" class="error-messages">
<p ng-message="email">Invalid email!</p>
<p ng-message="required">Email is required!</p>
</div>
Аналогичные сообщения об ошибках можно добавить и для других полей ввода.
Мы можем контролировать, когда директива отображается для поля электронной почты , используя свойство
ng-show
с логическим выражением. В нашем примере мы отображаем директиву, когда поле имеет недопустимое значение, что означает, что свойство $invalid имеет значение
true
, а переданная
переменная также имеет значение true
.
Для поля будет отображаться только одно сообщение об ошибке за раз.
Мы также можем добавить галочку (представленную символом HEX-кода ✓) после поля ввода, если поле допустимо, в зависимости от свойства $valid :
<div class="check" ng-show="userForm.email.$valid">✓</div>
Проверка AngularJS также предлагает поддержку стилей с использованием классов CSS, таких как ng-valid
и ng-invalid
, или более конкретных, таких как ng-invalid-required
и ng-invalid-minlength
.
Давайте добавим свойство CSS border-color:red
для недопустимых входных данных внутри класса form-error формы
:
.form-error input.ng-invalid {
border-color:red;
}
Мы также можем показать сообщения об ошибках красным цветом, используя класс CSS:
.error-messages {
color:red;
}
Собрав все вместе, давайте посмотрим на пример того, как наша проверка формы на стороне клиента будет выглядеть при заполнении смесью допустимых и недопустимых значений:
5. Вывод
В этом руководстве мы показали, как мы можем комбинировать проверку на стороне клиента и на стороне сервера с помощью AngularJS и Spring MVC.
Как всегда, полный исходный код примеров можно найти на GitHub .
Чтобы просмотреть приложение, перейдите по URL-адресу /userPage
после его запуска.