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

关键字

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

前言

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

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

正文

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. //1-POJO
  2. import org.hibernate.validator.constraints.NotEmpty;
  3. import javax.validation.constraints.NotNull;
  4. public class ValidateAuthCodeReq {
  5. @NotEmpty(message = "手机号不能为空")//message将在接口返回数据中得以体现
  6. private String mobile;//手机号
  7. @NotEmpty(message = "验证码不能为空")
  8. private String code;//验证码
  9.  
  10. public String getMobile() {
  11. return mobile;
  12. }
  13.  
  14. public void setMobile(String mobile) {
  15. this.mobile = mobile;
  16. }
  17.  
  18. public String getCode() {
  19. return code;
  20. }
  21.  
  22. public void setCode(String code) {
  23. this.code = code;
  24. }
  25. }
  26.  
  27. //2-controller层接口方法
  28. @RestController
  29. @RequestMapping(value="/api/shop/login")
  30. public class ApiShopLoginController extends ApiShopBaseController {
  31.  
  32. //2-2-校验并绑定手机号
  33. @MyValidateAopAnnotation//自定义注解,用户AOP定位方法
  34. @RequestMapping(value = { "/validate/authcode" }, method = {RequestMethod.POST })
  35. public Object validateAndBind( @Valid @RequestBody ValidateAuthCodeReq validateAuthCodeReq,BindingResult bindingResult) throws Exception {
  36. //BindingResult封装了数据校验结果
  37. //业务逻辑略
  38. }
  39. }
  40.  
  41. //3-AOP方法,统一处理数据校验
  42. @Aspect
  43. @Component
  44. public class ExceptionAspect {
  45. //根据自定义注解MyValidateAopAnnotation定位方法
  46. @Pointcut("@annotation(com.hs.web.common.exception.MyValidateAopAnnotation)")
  47. public void bindPointCut() {
  48. }
  49.  
  50. @Before("bindPointCut()")
  51. public void before(JoinPoint joinPoint) throws BindException {
  52. // 接收到请求
  53. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  54. //获取请求的request
  55. HttpServletRequest request = attributes.getRequest();
  56. //获取BindingResult,并进行判断
  57. Object[] args = joinPoint.getArgs();
  58. BindingResult bindingResult = (BindingResult)args[args.length-1];
  59. if(bindingResult.hasErrors()){
  60. throw new BindException(bindingResult);
  61. }
  62. System.out.println("args[args.length-1]: "+ args[args.length-1]);
  63. System.out.println("bindingResult" + bindingResult);
  64. }
  65. }
  66.  
  67. //4-自定义注解MyValidateAopAnnotation-目的是为了AOP定位
  68. @Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
  69. @Retention(RUNTIME)
  70. @Documented
  71. public @interface MyValidateAopAnnotation {
  72.  
  73. }
  74.  
  75. //5-全局异常处理类
  76.  
  77. import com.hs.common.util.json.JsonUtil;
  78. @ControllerAdvice
  79. @ResponseBody
  80. public class GlobalExceptionHandler {
  81. //数据校验
  82. @ExceptionHandler(value=BindException.class)
  83. public String BindExceptionHandler(HttpServletRequest request,BindException exception) throws Exception{
  84. logger.warn(exception);
  85. //1-封装异常说明
  86. List<ObjectError> errorList = exception.getAllErrors();
  87. StringBuffer sb = new StringBuffer();
  88. for(ObjectError error : errorList){
  89. sb.append("参数" + exception.getFieldError().getField() + "异常:" + exception.getFieldError().getDefaultMessage() + ";");
  90. }
  91. //2-封装返回参数
  92. ExceptionResponseBean detailBean = new ExceptionResponseBean(
  93. GlobalExceptionEnum.ERROR_DATA_VALIDATION.getCode(),
  94. GlobalExceptionEnum.ERROR_DATA_VALIDATION.getMsg(),
  95. sb.toString());
  96. //3-以Json格式返回数据
  97. return JsonUtil.toJson(detailBean).toString();
  98. }
  99.  
  100. }
  101.  
  102. //6-异常情况枚举
  103.  
  104. import com.hs.common.util.json.JsonUtil;
  105. public enum GlobalExceptionEnum {
  106.  
  107. OTHER_EXCEPTION(800, "出现异常", "其它异常,待识别", Exception.class),
  108.  
  109. ERROR_DATA_VALIDATION(801, "数据校验异常", "请求参数数据校验异常", BindException.class),
  110.  
  111. ;
  112.  
  113. private int code;
  114. private String msg;
  115. private ExceptionResponseDetailBean data;
  116. private Class exception;
  117. private GlobalExceptionEnum(int code, String msg, String data, Class exception) {
  118. this.code = code;
  119. this.msg = msg;
  120. this.data = new ExceptionResponseDetailBean(data);
  121. this.exception = exception;
  122. }
  123.  
  124. public int getCode() {
  125. return code;
  126. }
  127.  
  128. public String getMsg() {
  129. return msg;
  130. }
  131.  
  132. public ExceptionResponseDetailBean getData() {
  133. return data;
  134. }
  135.  
  136. public Class getException() {
  137. return exception;
  138. }
  139.  
  140. }
  141.  
  142. //7-ExceptionResponseBean异常返回POJO
  143. public class ExceptionResponseBean {
  144.  
  145. private int code;
  146. private String msg;
  147. private ExceptionResponseDetailBean data;
  148.  
  149. public ExceptionResponseBean() {
  150. super();
  151. }
  152.  
  153. public ExceptionResponseBean(int code, String msg, String detail) {
  154. super();
  155. this.code = code;
  156. this.msg = msg;
  157. this.data = new ExceptionResponseDetailBean(detail);
  158. }
  159.  
  160. public int getCode() {
  161. return code;
  162. }
  163. public void setCode(int code) {
  164. this.code = code;
  165. }
  166. public String getMsg() {
  167. return msg;
  168. }
  169. public void setMsg(String msg) {
  170. this.msg = msg;
  171. }
  172. public ExceptionResponseDetailBean getData() {
  173. return data;
  174. }
  175. public void setData(ExceptionResponseDetailBean data) {
  176. this.data = data;
  177. }
  178.  
  179. }
  180.  
  181. //8-ExceptionResponseDetailBean异常返回明细POJO
  182.  
  183. public class ExceptionResponseDetailBean {
  184.  
  185. private String detail;
  186.  
  187. public ExceptionResponseDetailBean() {
  188. super();
  189. }
  190.  
  191. public ExceptionResponseDetailBean(String detail) {
  192. super();
  193. this.detail = detail;
  194. }
  195.  
  196. public String getDetail() {
  197. return detail;
  198. }
  199.  
  200. public void setDetail(String detail) {
  201. this.detail = detail;
  202. }
  203. }

4-应用示例

5-进一步优化校验

5-1-优点和问题

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

5-2-优化思路

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

5-3-源码分析

  1. //1-POJO
  2. package com.hs.api.shopapp.entity.commom;
  3.  
  4. import com.hs.web.common.annotation.validation.MobileFormat;
  5.  
  6. public class AuthCodeReq {
  7.  
  8. @MobileFormat//使用自定义校验注解
  9. private String mobile;//手机号
  10.  
  11. public String getMobile() {
  12. return mobile;
  13. }
  14.  
  15. public void setMobile(String mobile) {
  16. this.mobile = mobile;
  17. }
  18. }
  19.  
  20. //2-自定义注解MobileFormat
  21. @Documented
  22. @Target({ElementType.METHOD, ElementType.FIELD})
  23. @Retention(RetentionPolicy.RUNTIME)
  24. @Constraint(validatedBy = MobileConstraintValidator.class)
  25. public @interface MobileFormat {
  26. String message() default "手机号格式不正确";
  27.  
  28. Class<?>[] groups() default {};
  29.  
  30. Class<? extends Payload>[] payload() default {};
  31.  
  32. @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
  33. @Retention(RetentionPolicy.RUNTIME)
  34. @Documented
  35. public @interface List {
  36. NotBlank[] value();
  37. }
  38. }
  39.  
  40. //3-重写ConstraintValidator接口,自定义校验规则
  41. public class MobileConstraintValidator implements ConstraintValidator<MobileFormat,String>{
  42.  
  43. @Override
  44. public void initialize(MobileFormat constraintAnnotation) {
  45. }
  46.  
  47. //在当前方法指定校验规则
  48. @Override
  49. public boolean isValid(String value, ConstraintValidatorContext context) {
  50. if(StringUtil.isBlank(value)){
  51. return false;
  52. }
  53. return true;
  54. }
  55. }
  56.  
  57. //4-测试接口
  58. @RestController
  59. @RequestMapping(value="/api/shop/login")
  60. public class ApiShopLoginController extends ApiShopBaseController {
  61.  
  62. @MyValidateAopAnnotation
  63. @RequestMapping(value = { "/get/authcode" }, method = {RequestMethod.POST })
  64. public Object getSignInAuthCode(@Valid @RequestBody AuthCodeReq authCodeReq,BindingResult bindingResult) throws Exception {
  65. //业务逻辑略
  66. }
  67.  
  68. }

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. 2.python发展历程

    创始人:吉多·范罗苏姆于1989年圣诞节在阿姆斯特丹编写 python分为: python 2.X python 3.X 使用python的公司: 豆瓣.BT.Dropbox.YouTube.Quor ...

  2. react hooks 笔记

    1. 建议安装以上版本: "dependencies": { "react": "^16.7.0-alpha.2", "react ...

  3. 开发一个简单的postgresql extension

      主要是学习如何编写一个简单的pg extension,参考https://severalnines.com/blog/creating-new-modules-using-postgresql-c ...

  4. 创建一个dynamics 365 CRM online plugin (一) - Hello World Plugin

    源代码连接:https://github.com/TheMiao/Dynamics365CRM/blob/master/MyCRM/MyCRM/HelloWorld.cs 首先,我们需要创建一个.NE ...

  5. 为什么浏览器User-agent总是有Mozilla字样

    你是否好奇标识浏览器身份的User-Agent,为什么每个浏览器都有Mozilla字样?Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ( ...

  6. OpenStack上搭建Q版的公共环境准备(step1)

    vmware14 centos7.5minimal版 controller1节点虚拟硬件配置: CPU:1颗2核 Memory:2G 硬盘:20G 网卡: VMnet1(仅主机模式):关闭DHCP,手 ...

  7. C语言编程知识点

    (1)预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题):#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL 1) #defin ...

  8. 采用ddt 可以把ddt获取的数据 塞进测试用例里面的备注里面去展示 (还没有试)

  9. 源码:Java集合源码之:数组与链表(一)

    数组和链表是数据结构中最基本的部分. 数组 在java中,数组定义为一种基本类型,其可以通过下标获取到对应位置的数据.那么这种结构的数据,在内存中是怎么存放的呢? 数组在内存中是一段连续的存储单元,每 ...

  10. Linux find 命令参数大全及示例

    Linux中find常见用法示例 命令格式:find path -option [-print] [ -exec -ok command] {} \; 参数说明: path:find命令所查找的目录路 ...