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. python 的 format 函数

    python的格式化字符串方法之一------------format 函数 它通过{}和:来代替%. 数字 格式 输出 描述 3.1415926 {:.2f} 3.14 保留小数点后两位 3.141 ...

  2. 【SpringBoot】数据库操作之整合Mybaties和事务讲解

    ========================8.数据库操作之整合Mybaties和事务讲解 ================================ 1.SpringBoot2.x持久化数 ...

  3. 安装httpd服务

    1.yum安装httpd服务 2.启动httpd服务端口被占用 3.修改端口 4.启动httpd服务 5.输入网址是否正常能访问

  4. vim 简单实用

    http://www.runoob.com/linux/linux-vim.html 编辑模式 : (同时打开两个文件) vim test.c test1.c -O     同时编辑两个文件    - ...

  5. FluentData -Micro ORM with a fluent API that makes it simple to query a database 【MYSQL】

    官方地址:http://fluentdata.codeplex.com/documentation MYSQL: MySQL through the MySQL Connector .NET driv ...

  6. vue组件之间数据的传递

    父子组件通信 父组件向子组件传递数据: 1.通过子组件的 props 选项声明它期待获得的数据,用以接收父组件传过来的值. 2.在子组件标签中使用子组件props中创建的属性 3.父组件中注册子组件 ...

  7. python if not

    判断是否为None的情况 if not x if x is None if not x is None if x is not None`是最好的写法,清晰,不会出现错误,以后坚持使用这种写法. 使用 ...

  8. bzoj5049: 导航系统

    Description 小Q来到了一个随机的国度.这个国度由n座城市和m条双向道路构成.因为这个国度崇尚随机,因此m条边是用随机 选择两端点的方式生成的.充满好奇的小Q想在这里进行k次随机的旅行,每次 ...

  9. Spring复习

    第一天 IOC:控制反转,对象的创建权交给Spring DI:依赖注入,前提必须有IOC的环境,Spring管理这个类的时候将类的依赖的属性注入(设置)进来. 包括集合的注入 ClassPathXml ...

  10. python开发计算器

    1 业务需求 1.1 用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16- ...