Java Bean Validation入门介绍

概述

“数据校验”是比较常见的工作,在日常的开发中贯穿于代码的各个层次,从上层的View层到底层的数据层,为了保证程序的正确运行以及数据的正确性,开发者通常会在不同层次间做数据校验,而且这些校验工作通常都是重复的,为了实现代码的复用性,通常会把校验的逻辑写在被校验的对象上。

Bean Validation就是为了解决这样的问题,它定义了一套元数据模型和API,对JavaBean实现校验,默认是以注解作为元数据,可以通过XML重写或者拓展元数据,通常来说注解的方式可以实现比较简单逻辑的校验,而复杂校验就需要通过XML来描述。

Bean Validation包含两部分:规范实现

规范只提供了相关的API和注解说明,而实现则要依靠具体的软件厂商提供实现代码。就像JDBC是数据库操作的规范,再由各个数据库厂商提供实现了规范的数据库驱动一样。

使用Bean Validation前的校验方式:

image-20200912232837055

使用Bean Validation后的校验方式:

image-20200912232904922

介绍

规范

Bean Validation通常的使用方法是:在你需要被校验的实体类上用一些注解来修饰需要校验的属性,这些注解就是校验规则,比如必须是邮件地址、必须是整数、必须是未来的日期等。

那么这些作为规则的注解是哪里来的呢,它们一定是来自某个Jar包,那Jar包是谁定义的呢?答案就是它们来自JSR规范,通常是某个组织比如JCPEclipse基金会。JSR规范就像是接口(Interface),它只作为标识,用来描述能力,并不提供具体的逻辑。

Java Specification Requests(Java规范提案)关于Bean Validation的定义目前有三个版本,正在广泛使用的版本是JSR-380

JSR名称 对应版本 发起时间
JSR 380 Bean Validation 2.0 2017-08-21
JSR 349 Bean Validation 1.1 2013-05-24
JSR 303 Bean Validation 1.0 2009-11-16

Bean Validation2.0版本针对1.0版本做了向下很好的兼容,因此从Bean Validation 1.0切换到Bean Validation 2.0基本上是无缝和透明的。本文的侧重也是介绍Bean Validation 2.0JSR-380)。

实现

Bean Validation 2.0之前,有两个官方认可的实现,分别是:Hibernate ValidatorApache BVal,但如果你想用2.0版本的话,就只有Hibernate Validator这个实现了(hibernate-validator与持久层框架 hibernate 没有什么关系)。

image-20200912232946737

image-20200912233001019

Hibernate Validator的兼容性矩性:

Hibernate Validator 7.0 6.1 6.0 5.4
Java 8 or 11 8 or 11 8 or 11 6, 7 or 8
Bean Validation N/A N/A 2.0 1.1
Jakarta Bean Validation 3.0 2.0 N/A N/A

导入了hibernate-validator这个实现之后,会自动引入规范的Jar包,也就不必要再自己手工导入Java Bean ValidationAPI了,因此不建议再手动导入API,交给内部来管理依赖。

image-20200912233019302

使用

添加依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>

声明约束

Java Bean约束

  1. 字段/属性级别的约束

    1
    2
    @NotNull
    private String manufacturer;
  2. 方法返回值约别的约束

    1
    2
    3
    4
    @NotNull
    public String getManufacturer(){
    return manufacturer;
    }
  3. 容器级别的约束

    1
    private Map<@NotNull FuelConsumption, @MaxAllowedFuelConsumption Integer> fuelConsumption = new HashMap<>();
  4. 级联验证,使用@Valid修饰对象属性的引用,则对象属性中声明的所有约束也会起作用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 当验证 Car 实例时,Person 对象中的 name 字段也会验证
    public class Car
    {
    @NotNull
    @Valid
    private Person driver;
    //...
    }

    public class Person
    {
    @NotNull
    private String name;
    //...
    }
  5. 约束继承

    当一个类继承/实现另一个类时,父类声明的所有约束也会应用在子类继承的对应属性上。 如果方法重写,约束注解将会聚合,也就是此方法父类和子类声明的约束都会起作用。

声明方法约束

  1. 参数约束

    1
    2
    3
    public RentalStation(@NotNull String name){}

    public void rentCar(@NotNull Customer customer, @NotNull @Future Date startDate, @Min(1) int durationInDays){}
  2. 返回值约束

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class RentalStation 
    {
    @ValidRentalStation // 自定义约束,加在构造方法上,表明任何新创建的 RentalStation 对象都必须满足 @validRentalStation 约束
    public RentalStation()
    {
    //...
    }

    // 列表不能为空,并且必须至少包含 1 个元素
    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getCustomers() // 返回的客户列表不能包含空对象
    {
    //...
    return null;
    }
    }
  3. 级联验证,使用@Valid标记可执行参数和级联验证的返回值。当验证用@valid 注释的参数或返回值时,也会验证在参数或返回值对象上声明的约束。 而且,也可用在容器元素中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Garage
    {
    // 列表不能为空,返回的小汽车对象的属性也需要验证
    public boolean checkCars(@NotNull List<@Valid Car> cars)
    {
    //...
    return false;
    }
    }
  4. 约束继承,当在继承体系中声明方法约束时,必须了解两个规则:

    • 方法调用方要满足前置条件不能在子类型中得到加强
    • 方法调用方要保证后置条件不能再子类型中被削弱

这些规则是由子类行为概念所决定的:在使用类型 T 的任何地方,也能在不改变程序行为的情况下使用 T 的子类。

当两个类分别有一个同名且形参列表相同的方法,而另一个类用一个方法重写/实现上述两个类的同名方法时,这两个父类的同名方法上不能有任何参数约束,因为不管怎样都会与上述规则冲突。 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Vehicle 
{
// 错误的用法
void drive(@Max(75) int speedInMph);
}

public interface Car
{
void drive(int speedInMph);
}

public class RacingCar implements Car, Vehicle
{
@Override
public void drive(int speedInMph)
{
//...
}
}

Web场景

集成到SpringMVC

  1. 配置验证器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer
    {
    @Override
    public Validator getValidator();
    {
    // ...
    }
    }
  2. 使用注解@Valid@Validated实现对请求参数的校验

    1
    2
    3
    4
    5
    6
    7
    @PostMapping
    @ResponseBody
    public ResponseEntity<User> create(@RequestBody @Validated UserForm form)
    {
    User user = userService.create(form);
    return ResponseEntity.ok().body(user);
    }
  3. 配置统一的控制器通知来处理校验结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @ControllerAdvice
    public class ValidationResponseAdvice extends ResponseEntityExceptionHandler
    {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
    String message = ex.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
    .collect(Collectors.joining(","));
    return ResponseEntity.badRequest().body(message);
    }
    }
  4. 如果方法中有BindingResult类型的参数,spring校验完成之后会将校验结果传给这个参数。通过BindingResult控制程序抛出自定义类型的异常或者返回不同结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @PostMapping
    public boolean addUser(@Validated ValidatorVO user, BindingResult result)
    {
    if (result.hasErrors())
    {
    for (ObjectError error : result.getAllErrors())
    {
    log.error(error.getDefaultMessage());
    }
    return false;
    }
    return true;
    }

直接使用场景

1
2
3
4
5
6
7
8
9
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
@Getter
@Setter
@ToString
public class Person
{
// 错误消息message是可以自定义的
@NotNull(message = "名字不能为null")
public String name;

@Positive
public Integer age;

@NotNull
@NotEmpty
private List<@Email String> emails;

@Future
private Date start;
}

public static void main(String[] args)
{
Person person = new Person();
person.setName("fsx");
person.setAge(-1);
person.setEmails(Arrays.asList("fsx@gmail.com", "baidu@baidu.com", "aaa.com"));
person.setStart(new Date()); //start 需要是一个将来的时间: Sun Jul 21 10:45:03 CST 2019
person.setStart(new Date(System.currentTimeMillis() + 10000)); //校验通过

// 对person进行校验然后拿到结果(显然使用时默认的校验器) 会保留下校验失败的消息
Set<ConstraintViolation<Person>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(person);

// 对结果进行遍历输出
result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@NotBlank(message = "用户名不能为空")
@Length(max = 20, message = "用户名不能超过20个字符")
@Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")
private String username;

@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
private String mobile;

@NotBlank(message = "联系邮箱不能为空")
@Email(message = "邮箱格式不对")
private String email;

@Future(message = "创建时间必须是将来时间")
private Date createTime;

@Pattern(regexp = "^asc|desc$", message = "排序只能是asc或desc")
private String sort;

其它

关于更名

2018年03月, Oracle决定把Java EE移交给开源组织Eclipse基金会,但是不希望Java EE继续使用Java这个名字,尽管 Eclipse 做了争取,但是最终没有达成一致,因此Eclipse最终将Java EE正式改名为Jarkarta EE。

在改名之后对应的Maven GAV坐标也进行了更名,迁移前:

1
2
3
4
5
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>

迁移后:

1
2
3
4
5
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.1</version>
</dependency>

Bean Validation 2.0新特性

  • 使用Bean Validation的最低Java版本为Java 8

  • 支持容器的校验,通过TYPE_USE类型的注解实现对容器内容的约束:List<@Email String>

  • 支持日期/时间的校验,@Past@Future

  • 新增注解:@Email@NotEmpty@NotBlank@Positive@PositiveOrZero@Negative@NegativeOrZero@PastOrPresent@FutureOrPresent@Email、@NotEmpty、@NotBlank之前是Hibernate额外提供的注解,Bean Validation 2.0标准后将这些注解引入到自己的规范中了,因此Hibernate自动将这些注解标注为过期,引包的时候需要注意一下)

  • 官方认证的实现就只有Hibernate Validator,不再包含Apache BVal

未来版本

目前Jakarta Bean Validation 3.0已经在开发中,对应Java规范是Java 8或Java11,Bean Validation校验规范的Maven坐标是:jakarta.validation:jakarta.validation-api:3.0.0,对应的Hibernate实现是Hibernate Validator 7.0.0.Alpha5

1
2
3
4
5
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.0</version>
</dependency>
1
2
3
4
5
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.0.Alpha5</version>
</dependency>

集合校验

1
private List<@Email String> emails; // emails这个List中包含的每个字符串都必须是合法的Email地址

自定义约束规则

Bean Validation API规范要求约束注解定义以下要求:

  • 一个 message 属性:在违反约束的情况下返回一个默认 key,以用于创建错误消息。

  • 一个 groups 属性:允许指定此约束所属的验证分组。必须默认是一个空 Class 数组。

  • 一个 payload 属性:能被 Bean Validation API 客户端使用,以自定义一个注解的 payload 对象。API 本身不使用此属性。自定义 payload 可以是用来定义严重程度。

下面的例子通过实现一个身份证的验证器,演示如何自定义约束:

创建自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD}) // 作用在Field字段上或方法的参数上
@Retention(RetentionPolicy.RUNTIME) // 指定此类型的注解将在运行时通过反射方式可用
@Constraint(validatedBy = IdentityCardNumberValidator.class) // 触发的是IdentityCardNumberValidator这个验证类
public @interface IdentityCardNumber
{
// message 定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制
String message() default "身份证号码不合法";

// groups 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作
Class<?>[] groups() default {};

// payload 主要是针对bean的,使用不多
Class<? extends Payload>[] payload() default {};
}

自定义验证器

1
2
3
4
5
6
7
8
9
10
11
12
13
public class IdentityCardNumberValidator implements ConstraintValidator<IdentityCardNumber, Object> 
{
@Override
public void initialize(IdentityCardNumber identityCardNumber)
{
}

@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext)
{
return IdCardValidatorUtils.isValidate18Idcard(o.toString());
}
}

使用自定义的注解

1
2
3
@NotBlank(message = "身份证号不能为空")
@IdentityCardNumber(message = "身份证信息有误,请核对后提交")
private String clientCardNo;

内置约束规则

以下每个约束都有参数 message,groups 和 payload,其中,message 是提示消息,groups 可以根据情况来分组。(以下每一个注解都可以在相同元素上定义多个)

注解              版本 支持数据类型 说明
@Null 1.0 对象 验证注解的元素值是null
@NotNull 1.0 对象 验证注解的元素值不是null,但可以为empty(“”,” “,” “)
@AssertTrue 1.0 boolean/Boolean 验证注解的元素值是true
@AssertFalse 1.0 boolean/Boolean 验证注解的元素值是false
@Min(value) 1.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值大于等于@Min指定的value值
@Max(value) 1.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值小于等于@Max指定的value值
@DecimalMin(value, inclusive) 1.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值大于等于@DecimalMin指定的value值,inclusive:是否包括value的值,布尔值,默认为true
@DecimalMax(value, inclusive) 1.0 BigDecimal、BigInteger、 byte、short、int,、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值小于等于@ DecimalMax指定的value值,inclusive:是否包括value的值,布尔值,默认为true
@Negative 2.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值是负数
@NegativeOrZero 2.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值是负数或零
@Positive 2.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值是正数
@PositiveOrZero 2.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值是正数或零
@Size(min, max) 1.0 字符串、Collection、Map、数组等 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Digits(integer, fraction) 1.0 BigDecimal、BigInteger、 byte、short、int、long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值的整数位数和小数位数上限
integer:整数精度,fraction:小数精度
@Past 1.0 java.util.Date、java.util.Calendar、Joda Time类库的日期类型 验证注解的元素值(日期类型)比当前时间早
@PastOrPresent 2.0 java.util.Date、java.util.Calendar、Joda Time类库的日期类型 验证注解的元素值(日期类型)比当前时间早或者是现在
@Future 1.0 java.util.Date、java.util.Calendar、Joda Time类库的日期类型 验证注解的元素值(日期类型)比当前时间晚
@FutureOrPresent 2.0 java.util.Date、java.util.Calendar、Joda Time类库的日期类型 验证注解的元素值(日期类型)比当前时间晚或者是现在
@Pattern(regexp, flags) 1.0 String,任何CharSequence的子类型 验证注解的元素值与指定的正则表达式匹配
String regexp:正则表达式
Flag[] flags:标志的模式,默认为{}
@NotEmpty 2.0 CharSequence子类型、Collection、Map、数组 验证注解的元素值不为null,且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 2.0 CharSequence子类型 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首尾空格
@Email(regexp, flags) 2.0 CharSequence子类型(如String) 检查指定的字符序列是否为有效的电子邮件地址
String regexp:正则表达式,默认为.,
Flag[] *
flags**:标志的模式,默认为{}

分组校验

最常见的一个场景是,我们在增加和修改实体的时候,一般都是使用同一个实体类,但是增加和修改操作对实体的参数校验是不同的。比如,更新时候要校验userId,在保存的时候不需要校验userId,在两种情况下都要校验username。Java Bean Validation提供分组校验的功能,可以实现针对不同的场景应用不同的校验规则。

定义分组类

每个分组类只需要一个接口就可以了,即使用一个空接口做标识

1
2
3
4
5
6
7
public interface AddGroup 
{
}

public interface UpdateGroup
{
}

在校验规则上添加分组

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class Person
{
//添加分组信息:添加的时候不能有id,修改的时候却一定要有id
@Null(message = "id should be empty", groups = {AddGroup.class})
@NotNull(message = "id should not be empty", groups = {UpdateGroup.class})
private Integer id;

@Length(min = 2, max = 10, message = "name的长度为[2-10]之间")
@NotBlank(message = "name should not be empty")
private String name;
}

在Controller接口配置使用分组校验

注意要使用@Validated注解,而不是@Valid注解

1
2
3
4
5
6
7
8
9
10
11
12
13
@PostMapping("create")
public WebResult<String> create(@Validated({AddGroup.class, Default.class}) @RequestBody Person person)
{
log.info("person to create: {}", person);
return new WebResult<>(person.getName());
}

@PutMapping("update")
public WebResult<Void> update(@Validated({UpdateGroup.class, Default.class}) @RequestBody Person person)
{
log.info("person to update: {}", person);
return WebResult.SUCCESS;
}

分组继承

如果想要默认分组起作用,而其他分组也要校验,怎么操作呢? 可以在使用的时候,指定校验多个分组,如下:

1
2
3
4
public boolean addUser(@Validated({Default.class,NoIdGroup.class}) ValidatorVO user, BindingResult result)
{
...
}

这里是想 Default 分组一直都要校验,每次都带上有些赘余,像这样:@Validated({Default.class, ….),因此建议分组在定义的时候继承默认分组(javax.validation.groups.Default):public interface AddGroup extends Default { }

  • 配置分组的时候,记得不要漏掉默认分组Default.class,否则就只会校验groups = {AddGroup.class}的规则了
  • 上面的22个内置约束规则都有一个groups属性,如果不指定groups,默认为Default分组

分组排序

分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。比如用户输入的请求中包含用户名和密码,要求先校验用户名,再校验密码。

定义一个标识接口,使用@GroupSequence注解,对校验的注解进行组合排序:

1
2
3
4
5
6
7
8
9
10
public interface Name {}

public interface Password {}

@GroupSequence({Name.class, Password.class, Default.class})
public interface UserSequence
{
// Default.class必须添加,否则被校验的类中没有使用group标识的属性就不会校验
// @Validate注解的group属性不传时会调用默认的Default.class分组
}

然后使用Validated注解时,指定这个组合了顺序之后的新的标识接口:

1
@Validated(groups = {UserSequence.class	})

关于@Valid注解和@Validated注解

简单的讲:@ValidJSR的注解(javax.validation.Valid),@ValidatedSpring的注解(org.springframework.validation.annotation.Validated)。

JSR 规范支持手动校验,不直接支持使用注解校验,不过 spring 提供了分组校验注解扩展支持,即:@Validated,参数为 group 类集合。@Validated注解group属性不传时会调用默认的Default.class分组。

在全局校验中增加校验异常

ControllerAdvice

1
2
3
4
5
6
7
8
9
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
72
73
74
75
76
77
78
79
80
81
82
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;


@RestControllerAdvice
public class GlobalExceptionHandler
{
private Logger logger = LoggerFactory.getLogger(getClass());

private static int DUPLICATE_KEY_CODE = 1001;
private static int PARAM_FAIL_CODE = 1002;
private static int VALIDATION_CODE = 1003;

/**
* 处理自定义异常
*/
@ExceptionHandler(BizException.class)
public RspDTO handleRRException(BizException e)
{
logger.error(e.getMessage(), e);
return new RspDTO(e.getCode(), e.getMessage());
}

/**
* 方法参数校验
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
logger.error(e.getMessage(), e);
return new RspDTO(PARAM_FAIL_CODE, e.getBindingResult().getFieldError().getDefaultMessage());
}

/**
* ValidationException
*/
@ExceptionHandler(ValidationException.class)
public RspDTO handleValidationException(ValidationException e)
{
logger.error(e.getMessage(), e);
return new RspDTO(VALIDATION_CODE, e.getCause().getMessage());
}

/**
* ConstraintViolationException
*/
@ExceptionHandler(ConstraintViolationException.class)
public RspDTO handleConstraintViolationException(ConstraintViolationException e)
{
logger.error(e.getMessage(), e);
return new RspDTO(PARAM_FAIL_CODE, e.getMessage());
}

@ExceptionHandler(NoHandlerFoundException.class)
public RspDTO handlerNoFoundException(Exception e)
{
logger.error(e.getMessage(), e);
return new RspDTO(404, "路径不存在,请检查路径是否正确");
}

@ExceptionHandler(DuplicateKeyException.class)
public RspDTO handleDuplicateKeyException(DuplicateKeyException e)
{
logger.error(e.getMessage(), e);
return new RspDTO(DUPLICATE_KEY_CODE, "数据重复,请检查后提交");
}

@ExceptionHandler(Exception.class)
public RspDTO handleException(Exception e)
{
logger.error(e.getMessage(), e);
return new RspDTO(500, "系统繁忙,请稍后再试");
}
}

AOP

1
2
3
4
5
6
7
8
9
10
11
@Aspect
@Component
public class ControllerValidatorAspect
{
@Around("execution(* com.*.controller..*.*(..)) && args(..,result)")
public Object doAround(ProceedingJoinPoint pjp, result result)
{
result.getFieldErrors();
...
}
}

参考