1. 概述

表单验证从来不像我们想象的那么简单。验证用户输入的数据对于维护数据完整性至关重要,这点毋庸置疑。

在 Web 应用中,数据输入通常通过 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.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.1.Final</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.0</version>
</dependency>

最新版本可从 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;

    // 标准构造器、getter/setter
}

@Email@NotBlank 属于 hibernate-validator 扩展外,其余均为 JSR 349 标准注解。

3.2. Spring MVC 控制器

创建控制器类,定义 /user 接口保存新用户:

@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("邮箱已存在!"), 
              HttpStatus.CONFLICT);
        } else {
            users.add(user);
            return new ResponseEntity<>(HttpStatus.CREATED);
        }
    }
}

关键点:

  • @Valid 注解触发验证
  • BindingResult 捕获验证错误
  • 额外检查邮箱唯一性(客户端无法完成)

初始化用户列表:

private List<User> users = Arrays.asList(
  new User("ana@example.com", "pass", "Ana", 20),
  new User("bob@example.com", "pass", "Bob", 30),
  new User("john@example.com", "pass", "John", 40),
  new User("mary@example.com", "pass", "Mary", 30));

添加获取用户列表的接口:

@GetMapping(value = "/users")
@ResponseBody
public List<User> getUsers() {
    return users;
}

返回主页的映射:

@GetMapping("/userPage")
public String getUserProfilePage() {
    return "user";
}

3.3. Spring MVC 配置

基础 MVC 配置:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.baeldung.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. 使用 cURL 测试验证

实现 AngularJS 前前,先用 cURL 测试 API:

curl -i -X POST -H "Accept:application/json" 
  "localhost:8080/spring-mvc-forms/user?email=aaa&password=12&age=12"

响应包含默认错误信息:

[
    "不是有效的邮箱地址",
    "长度需在4到15之间",
    "不能为空",
    "必须大于等于18"
]

4. AngularJS 验证

客户端验证能提升用户体验,提供即时反馈并减少无效请求。AngularJS 对表单验证有完善支持。

首先创建注入 ngMessages 模块的 AngularJS 模块:

var app = angular.module('app', ['ngMessages']);

4.1. AngularJS 服务

创建服务调用后端接口:

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 控制器

控制器处理业务逻辑和错误响应:

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 = '用户添加成功!';
                  $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 = '添加用户失败!';
                  }
                  $scope.message = '';
            });
          }
    }
   
   $scope.getUsers();
}]);

关键点:

  • submitted 变量控制验证信息显示时机
  • 仅在表单有效时提交 ($valid)
  • 单独处理邮箱冲突错误 (HTTP 409)

4.3. 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>

绑定模块和控制器:

<body ng-app="app" ng-controller="UserCtrl">

创建表单结构:

<form name="userForm" method="POST" novalidate 
  ng-class="{'form-error':submitted}" ng-submit="saveUser()" >
...
</form>

⚠️ 必须设置 novalidate 禁用 HTML5 原生验证

添加输入字段:

<label class="form-label">邮箱:</label>
<input type="email" name="email" required ng-model="user.email" class="form-input"/>

<label class="form-label">密码:</label>
<input type="password" name="password" required ng-model="user.password" 
  ng-minlength="4" ng-maxlength="15" class="form-input"/>

<label class="form-label">姓名:</label>
<input type="text" name="name" ng-model="user.name" ng-trim="true" 
  required class="form-input" />

<label class="form-label">年龄:</label>
<input type="number" name="age" ng-model="user.age" ng-min="18"
  class="form-input" required/>

验证规则:

  • HTML5 required + AngularJS 特性 (ng-minlength, ng-min 等)
  • 邮箱字段使用 type="email"

使用 ng-messages 显示错误信息:

<div ng-messages="userForm.email.$error" 
  ng-show="submitted && userForm.email.$invalid" class="error-messages">
    <p ng-message="email">无效的邮箱!</p>
    <p ng-message="required">邮箱必填!</p>
</div>

显示逻辑:

  • 仅在提交后显示 (submitted && $invalid)
  • 每次只显示一条错误

添加有效状态提示:

<div class="check" ng-show="userForm.email.$valid">✓</div>

样式增强:

.form-error input.ng-invalid {
    border-color:red;
}

.error-messages {
    color:red;
}

最终效果示例:

AngularJS 表单验证示例

5. 总结

我们成功实现了:

  1. ✅ 服务端验证(Spring MVC + Bean Validation)
  2. ✅ 客户端验证(AngularJS + ngMessages)
  3. ✅ 双端验证协同工作

完整源代码见 GitHub 仓库。启动后访问 /userPage 查看效果。


原始标题:Form Validation with AngularJS and Spring MVC

« 上一篇: JiXML 绑定框架详解