Springboot学习06-Spring AOP封装接口自定义校验

关键字

  BindingResult、Spring AOP、自定义注解、自定义异常处理、ConstraintValidator

前言

  在实际项目中,对接口的传如的参数需要做校验处理,原来都是在接口里面直接进行if判断,虽然简单,但是每个接口都要重复写,显得冗余;并且返回的数据也无法很好的自定义说明校验情况;如下;

    @RequestMapping(value = { "/get/authcode" }, method = {RequestMethod.POST })
public Object getSignInAuthCode(@RequestBody AuthCodeReq authCodeReq) throws Exception {
//每个数据都要这样重复写
if(StringUtils.isBlank(authCodeReq.getMobile())){
//返回数据也是固定格式,无法知道究竟是什么数据校验没通过
return ResponseMessageEnum.ARGUMENT_EXCEPTION.toString();
}
//业务逻辑略
}

正文

0-封装全局数据校验功能的目的

  1-避免每个接口重复使用if判断,显得冗余

  2-可以自定义返回数据,告诉前端什么数据检验失败

1-POST请求业务逻辑(且数据以json格式放在body中传入)

  1-将数据校验的具体内容放在POJO中,通过注解进行,见源码-01
  2-当前端URL请求(POST请求,且数据以json格式放在body中传入),且有数据校验失败时,传参BindException exception会接收校验失败的结果,见源码-02
  3-使用Spring AOP的前置方法处理数据检验,见源码-03
  3-1-自定义注解MyValidateAopAnnotation(见源码-04),用于定位AOP的Pointcut(见源码-03)
  3-2-AOP前置方法,根据joinPoint获取接口方法BindingResult参数值(见源码-03)
  3-3-如果bindingResult.hasErrors()为true,则表明数据校验没有通过(见源码-03),则直接抛出BindException异常()
  3-4-在GlobalExceptionHandler类中的BindExceptionHandler方法,专门处理BindException异常,返回json数据(见源码-05)

2-GET请求

3-源码分析

//1-POJO
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class ValidateAuthCodeReq {
@NotEmpty(message = "手机号不能为空")//message将在接口返回数据中得以体现
private String mobile;//手机号
@NotEmpty(message = "验证码不能为空")
private String code;//验证码 public String getMobile() {
return mobile;
} public void setMobile(String mobile) {
this.mobile = mobile;
} public String getCode() {
return code;
} public void setCode(String code) {
this.code = code;
}
} //2-controller层接口方法
@RestController
@RequestMapping(value="/api/shop/login")
public class ApiShopLoginController extends ApiShopBaseController { //2-2-校验并绑定手机号
@MyValidateAopAnnotation//自定义注解,用户AOP定位方法
@RequestMapping(value = { "/validate/authcode" }, method = {RequestMethod.POST })
public Object validateAndBind( @Valid @RequestBody ValidateAuthCodeReq validateAuthCodeReq,BindingResult bindingResult) throws Exception {
//BindingResult封装了数据校验结果
//业务逻辑略
}
} //3-AOP方法,统一处理数据校验
@Aspect
@Component
public class ExceptionAspect {
//根据自定义注解MyValidateAopAnnotation定位方法
@Pointcut("@annotation(com.hs.web.common.exception.MyValidateAopAnnotation)")
public void bindPointCut() {
} @Before("bindPointCut()")
public void before(JoinPoint joinPoint) throws BindException {
// 接收到请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取请求的request
HttpServletRequest request = attributes.getRequest();
//获取BindingResult,并进行判断
Object[] args = joinPoint.getArgs();
BindingResult bindingResult = (BindingResult)args[args.length-1];
if(bindingResult.hasErrors()){
throw new BindException(bindingResult);
}
System.out.println("args[args.length-1]: "+ args[args.length-1]);
System.out.println("bindingResult" + bindingResult);
}
} //4-自定义注解MyValidateAopAnnotation-目的是为了AOP定位
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
public @interface MyValidateAopAnnotation { } //5-全局异常处理类 import com.hs.common.util.json.JsonUtil;
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
//数据校验
@ExceptionHandler(value=BindException.class)
public String BindExceptionHandler(HttpServletRequest request,BindException exception) throws Exception{
logger.warn(exception);
//1-封装异常说明
List<ObjectError> errorList = exception.getAllErrors();
StringBuffer sb = new StringBuffer();
for(ObjectError error : errorList){
sb.append("参数" + exception.getFieldError().getField() + "异常:" + exception.getFieldError().getDefaultMessage() + ";");
}
//2-封装返回参数
ExceptionResponseBean detailBean = new ExceptionResponseBean(
GlobalExceptionEnum.ERROR_DATA_VALIDATION.getCode(),
GlobalExceptionEnum.ERROR_DATA_VALIDATION.getMsg(),
sb.toString());
//3-以Json格式返回数据
return JsonUtil.toJson(detailBean).toString();
} } //6-异常情况枚举 import com.hs.common.util.json.JsonUtil;
public enum GlobalExceptionEnum { OTHER_EXCEPTION(800, "出现异常", "其它异常,待识别", Exception.class), ERROR_DATA_VALIDATION(801, "数据校验异常", "请求参数数据校验异常", BindException.class), ; private int code;
private String msg;
private ExceptionResponseDetailBean data;
private Class exception;
private GlobalExceptionEnum(int code, String msg, String data, Class exception) {
this.code = code;
this.msg = msg;
this.data = new ExceptionResponseDetailBean(data);
this.exception = exception;
} public int getCode() {
return code;
} public String getMsg() {
return msg;
} public ExceptionResponseDetailBean getData() {
return data;
} public Class getException() {
return exception;
} } //7-ExceptionResponseBean异常返回POJO
public class ExceptionResponseBean { private int code;
private String msg;
private ExceptionResponseDetailBean data; public ExceptionResponseBean() {
super();
} public ExceptionResponseBean(int code, String msg, String detail) {
super();
this.code = code;
this.msg = msg;
this.data = new ExceptionResponseDetailBean(detail);
} public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public ExceptionResponseDetailBean getData() {
return data;
}
public void setData(ExceptionResponseDetailBean data) {
this.data = data;
} } //8-ExceptionResponseDetailBean异常返回明细POJO public class ExceptionResponseDetailBean { private String detail; public ExceptionResponseDetailBean() {
super();
} public ExceptionResponseDetailBean(String detail) {
super();
this.detail = detail;
} public String getDetail() {
return detail;
} public void setDetail(String detail) {
this.detail = detail;
}
}

4-应用示例

5-进一步优化校验

5-1-优点和问题

  • 上面的数据校验已经进行分装,实现了和接口业务低耦合要求,并且可以自定义结果;但有一个问题:对于每一个POJO要校验的参数,都要重复指定message值,
  • 示例:@NotEmpty(message = "手机号不能为空") ;如果多个POJO对手机号验证,又会出现冗余情况

5-2-优化思路

  • 自定义校验注解,对相同或类似的参数使用相同的自定义注解(见源码-01)
  • 自定义注解需要,自定义一个注解类(见源码-03)和一个ConstraintValidator实现类(见源码-03)

5-3-源码分析

//1-POJO
package com.hs.api.shopapp.entity.commom; import com.hs.web.common.annotation.validation.MobileFormat; public class AuthCodeReq { @MobileFormat//使用自定义校验注解
private String mobile;//手机号 public String getMobile() {
return mobile;
} public void setMobile(String mobile) {
this.mobile = mobile;
}
} //2-自定义注解MobileFormat
@Documented
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MobileConstraintValidator.class)
public @interface MobileFormat {
String message() default "手机号格式不正确"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
} //3-重写ConstraintValidator接口,自定义校验规则
public class MobileConstraintValidator implements ConstraintValidator<MobileFormat,String>{ @Override
public void initialize(MobileFormat constraintAnnotation) {
} //在当前方法指定校验规则
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(StringUtil.isBlank(value)){
return false;
}
return true;
}
} //4-测试接口
@RestController
@RequestMapping(value="/api/shop/login")
public class ApiShopLoginController extends ApiShopBaseController { @MyValidateAopAnnotation
@RequestMapping(value = { "/get/authcode" }, method = {RequestMethod.POST })
public Object getSignInAuthCode(@Valid @RequestBody AuthCodeReq authCodeReq,BindingResult bindingResult) throws Exception {
//业务逻辑略
} }

5-4-应用示例

6-备注

6-1-@NotEmpty、@NotNull、@NotBlank 的区别

  • @NotEmpty 用在集合上面(不能注释枚举)
  • @NotBlank用在String上面
  • @NotNull用在所有类型上面

参考文献

1-https://blog.csdn.net/ranshenyi/article/details/79548188
2-https://www.cnblogs.com/NeverCtrl-C/p/8185576.html

Springboot学习06-Spring AOP封装接口自定义校验的更多相关文章

  1. SpringBoot应用中使用AOP记录接口访问日志

    SpringBoot应用中使用AOP记录接口访问日志 本文主要讲述AOP在mall项目中的应用,通过在controller层建一个切面来实现接口访问的统一日志记录. AOP AOP为Aspect Or ...

  2. CgLib动态代理学习【Spring AOP基础之一】

    如果不了解JDK中proxy动态代理机制的可以先查看上篇文章的内容:Java动态代理学习[Spring AOP基础之一] 由于Java动态代理Proxy.newProxyInstance()的时候会发 ...

  3. 事务框架之声明事务(自动开启,自动提交,自动回滚)Spring AOP 封装

    利用Spring AOP 封装事务类,自己的在方法前begin 事务,完成后提交事务,有异常回滚事务 比起之前的编程式事务,AOP将事务的开启与提交写在了环绕通知里面,回滚写在异常通知里面,找到指定的 ...

  4. springboot 2.x整合redis,spring aop实现接口缓存

    pox.xml: <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  5. 使用AOP+自定义注解完成spring boot的接口权限校验

    记使用AOP+自定义注解完成接口的权限校验,代码如下: pom文件添加所需依赖: 1 <dependency> 2 <groupId>org.aspectj</group ...

  6. spring深入学习(四)-----spring aop

    AOP概述 aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些 ...

  7. JavaEE学习之Spring AOP

    一.基本概念 AOP——Aspect-Oriented Programming,面向切面编程,它是spring框架的一个重要组成部分.一般的业务逻辑都有先后关系,我们可以理解为纵向关系,而AOP关注的 ...

  8. 5.3 Spring5源码--Spring AOP使用接口方式实现

    Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解. 本文重点介绍Spring使用接口方式实现AOP. 使用接口方式实现AOP以了解为目的. 更好地理解动态 ...

  9. Java动态代理学习【Spring AOP基础之一】

    Spring AOP使用的其中一个底层技术就是Java的动态代理技术.Java的动态代理技术主要围绕两个类进行的 java.lang.reflect.InvocationHandler java.la ...

随机推荐

  1. Devexpress Winform MVVM

    归纳总结备忘 Devexpress Winform MVVM Practice 前言 MVVM Devexpress 正文 databindings及 UI Triggers Command 委托Co ...

  2. iuplua test on luaforwindows

    SW https://github.com/rjpcomputing/luaforwindows/releases Steps Install lua for windows write a bat ...

  3. Python 解决: from pip import main ImportError: cannot import name 'main'

    此次报错是因为 pip 升级出的问题: from pip import mainif __name__ == '__main__': sys.exit(main()) 改为: from pip imp ...

  4. Jenkins 配置 FindBugs,Checkstyle,PMD 实现代码的静态检查 (14)

    一.插件介绍 FindBugs:静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题.利用这个工具,就可以在不实际运行程序的情况对软件进行分析.它可以帮助改进代码的 ...

  5. java fail-fast和fail-safe

    快速失败(fail—fast) 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(如增加.删除等),则会抛出Concurrent Modification Exception. ...

  6. Python基础------运算符

    运算符类型 算数运算符 +   加               -    减              *    乘              /    除              %取余     ...

  7. NodeJS对象数组Array 根据对象object key的值排序sort

    有个js对象数组 var ary=[{id:1,name:”b”},{id:2,name:”b”}] 需求是根据name 或者 id的值来排序,这里有个风骚的函数. /** * 对数组中的对象,按对象 ...

  8. java GC是在什么时候,对什么东西,做了什么事情

    面试题:“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?” 面试题目:地球人都知道,Java有个东西叫垃圾收集器,它让创建的对象不需要像c/cpp那样delete.free掉,你 ...

  9. Oracle创建pfile spfile 文件及其恢复

    1.Oralce在启动实例的时读取$ORACLE_HOME/dbs下面的初始化文件.初始化文件分为:A.静态参数文件PFILE,一般名为initSID.oraB.永久参数文件SPFILE,一般名为sp ...

  10. ABAP-FTP-配置

    1.FTP配置: 设置FTP参数:IP地址.账号.密码.路径.RFC目标. 设置数据表:数据表及字段明细,设置查询字段及报表输出字段. 2.操作界面 3.程序 ZFID0003_ETL_FTP 主程序 ...