SpringBoot自定义注解AOP实现参数非空判断
(可延伸为多种通过自定义注解的切面编程)

懒驴 2023年02月23日 1,664次浏览

一. 基础概念信息

1. 切面(Aspect)

面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,常见的有日志处理、事务处理、权限认证等等。

2. 切入点(PointCut)

要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。

3. 连接点(JoinPoint)

知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等。

4. 通知(Advice)

通知被织入方法,该如何被增强。定义切面的具体实现。@Pointcut规则中指明的方法即为切入点,@Before、@After是连接点,而下面的代码就是对应通知。

5. 目标对象(Target Object)

被一个或多个切面所通知的对象,即为目标对象。

6. AOP代理对象(AOP Proxy Object)

AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。

7. 织入(Weaving)

将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。


二. Java代码实现

1. Maven依赖

<!--AOP依赖-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 自定义注解类

(这里自定义的为参数非空判断)
ParamsCheckNull.java

import java.lang.annotation.*;

/***
 * 自定义注解类
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Documented
public @interface ParamsCheckNull {
    String[] params();		//参数属性名
    String[] msg();		//提示信息
}

3. 自定义AOP类

ParamsCheckNullAspect.java

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.web.system.config.spring.code.exception.BaseErrorCode;
import com.web.system.config.spring.code.exception.BaseErrorException;
import com.web.system.config.tools.StringUtil;
import com.web.system.config.tools.UuidGenerate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.*;

/***
 * 验证参数不能为空
 * 处理通知连接点[ProceedingJoinPoint]只能在环绕通知上
 */
@Slf4j
@Aspect
@Component
public class ParamsCheckNullAspect {
    private static String dateFormat = "yyyy-MM-dd HH:mm:ss";

    //切入点
    @Pointcut("@annotation(com.web.system.config.checkutil.checknull.ParamsCheckNull)")
    public void paramNotNull(){}

    /*@Pointcut("execution(public * com.modules..*.*Controller.*(..))")
    public void pointcut(){}*/

    @Around("paramNotNull()")
    public Object  doAround(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("进入到环绕通知中");
        // 1、记录方法开始执行的时间
        long start = System.currentTimeMillis();
        // 2、打印请求参数
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        //StringBuffer sb = new StringBuffer();
        //String requestId = UuidGenerate.getUUID();
        //sb.append("\n【请求 URL】:").append(request.getRequestURL());
        //sb.append("\n【请求类名】:").append(joinPoint.getSignature().getDeclaringTypeName());
        //sb.append("【请求方法名】:").append(joinPoint.getSignature().getName());
        //sb.append("\n【body】:").append(JSONUtil.toJsonStr(joinPoint.getArgs()));
        //sb.append("\n【请求参数】:").append(JSONUtil.toJsonStr(parameterMap));
        //log.info(sb.toString());
        //System.out.println("*****sb.toString():"+JSONUtil.toJsonStr(joinPoint.getArgs())+"*****");
        //System.out.println("request:"+request.getRequestURI());
        String target = joinPoint.getSignature().getDeclaringTypeName();              // 全路径类名
        String classNm = target.substring(target.lastIndexOf(".") + 1, target.length()); // 类名截取
        String method = joinPoint.getSignature().getName();                          // 获取方法名
        Map<String, Object> params = getRequestBody(joinPoint.getArgs());                   // 获取请求参数
        ParamsCheckNull check = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ParamsCheckNull.class); // 获取注解
        String[] requiredFields = check.params();       // 获取注解参数
        String[] msgList = check.msg();     //获取注解的提示语
        if(requiredFields.length!=msgList.length){
            throw new BaseErrorException(BaseErrorCode.DO_ERROR, "Code writing error");
        }
        // 3、必填参数非空校验
        Map<String,Object> result = validParams(params, requiredFields, msgList);
        if ((Boolean) result.get("boolean")) {
            Object object = joinPoint.proceed();        // 必填参数非空校验通过,执行方法,获取执行结果
            // 4、打印应答数据和方法耗时
            long time = System.currentTimeMillis() - start;
            log.info("{}.{} 应答数据: {}; 耗时 {} ms", classNm, method, JSONObject.toJSONStringWithDateFormat(object,
                    dateFormat, SerializerFeature.WriteMapNullValue), time);
            return object;
        } else {
            // 必填参数非空校验未通过,抛出异常,由GlobalExceptionHandler捕获全局异常,返回调用方“参数缺失”
            // result.get("param") 获取参数属性名
            // result.get("message").toString() 获取参数属性提示语
            throw new BaseErrorException(BaseErrorCode.PARAM_NOT_CORRECT, result.get("message").toString());
        }
    }

    /***
      * 校验传入参数params(非null)中是否必含requiredFields(非null)中的各个属性,且属性值非空
      * @param params         传入参数
      * @param requiredFields 设置的非空属性数组
      * @return 校验通过返回true,否则返回false
      */
    private Map<String,Object> validParams(Map<String, Object> params, String[] requiredFields, String[] msgList) {
        Map<String, Object> map = new HashMap<>();
        /*System.out.println("++++++++++++参数属性:"+JSON.toJSONString(requiredFields)+"+++++++++");
        System.out.println("++++++++++++属性提示:"+JSON.toJSONString(msgList)+"+++++++++");
        System.out.println("++++++++++++params:"+params+"+++++++");*/
        if (requiredFields.length == 0) {
            // 无必选参数,直接返回true
            map.put("boolean", true);
            return map;
        } else {
            for(int x=0; x<requiredFields.length; x++){
                Object oneObj=params.get(requiredFields[x]);
                if(oneObj instanceof String){
                    if(StringUtil.isEmpty(params.get(requiredFields[x]).toString())){
                        map.put("boolean", false);
                        map.put("param", requiredFields[x]);
                        map.put("message", msgList[x]);
                        return map;
                    }
                }else if(null==oneObj){
                    map.put("boolean", false);
                    map.put("param", requiredFields[x]);
                    map.put("message", msgList[x]);
                    return map;
                }
            }
            map.put("boolean", true);
            return map;
        }
    }

    /***
     * 获取 @RequestBody注解的参数信息
     */
    public static Map<String, Object> getRequestBody(Object[] obj){
        String sssss=JSONUtil.toJsonStr(obj);
        Map<String, Object> mmp= JSONObject.parseObject((sssss.substring(1,sssss.length()-1)));
        return mmp;
    }

    /**
       * 获取请求参数
       */
     public static Map<String, String> getAllRequestParam(HttpServletRequest request) {
         try {
             //从前端获取输入字节流
             ServletInputStream requestInputStream = request.getInputStream();
             //将字节流转换为字符流,并设置字符编码为utf-8
             InputStreamReader ir = new InputStreamReader(requestInputStream,"utf-8");
             //使用字符缓冲流进行读取
             BufferedReader br = new BufferedReader(ir);
             //开始拼装json字符串
             String line = null;
             StringBuilder sb = new StringBuilder();
             while((line = br.readLine())!=null) {
                 sb.append(line);
             }

             JSONObject json = JSONObject.parseObject(sb.toString());
         }catch (Exception e){
             System.out.println("eeeeeee:"+e);
         }
         Map<String, String> res = new HashMap<>();
         Enumeration<?> temp = request.getParameterNames();
         if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                // 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
                if (StringUtil.isEmpty(res.get(en))) {
                        res.remove(en);
                    }
            }
        }
        return res;
     }

     /********AOP的多种通知*********/
    /***
     * 1.方法执行前执行
     */
    @Before("paramNotNull()")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName=joinPoint.getSignature().getName();
        Object[] args=joinPoint.getArgs();
        System.out.println("切面开始----->>>>> The method "+methodName +" begin with :"+ Arrays.asList(args));
    }

    /***
     * 2.方法执行后通知
     */
    @After("paramNotNull()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("切面结束打--->The method: " + methodName + " 结束");
    }

    /***
     * 3.带返回值的通知
     */
    @AfterReturning(value = "paramNotNull()", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法: " + methodName + " 结束参数: " + result);
    }

    /***
     * 4.异常结果处理通知
     */
    @AfterThrowing(value = "paramNotNull()",throwing = "e")
    public void afterThrowing(JoinPoint point, Exception e) throws Throwable {
        String target = point.getSignature().getDeclaringTypeName();              // 全路径类名
        String classNm = target.substring(target.lastIndexOf(".") + 1, target.length()); // 类名截取
        String method = point.getSignature().getName();                          // 获取方法名
        log.error("{}.{} 【异常信息】: {}", classNm, method, e.getMessage());
    }

    /********AOP的多种通知*********/

}

4. 自定义注解实现

(这里的自定义注解在Controller的方法上使用)

@ResponseBody
@RequestMapping(value = "/get-monitorconfig-list", method = RequestMethod.POST)
@ParamsCheckNull(params = {"userStrId"}, msg = {"用户ID参数为空"})
public Map<String, Object> trainerGetMonitoringDeviceMethod(@RequestBody UserInfoPojo pram){
	/**[此处为自己业务的代码]**/
	return null;
}

上面的注解 @ParamsCheckNull 为自定义的注解,params 为@RequestBody参数UserInfoPojo类中需要判断处理的属性名,msg 为params对应下标的判断提示语,判断多个参数时:params = {"userStrId","userName"}, msg = {"用户ID参数为空","用户名不能为空"},如果params的元素个数不等于msg的元素个数,则会抛出自定义的错误:throw new BaseErrorException(BaseErrorCode.DO_ERROR, "Code writing error");