发布于2022-07-08
前言
今天给大家介绍一下SpringBoot(Spring也可以)项目中如何优雅的整合==JSR-303==进行参数校验 以及对全局的异常进行处理封装
什么是JSR-303
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回.Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint.
添加依赖
SpringBoot项目可以用starter组件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
老点的spring项目可以用下面这个(同样适用于springBoot项目)
<!-- 参数校验 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
SpringBoot项目设置全局异常处理
参数在校验失败的时候会抛出的==MethodArgumentNotValidException==异常,可以在全局的异常处理器中捕捉这个异常,将报错信息同一封装之后返回给客户端。
全局异常处理器代码如下
注意: 此控制器为接口形式 处理异常的方法均为接口的default方法 此举不同于其他博客的注解形式和继承形式 注解形式是直接控制全局灵活性欠佳(比如你有一个三方回调的控制器类不想被监控) 而继承的方式又绑定的太死 所以采用了接口和default方法
/**
* <p>
* 全局异常处理的基础类 必须实现此类
* </p>
* @author liang
* @date 2021/5/28 17:46
*/
public interface HandleExceptionController {
/**
* 处理实现此类的所有程序抛出的业务代码异常
* @param e 业务代码自定义异常
* @return 封装好的统一返回结果
*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
default ResponseResult handleException(ServiceException e) {
ResponseResult result = new ResponseResult();
result.setCode(e.getCode());
result.setMessage(e.getMessage());
result.setData(e.getData());
return result;
}
/**
* 处理实现此类的所有程序抛出的参数绑定异常
* @param e 参数绑定异常
* @return 封装好的统一返回结果
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
default ResponseResult methodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return new ResponseResult(ResultEnum.REQUIRED_MISSING.getCode(), message,null);
}
/**
* 处理实现此类的所有程序抛出的未知异常
* @param e 程序抛出来的异常
* @return 封装好的统一返回结果
*/
@ExceptionHandler(Exception.class)
@ResponseBody
default ResponseResult handleException(Exception e) {
return new ResponseResult(ResultEnum.ERROR, e.getMessage());
}
}
示例Controller代码
@Validated注解是用来标识这个参数类需要进行参数校验的
@RestController
@RequestMapping("/api/test")
@Api(value = "TestController", tags = { "测试类" })
public class TestController implements HandleExceptionController {
@ApiOperation(value = "测试查询", notes = "测试查询")
@PostMapping(value = "/queryTest")
public ResponseResult queryTest(@RequestBody @Validated QueryTestParam param){
JSONObject result = new JSONObject();
return new ResponseResult(ResultEnum.SUCCESS,result);
}
}
参数类
省略get和set方法
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
@NotNull(message = "integer不能为空")
private Integer integer;
@NotBlank(message = "string不能为空")
private String string;
}
如果参数类中的integer为null或者string为空字符串则会抛出MethodArgumentNotValidException异常,紧接着此异常会被HandleExceptionController接口的methodArgumentNotValidException方法捕获到 注意这行代码: @ExceptionHandler(MethodArgumentNotValidException.class)这个注解表示你的方法要捕获处理的异常是什么 如果你要捕获的异常有继承关系 他会优先捕获子类的详细类型的异常 所以接口中有一个handleException方法来捕获最终的异常exception
javax参数校验
内嵌的注解列表
注释 | 解释 |
---|---|
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@NotBlank | 被注释的元素必须为字符串而且不能为空 |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
如何使用
get和set方法省略
import javax.validation.constraints.Future;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
@NotNull(message = "文章id不能为空")
@Min(value = 1,message = "文章ID不能为负数")
private Integer id;
@NotBlank(message = "文章内容不能为空")
private String content;
@NotBlank(message = "作者Id不能为空")
private String authorId;
@Future(message = "提交时间不能为过去时间")
private Date submitTime;
}
分组校验
举例说明 新增和修改一篇文章 修改只比新增接口多传一个主键id,这时候很明显不想要建立两个非常相似的实体类去分别接收,这时候该怎么办呢?
答案显而易见,就是进行分组校验 新增是一个分组 修改是一个分组
所有的校验注解都有一个groups属性用来指定分组,Class<?>[]类型,没有实际意义,因此只需要定义一个或者多个接口用来区分即可。
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
/**
*添加的校验分组接口
*/
public interface add{}
/**
* 修改的校验分组接口
*/
public interface update{}
@NotNull(message = "文章id不能为空",groups = update.class)
@Min(value = 1,message = "文章ID不能为负数",groups = update.class)
private Integer id;
@NotBlank(message = "文章内容不能为空",groups = {add.class,update.class})
private String content;
@NotBlank(message = "作者Id不能为空",groups = {add.class,update.class})
private String authorId;
@Future(message = "提交时间不能为过去时间",groups = {add.class,update.class})
private Date submitTime;
}
JSR303本身的@Valid并不支持分组校验,但是Spring在其基础提供了一个注解@Validated支持分组校验。@Validated这个注解value属性指定需要校验的分组。
@RestController
@RequestMapping("/api/test")
@Api(value = "TestController", tags = { "测试类" })
public class TestController implements HandleExceptionController {
@ApiOperation(value = "测试查询", notes = "测试查询")
@PostMapping(value = "/queryTest")
public ResponseResult queryTest(@RequestBody @Validated(value =QueryTestParam.add.class ) QueryTestParam param){
JSONObject result = new JSONObject();
return new ResponseResult(ResultEnum.SUCCESS,result);
}
}
嵌套校验
嵌套校验 就是一个实体中包含了另一个实体 而这两个实体都需要校验
嵌套校验很简单,只需要在嵌套的实体属性标注@Valid注解,则其中的属性也将会得到校验,否则不会校验。
/**
* @author liang
* @date 2021年12月29日 14:23
*/
public class QueryTestParam {
@Valid
private AddParam addParam;
}
最后附赠一个校验工具类 可以在代码里随时对对象进行javax的校验
import org.hibernate.validator.HibernateValidator;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
/**
* @author liang
*/
public class ValidatorUtils {
private static final Validator VALIDATOR_ALL = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
/**
* 校验不通过则抛出去自定义异常 {@link ServiceException}
* @param obj 需要校验的对象
* @param groups 需要校验的分组(可变长参数列表)
* @param <T> 对象类型
*/
public static <T> void validatorByGroups(T obj, Class<?>... groups) {
Set<ConstraintViolation<T>> constraintViolations = VALIDATOR_ALL.validate(obj, groups);
// 抛出检验异常
if (constraintViolations.size() > 0) {
throw new ServiceException(ResultEnum.REQUIRED_MISSING.getCode(),constraintViolations.iterator().next().getMessage());
}
}
/**
* 校验不通过则抛出去自定义异常 {@link ServiceException}
* @param obj 需要校验的对象
* @param <T> 对象类型
*/
public static <T> void validatorObject(T obj) {
Set<ConstraintViolation<T>> constraintViolations = VALIDATOR_ALL.validate(obj);
// 抛出检验异常
if (constraintViolations.size() > 0) {
throw new ServiceException(ResultEnum.REQUIRED_MISSING.getCode(),constraintViolations.iterator().next().getMessage());
}
}
}
manchester united tøj 游客 2022-12-14 11:14 回复
Araofclmh australia vm barn Drakt KaseyTls
Kristieif real madrid drakt ThadHilly
RhysHorns liverpool tröja barn DedraNett
AlissaGal Maglia Chile Mondiali 2022 MilanFait
OnitaSass chelsea drakt AlannahEa
NaomiLev liverpool tröja barn JuanTregu
AdrieneBe Brasilien VM 2022 Landsholdstrøje MilfordNe
SimonJsh maglie napoli 2022 JuliaLayt
EzraPouli marseille tröja LinoldDi
JannieSpr chelsea matchtröja GlendaMes
CraigTasm Atalanta Drakt LenaBella
LashondaB Maglia Armenia Mondiali 2022 JermaineE
RicoLeona danmark VM tröja Shananwsw
Gildaohft VM fotbollstrojör VM CaryBrans
TrenavcZ Portugal VM 2022 Landsholdstrøje JewelJano
Jacquetta Island VM 2022 Landsholdstrøje FreyaYdyr
TobiasHer Portugal VM 2022 Landsholdstrøje AzucenaVe
BeatriceV australia VM 2022 Drakt VeronaTul
MoseButtr Maglia Wales Mondiali 2022 StacyHytt
TobiasHer Portugal VM 2022 Landsholdstrøje AzucenaVe
il: Clark11370 游客 2022-10-31 08:19 回复
Margenemh 357 fotballdrakter til bedriftslag Coreyc – Coin Media – журнал о заработке в интернете
Francesco
CasieDalu maglia argentina AbbieLand
JessRoman Website Darrellie
PaulinaRu Profile of ChristieRy SidneyMgw
ToneyKrog Liverpool Tøj - Blog Profile - iotaJots JaimieChe
TriciaBri Wolearn: GregoryEmelia: Magliette Calcio Poco
Prezzo SheltonTh
WayneHone 438 Chile Tröja Barn Leopol – Profile – Primescool Forum CharlineC
VitoHartf norge bortedrakt to receive adulation, always
insists the result is most important and will therefore be
thoroughly disheartened by this display.
As is often the case in friendly matches, there was a lack FedericoW صفحه اصلی – Maglia Porto KurtBrise of urgency for large periods during this
match, though Wales did link play promisingly in attack through
Wilson and Brooks. Italy will be among the nations in pot one
for the Euro 2020 qualifying draw in Dublin on 2 December having drawn 0-0 against Portugal
in their previous match to ensure a second-place finish in Nations League Group 3A.
TanjaFitz
Elizabeth manchester city tröjor KenPoulin
CarenWhar Maglia Juventus Bambino EmeliaBow
Stephaine liverpool drakt DarlaFulm
FerneSuvg barcelona FC drakt Meaganold
EmilyEise arsenal drakt KourtneyS
Kellyejoh inter trøje JamaalWor
Loisniyet psg drakt MaricelaS
CecileSwa lavora come produttore esecutivo della serie CharissaC
ChastityF Home - Liverpool Drakt EmilGmvut
CasieDalu maglia argentina AbbieLand
UrsulaLof rashford numero maglia KareemCap
SSNGilber arsenal drakt 2022 MelodeePe
arsenal trøjer 游客 2022-10-12 15:35 回复
BrittnyFo atletico madrid tröja ClaritaBu
ZJRBridge magliette psg JosephDur
Valentina real madrid drakt MarionTol
NevaLangt manchester united drakt BraydenBo
RodolfoCa Real Madrid Tröja KelliefjL
BettinaMi Notenbrood op top niveau MillardMh
RichardMo arsenal drakt Bradleyjo
TanyaNuge Spanien landsholdstrøje AprilBuck
JoannaShe Billige Polen Fotballdrakter NiklasWes
Maryellen terza maglia milan PrincessN
AnhGiffen real madrid tröja FredricAu
JoannaShe Billige Polen Fotballdrakter NiklasWes
EssieOate juventus matchtröja PreciousF
EdnaTippe sabinahogan447 EffieOcon
MichaelaH liverpool tröja barn KayleighM
JonnieHoc nuova maglia roma 2021/22 CortezSha
YDZClaire ac milan drakt KathleneH
CedricBac manchester united drakt Peterysjl
HaicoQcmw Maglia Brasile Femmina ThereseJu
DoreenMes napoli trøje JadaLanni
maglie hellas verona 2022 游客 2022-09-30 02:14 回复
Your style is very unique in comparison to other folks I have read stuff from.
Many thanks for posting when you have the opportunity, Guess I'll just bookmark
this blog. maglie hellas verona 2022
IlanaGxrn frankrike tröja JaneenZtt
inter milan tröja 游客 2022-09-29 18:00 回复
I truly love your site.. Pleasant colors & theme.
Did you make this website yourself? Please reply back as I'm hoping to create my own website and would like to learn where you got this
from or just what the theme is named. Cheers! inter milan tröja
LilianaCz Frankrike Tröja Barn NganRjpfi
atletico madrid trøje 游客 2022-09-29 13:40 回复
Thank you for the good writeup. It in fact was a amusement account it.
Look advanced to far added agreeable from you! However,
how could we communicate? atletico madrid trøje
TrishaBil nuove magliette napoli WarrenUlv
liverpool bortedrakt 游客 2022-09-28 22:04 回复
Whats up are using Wordpress for your site platform? I'm new to the blog world but I'm trying to get
started and set up my own. Do you require any html coding knowledge to make your own blog?
Any help would be greatly appreciated! liverpool bortedrakt
Marcelino Kjøp Russland Landslagsdrakt RachaelBe