
1 概述
本篇文章以Spring Boot為基礎(chǔ),從以下三個(gè)方向講述了如何設(shè)計(jì)一個(gè)優(yōu)秀的后端接口體系:
- 參數(shù)校驗(yàn):涉及Hibernate Validator的各種注解,快速失敗模式,分組,組序列以及自定義注解/Validator
- 異常處理:涉及ControllerAdvice/@RestControllerAdvice以及@ExceptionHandler
- 數(shù)據(jù)響應(yīng):涉及如何設(shè)計(jì)一個(gè)響應(yīng)體以及如何包裝響應(yīng)體
有了一個(gè)優(yōu)秀的后端接口體系,不僅有了規(guī)范,同時(shí)擴(kuò)展新的接口也很容易,本文演示了如何從零一步步構(gòu)建一個(gè)優(yōu)秀的后端接口體系。
2 新建工程
打開熟悉的IDEA,選擇依賴:

首先創(chuàng)建如下文件:

TestController.java:
@RestController
@RequestMapping("/")
@CrossOrigin(value = "http://localhost:3000")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
private final TestService service;
@PostMapping("test")
public String test(@RequestBody User user)
{
return service.test(user);
}
使用了@RequiredArgsConstructor代替@Autowired,由于筆者使用Postwoman測(cè)試,因此需要加上跨域注解@CrossOrigin,默認(rèn)3000端口(Postwoman端口)。
TestService.java:
@Service
public class TestService {
public String test(User user)
{
if(StringUtils.isEmpty(user.getEmail()))
return "郵箱不能為空";
if(StringUtils.isEmpty(user.getPassword()))
return "密碼不能為空";
if(StringUtils.isEmpty(user.getPhone()))
return "電話不能為空";
// 持久化操作
return "success";
}
}
業(yè)務(wù)層首先進(jìn)行了參數(shù)校驗(yàn),這里省略了持久化操作。
User.java:
@Data
public class User {
private String phone;
private String password;
private String email;
}
3 參數(shù)校驗(yàn)
首先來(lái)看一下參數(shù)校驗(yàn),上面的例子中在業(yè)務(wù)層完成參數(shù)校驗(yàn),這是沒(méi)有問(wèn)題的,但是,還沒(méi)進(jìn)行業(yè)務(wù)操作就需要進(jìn)行這么多的校驗(yàn)顯然這不是很好,更好的做法是,使用Hibernate Validator。
3.1 Hibernate Validator
3.1.1 介紹
JSR是Java Specification Requests的縮寫,意思是Java規(guī)范提案,是指向JCP(Java Community Process)提出新增一個(gè)標(biāo)準(zhǔn)化技術(shù)規(guī)范的正式請(qǐng)求。JSR-303是Java EE6中的一項(xiàng)子規(guī)范,叫作Bean Validation,Hibernate Validator是Bean Validator的參考實(shí)現(xiàn),除了實(shí)現(xiàn)所有JSR-303規(guī)范中的內(nèi)置constraint實(shí)現(xiàn),還有附加的constraint,詳細(xì)如下:
- @Null:被注解元素必須為null(為了節(jié)省篇幅下面用“元素”代表“被注解元素必須為”)
- @NotNull:元素不為null
- @AssertTrue:元素為true
- @AssertFalse:元素為false
- @Min(value):元素大于或等于指定值
- @Max(value):元素小于或等于指定值
- @DecimalMin(value):元素大于指定值
- @DecimalMax(value):元素小于指定值
- @Size(max,min):元素大小在給定范圍內(nèi)
- @Digits(integer,fraction):元素字符串中的整數(shù)位數(shù)規(guī)定最大integer位,小數(shù)位數(shù)規(guī)定最大fraction位
- @Past:元素是一個(gè)過(guò)去日期
- @Future:元素是將來(lái)日期
- @Pattern:元素需要符合正則表達(dá)式
其中Hibernate Validator附加的constraint如下:
- @Eamil:元素為郵箱
- @Length:字符串大小在指定范圍內(nèi)
- @NotEmpty:字符串必須非空(目前最新的6.1.5版本已棄用,建議使用標(biāo)準(zhǔn)的@NotEmpty)
- @Range:數(shù)字在指定范圍內(nèi)
而在Spring中,對(duì)Hibernate Validation進(jìn)行了二次封裝,添加了自動(dòng)校驗(yàn),并且校驗(yàn)信息封裝進(jìn)了特定的BindingResult中。下面看看如何使用。
3.1.2 使用
在各個(gè)字段加上@NotEmpty,并且郵箱加上@Email,電話加上11位限制,并且在各個(gè)注解加上message,表示對(duì)應(yīng)的提示信息:
@Data
public class User {
@NotEmpty(message = "電話不能為空")
@Length(min = 11,max = 11,message = "電話號(hào)碼必須11位")
private String phone;
@NotEmpty(message = "密碼不能為空")
@Length(min = 6,max = 20,message = "密碼必須為6-20位")
private String password;
@NotEmpty(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
}
對(duì)于String來(lái)說(shuō)有時(shí)候會(huì)使用@NotNull或@NotBlank,它們的區(qū)別如下:
- @NotEmpty:不能為null并且長(zhǎng)度必須大于0,除了String外,對(duì)于Collection/Map/數(shù)組也適用
- @NotBlank:只用于String,不能為null,并且調(diào)用trim()后,長(zhǎng)度必須大于0,也就是必須有除空格外的實(shí)際字符
- @NotNull:不能為null
接著把業(yè)務(wù)層的參數(shù)校驗(yàn)操作刪除,并把控制層修改如下:
@PostMapping("test")
public String test(@RequestBody @Valid User user, BindingResult bindingResult)
{
if(bindingResult.hasErrors())
{
for(ObjectError error:bindingResult.getAllErrors())
return error.getDefaultMessage();
}
return service.test(user);
}
在需要校驗(yàn)的對(duì)象上加上@Valid,并且加上BindingResult參數(shù),可以從中獲取錯(cuò)誤信息并返回。
3.1.3 測(cè)試
全部都使用錯(cuò)誤的參數(shù)設(shè)置,返回”郵箱格式不正確“:

第二次測(cè)試中除了密碼都使用正確的參數(shù),返回”密碼必須為6-20位“:

第三次測(cè)試全部使用正確的參數(shù),返回”success“:

3.2 校驗(yàn)?zāi)J皆O(shè)置
Hibernate Validator有兩種校驗(yàn)?zāi)J剑?/p>
- 普通模式:默認(rèn)模式,會(huì)校驗(yàn)所有屬性,然后返回所有的驗(yàn)證失敗信息
- 快速失敗模式:只要有一個(gè)驗(yàn)證失敗就返回
使用快速失敗模式需要通過(guò)HibernateValidateConfiguration以及ValidateFactory創(chuàng)建Validator,并且使用Validator.validate()進(jìn)行手動(dòng)驗(yàn)證。
首先添加一個(gè)生成Validator的類:
@Configuration
public class FailFastValidator<T> {
private final Validator validator;
public FailFastValidator()
{
validator = Validation
.byProvider(HibernateValidator.class).configure()
.failFast(true).buildValidatorFactory()
.getValidator();
}
public Set<ConstraintViolation<T>> validate(T user)
{
return validator.validate(user);
}
修改控制層的代碼,通過(guò)@RequiredArgsConstructor注入FailFastValidator<User>,并把原來(lái)的在User上的@Valid去掉,在方法體進(jìn)行手動(dòng)驗(yàn)證:
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
private final TestService service;
private final FailFastValidator<User> validator;
@PostMapping("test")
public String test(@RequestBody User user, BindingResult bindingResult)
{
Set<ConstraintViolation<User>> message = validator.validate(user);
message.forEach(t-> System.out.println(t.getMessage()));
// if(bindingResult.hasErrors())
// {
// bindingResult.getAllErrors().forEach(t->System.out.println(t.getDefaultMessage()));
// for(ObjectError error:bindingResult.getAllErrors())
// return error.getDefaultMessage();
// }
return service.test(user);
}
}
測(cè)試(連續(xù)三次校驗(yàn)的結(jié)果):

如果是普通模式(修改.failFast(false)),一次校驗(yàn)便會(huì)連續(xù)輸出三個(gè)信息:

3.3 @Valid與@Validated
@Valid是javax.validation包里面的,而@Validated是org.springframework.validation.annotation里面的,是@Valid的一次封裝,相當(dāng)于是@Valid的增強(qiáng)版,供Spring提供的校驗(yàn)機(jī)制使用,相比起@Valid,@Validated提供了分組以及組序列的功能。下面分別進(jìn)行介紹。
3.4 分組
當(dāng)需要在不同的情況下使用不同的校驗(yàn)方式時(shí),可以使用分組校驗(yàn)。比如在注冊(cè)時(shí)不需要校驗(yàn)id,修改信息時(shí)需要校驗(yàn)id,但是默認(rèn)的校驗(yàn)方式在兩種情況下全部都校驗(yàn),這時(shí)就需要使用分組校驗(yàn)。
下面以不同的組別校驗(yàn)電話號(hào)碼長(zhǎng)度的不同進(jìn)行說(shuō)明,修改User類如下:
@Data
public class User {
@NotEmpty(message = "電話不能為空")
@Length(min = 11,max = 11,message = "電話號(hào)碼必須11位",groups = {GroupA.class})
@Length(min = 12,max = 12,message = "電話號(hào)碼必須12位",groups = {GroupB.class})
private String phone;
@NotEmpty(message = "密碼不能為空")
@Length(min = 6,max = 20,message = "密碼必須為6-20位")
private String password;
@NotEmpty(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
public interface GroupA{}
public interface GroupB{}
}
在@Length中加入了組別,GroupA表示電話需要為11位,GroupB表示電話需要為12位,GroupA/GroupB是User中的兩個(gè)空接口,然后修改控制層:
public String test(@RequestBody @Validated({User.GroupB.class}) User user, BindingResult bindingResult)
{
if(bindingResult.hasErrors())
{
bindingResult.getAllErrors().forEach(t->System.out.println(t.getDefaultMessage()));
for(ObjectError error:bindingResult.getAllErrors())
return error.getDefaultMessage();
}
return service.test(user);
}
在@Validated中指定為GroupB,電話需要為12位,測(cè)試如下:

3.5 組序列
默認(rèn)情況下,不同組別的約束驗(yàn)證的無(wú)序的,也就是說(shuō),對(duì)于下面的User類:
@Data
public class User {
@NotEmpty(message = "電話不能為空")
@Length(min = 11,max = 11,message = "電話號(hào)碼必須11位")
private String phone;
@NotEmpty(message = "密碼不能為空")
@Length(min = 6,max = 20,message = "密碼必須為6-20位")
private String password;
@NotEmpty(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
}
每次進(jìn)行校驗(yàn)的順序不同,三次測(cè)試結(jié)果如下:


有些時(shí)候順序并不重要,而有些時(shí)候順序很重要,比如:
- 第二個(gè)組中的約束驗(yàn)證依賴于一個(gè)穩(wěn)定狀態(tài)運(yùn)行,而這個(gè)穩(wěn)定狀態(tài)由第一個(gè)組來(lái)進(jìn)行驗(yàn)證
- 某個(gè)組的驗(yàn)證比較耗時(shí),CPU和內(nèi)存的使用率相對(duì)較大,最優(yōu)的選擇是將其放在最后進(jìn)行驗(yàn)證
因此在進(jìn)行組驗(yàn)證的時(shí)候需要提供一種有序的驗(yàn)證方式,一個(gè)組可以定義為其他組的序列,這樣就可以固定每次驗(yàn)證的順序而不是隨機(jī)順序,另外如果驗(yàn)證組序列中,前面的組驗(yàn)證失敗,則后面的組不會(huì)驗(yàn)證。
例子如下,首先修改User類并定義組序列:
@Data
public class User {
@NotEmpty(message = "電話不能為空",groups = {First.class})
@Length(min = 11,max = 11,message = "電話號(hào)碼必須11位",groups = {Second.class})
private String phone;
@NotEmpty(message = "密碼不能為空",groups = {First.class})
@Length(min = 6,max = 20,message = "密碼必須為6-20位",groups = {Second.class})
private String password;
@NotEmpty(message = "郵箱不能為空",groups = {First.class})
@Email(message = "郵箱格式不正確",groups = {Second.class})
private String email;
public interface First{}
public interface Second{}
@GroupSequence({First.class,Second.class})
public interface Group{}
}
定義了兩個(gè)空接口First和Second表示順序,同時(shí)在Group中使用@GroupSequence指定了順序。
接著修改控制層,在@Validated中定義組:
這樣就能按照固定的順序進(jìn)行參數(shù)校驗(yàn)了。
3.6 自定義校驗(yàn)
盡管Hibernate Validator中的注解適用情況很廣了,但是有時(shí)候需要特定的校驗(yàn)規(guī)則,比如密碼強(qiáng)度,人為判定弱密碼還是強(qiáng)密碼。也就是說(shuō),此時(shí)需要添加自定義校驗(yàn)的方式,有兩種處理方法:
- 自定義注解
- 自定義Validator
首先來(lái)看一下自定義注解的方法。
3.6.1 自定義注解
這里添加一個(gè)判定弱密碼的注解WeakPassword:
@Documented
@Constraint(validatedBy = WeakPasswordValidator.class)
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WeakPassword{
String message() default "請(qǐng)使用更加強(qiáng)壯的密碼";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
同時(shí)添加一個(gè)實(shí)現(xiàn)了ConstraintValidator<A,T>的WeakPasswordValidator,當(dāng)密碼長(zhǎng)度大于10位時(shí)才符合條件,否則返回false表示校驗(yàn)不通過(guò):
public class WeakPasswordValidator implements ConstraintValidator<WeakPassword,String> {
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
return s.length() > 10;
}
@Override
public void initialize(WeakPassword constraintAnnotation) {}
}
接著可以修改User如下,在對(duì)應(yīng)的字段加上自定義注解@WeakPassword:
@Data
public class User {
//...
@WeakPassword(groups = {Second.class})
private String password;
//...
}
測(cè)試如下:

3.6.2 自定義Validator
除了自定義注解之外,還可以自定義Validator來(lái)實(shí)現(xiàn)自定義的參數(shù)校驗(yàn),需要實(shí)現(xiàn)Validator接口:
@Component
public class WeakPasswordValidator implements Validator{
@Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
@Override
public void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmpty(errors,"password","password.empty");
User user = (User)o;
if(user.getPassword().length() <= 10)
errors.rejectValue("password","Password is not strong enough!");
}
}
實(shí)現(xiàn)其中的supports以及validate:
- support:可以驗(yàn)證該類是否是某個(gè)類的實(shí)例
- validate:當(dāng)supports返回true后,驗(yàn)證給定對(duì)象o,當(dāng)出現(xiàn)錯(cuò)誤時(shí),向errors注冊(cè)錯(cuò)誤
ValidationUtils.rejectIfEmpty校驗(yàn)當(dāng)對(duì)象o中某個(gè)字段屬性為空時(shí),向其中的errors注冊(cè)錯(cuò)誤,注意并不會(huì)中斷語(yǔ)句的運(yùn)行,也就是即使password為空,user.getPassword()還是會(huì)運(yùn)行,這時(shí)會(huì)拋出空指針異常。下面的errors.rejectValue同樣道理,并不會(huì)中斷語(yǔ)句的運(yùn)行,只是注冊(cè)了錯(cuò)誤信息,中斷的話需要手動(dòng)拋出異常。
修改控制層中的返回值,改為getCode():
if(bindingResult.hasErrors())
{
bindingResult.getAllErrors().forEach(t-> System.out.println(t.getCode()));
for(ObjectError error:bindingResult.getAllErrors())
return error.getCode();
}
return service.test(user);
測(cè)試:

4 異常處理
到這里參數(shù)校驗(yàn)就完成了,下一步是處理異常。
如果將參數(shù)校驗(yàn)中的BindingResult去掉,就會(huì)將整個(gè)后端異常返回給前端:
//public String test(@RequestBody @Validated({User.Group.class}) User user, BindingResult bindingResult)
public String test(@RequestBody @Validated({User.Group.class}) User user)
復(fù)制代碼

這樣雖然后端是方便了,不需要每一個(gè)接口都加上BindingResult,但是前端不好處理,整個(gè)異常都返回了,因此后端需要捕捉這些異常,但是,不能手動(dòng)去捕捉每一個(gè),這樣還不如之前使用BindingResult,這種情況下就需要用到全局的異常處理。
4.1 基本使用
處理全局異常的步驟如下:
- 創(chuàng)建全局異常處理的類:加上@ControllerAdvice/@RestControllerAdvice注解(取決于控制層用的是@Controller/@RestController,@Controller可以跳轉(zhuǎn)到相應(yīng)頁(yè)面,返回JSON等加上@ResponseBody即可,而@RestController相當(dāng)于@Controller+@ResponseBody,返回JSON無(wú)需加上@ResponseBody,但是視圖解析器無(wú)法解析jsp以及html頁(yè)面)
- 創(chuàng)建異常處理方法:加上@ExceptionHandler指定想要處理的異常類型
- 處理異常:在對(duì)應(yīng)的處理異常方法中處理異常
這里增加一個(gè)全局異常處理類GlobalExceptionHandler:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
{
ObjectError error = e.getBindingResult().getAllErrors().get(0);
return error.getDefaultMessage();
}
}
首先加上@RestControllerAdvice,并在異常處理方法上加上@ExceptionHandler。
接著修改控制層,去掉其中的BindingResult:
@PostMapping("test")
public String test(@RequestBody @Validated({User.Group.class}) User user)
{
return service.test(user);
}
然后就可以進(jìn)行測(cè)試了:

全局異常處理相比起原來(lái)的每一個(gè)接口都加上BindingResult方便很多,而且可以集中處理所有異常。
4.2 自定義異常
很多時(shí)候都會(huì)用到自定義異常,這里新增一個(gè)測(cè)試異常TestException:
@Data
public class TestException extends RuntimeException{
private int code;
private String msg;
public TestException(int code,String msg)
{
super(msg);
this.code = code;
this.msg = msg;
}
public TestException()
{
this(111,"測(cè)試異常");
}
public TestException(String msg)
{
this(111,msg);
}
}
接著在剛才的全局異常處理類中添加一個(gè)處理該異常的方法:
@ExceptionHandler(TestException.class)
public String testExceptionHandler(TestException e)
{
return e.getMsg();
}
在控制層進(jìn)行測(cè)試:
@PostMapping("test")
public String test(@RequestBody @Validated({User.Group.class}) User user)
{
throw new TestException("出現(xiàn)異常");
// return service.test(user);
}
結(jié)果如下:

5 數(shù)據(jù)響應(yīng)
在處理好了參數(shù)校驗(yàn)以及異常處理之后,下一步就是要設(shè)置統(tǒng)一的規(guī)范化的響應(yīng)數(shù)據(jù),一般來(lái)說(shuō)無(wú)論響應(yīng)成功還是失敗都會(huì)有一個(gè)狀態(tài)碼,響應(yīng)成功還會(huì)攜帶響應(yīng)數(shù)據(jù),響應(yīng)失敗則攜帶相應(yīng)的失敗信息,因此,第一步是設(shè)計(jì)一個(gè)統(tǒng)一的響應(yīng)體。
5.1 統(tǒng)一響應(yīng)體
統(tǒng)一響應(yīng)體需要?jiǎng)?chuàng)建響應(yīng)體類,一般來(lái)說(shuō),響應(yīng)體需要包含:
- 狀態(tài)碼:String/int
- 響應(yīng)信息:String
- 響應(yīng)數(shù)據(jù):Object/T(泛型)
這里簡(jiǎn)單的定義一個(gè)統(tǒng)一響應(yīng)體Result:
@Data
@AllArgsConstructor
public class Result<T> {
private String code;
private String message;
private T data;
}
接著修改全局異常處理類:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
{
ObjectError error = e.getBindingResult().getAllErrors().get(0);
return new Result<>(error.getCode(),"參數(shù)校驗(yàn)失敗",error.getDefaultMessage());
}
@ExceptionHandler(TestException.class)
public Result<String> testExceptionHandler(TestException e)
{
return new Result<>(e.getCode(),"失敗",e.getMsg());
}
使用Result<String>封裝返回值,測(cè)試如下:

可以看到返回了一個(gè)比較友好的信息,無(wú)論是響應(yīng)成功還是響應(yīng)失敗都會(huì)返回同一個(gè)響應(yīng)體,當(dāng)需要返回具體的用戶數(shù)據(jù)時(shí),可以修改控制層接口直接返回Result<User>:
@PostMapping("test")
public Result<User> test(@RequestBody @Validated({User.Group.class}) User user)
{
return service.test(user);
}
測(cè)試:

5.2 響應(yīng)碼枚舉
通常來(lái)說(shuō)可以把響應(yīng)碼做成枚舉類:
@Getter
public enum ResultCode {
SUCCESS("111","成功"),FAILED("222","失敗");
private final String code;
private final String message;
ResultCode(String code,String message)
{
this.code = code;
this.message = message;
}
}
枚舉類封裝了狀態(tài)碼以及信息,這樣在返回結(jié)果時(shí),只需要傳入對(duì)應(yīng)的枚舉值以及數(shù)據(jù)即可:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
{
ObjectError error = e.getBindingResult().getAllErrors().get(0);
return new Result<>(ResultCode.FAILED,error.getDefaultMessage());
}
@ExceptionHandler(TestException.class)
public Result<String> testExceptionHandler(TestException e)
{
return new Result<>(ResultCode.FAILED,e.getMsg());
}
}
5.3 全局包裝響應(yīng)體
統(tǒng)一響應(yīng)體是個(gè)很好的想法,但是還可以再深入一步去優(yōu)化,因?yàn)槊看畏祷刂岸夹枰獙?duì)響應(yīng)體進(jìn)行包裝,雖然只是一行代碼但是每個(gè)接口都需要包裝一下,這是個(gè)很麻煩的操作,為了更進(jìn)一步“偷懶”,可以選擇實(shí)現(xiàn)ResponseBodyAdvice<T>來(lái)進(jìn)行全局的響應(yīng)體包裝。
修改原來(lái)的全局異常處理類如下:
@RestControllerAdvice
public class GlobalExceptionHandler implements ResponseBodyAdvice<Object> {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
{
ObjectError error = e.getBindingResult().getAllErrors().get(0);
return new Result<>(ResultCode.FAILED,error.getDefaultMessage());
}
@ExceptionHandler(TestException.class)
public Result<String> testExceptionHandler(TestException e)
{
return new Result<>(ResultCode.FAILED,e.getMsg());
}
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return !methodParameter.getParameterType().equals(Result.class);
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
return new Result<>(o);
}
}
實(shí)現(xiàn)了ResponseBodyAdvice<Object>:
- supports方法:判斷是否支持控制器返回方法類型,可以通過(guò)supports判斷哪些類型需要包裝,哪些不需要包裝直接返回
- beforeBodyWrite方法:當(dāng)supports返回true后,對(duì)數(shù)據(jù)進(jìn)行包裝,這樣在返回?cái)?shù)據(jù)時(shí)就無(wú)需使用Result<User>手動(dòng)包裝,而是直接返回User即可
接著修改控制層,直接返回實(shí)體類User而不是響應(yīng)體包裝類Result<User>:
@PostMapping("test")
public User test(@RequestBody @Validated({User.Group.class}) User user)
{
return service.test(user);
}
測(cè)試輸出如下:

5.4 繞過(guò)全局包裝
雖然按照上面的方式可以使后端的數(shù)據(jù)全部按照統(tǒng)一的形式返回給前端,但是有時(shí)候并不是返回給前端而是返回給其他第三方,這時(shí)候不需要code以及msg等信息,只是需要數(shù)據(jù),這樣的話,可以提供一個(gè)在方法上的注解來(lái)繞過(guò)全局的響應(yīng)體包裝。
比如添加一個(gè)@NotResponseBody注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface NotResponseBody {
}
接著需要在處理全局包裝的類中,在supports中進(jìn)行判斷:
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return !(
methodParameter.getParameterType().equals(Result.class)
||
methodParameter.hasMethodAnnotation(NotResponseBody.class)
);
}
最后修改控制層,在需要繞過(guò)的方法上添加自定義注解@NotResponseBody即可:
@PostMapping("test")
@NotResponseBody
public User test(@RequestBody @Validated({User.Group.class}) User user)
6 總結(jié)

7 源碼
直接clone下來(lái)使用IDEA打開即可,每一次優(yōu)化都做了一次提交,可以看到優(yōu)化的過(guò)程,喜歡的話記得點(diǎn)個(gè)贊加關(guān)注哦
