Springboot 自定义注解+AOP实现参数不能为空

自定义注解类

package com.wing.my.cloud.system.modular.system.util.annotation;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckNullParams {
    String[] params();
}

AOP

package com.wing.my.cloud.system.modular.system.util.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.wing.my.cloud.kernel.model.exception.ServiceException;
import com.wing.my.cloud.system.modular.system.util.annotation.CheckNullParams;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 校验参数不能为空
 * ProceedingJoinPoint只能用在环绕通知上。
 */
@Slf4j
@Aspect
@Component
public class CheckNullParamsAspect {

    private static String dateFormat = "yyyy-MM-dd HH:mm:ss";

    @Pointcut("@annotation(com.wing.my.cloud.system.modular.system.util.annotation.CheckNullParams)")
    public void paramNotNull(){}

    @Around("paramNotNull()")
    public Object  doAround(ProceedingJoinPoint joinPoint) throws Throwable{
        log.info("进入到环绕通知中");
        // 1、记录方法开始执行的时间
        long start = System.currentTimeMillis();

        // 2、打印请求参数
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String target = joinPoint.getSignature().getDeclaringTypeName();              // 全路径类名
        String classNm = target.substring(target.lastIndexOf(".") + 1, target.length()); // 类名截取
        String method = joinPoint.getSignature().getName();                          // 获取方法名
        Map<String, String> params = getAllRequestParam(request);                    // 获取请求参数
        log.info("{}.{} 接收参数: {}", classNm, method, JSON.toJSONString(params));

        CheckNullParams check = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(CheckNullParams.class); // 获取注解
        String[] requiredFields = check.params();                                   // 获取注解参数
        // 3、必填参数非空校验
        Map<String,Object> result = validParams(params, requiredFields);
        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捕获全局异常,返回调用方“参数缺失”
            throw new ServiceException(500, "参数【" + result.get("param") + "】缺失或格式错误");

        }

    }

    /**
     * 校验传入参数params(非null)中是否必含requiredFields(非null)中的各个属性,且属性值非空
     *
     * @param params         传入参数
     * @param requiredFields 设置的非空属性数组
     * @return 校验通过返回true,否则返回false
     */
    private Map<String,Object> validParams(Map<String, String> params, String[] requiredFields) {
        Map<String, Object> map = new HashMap<>(2);
        if (requiredFields.length == 0) {
            // 无必送参数,直接返回true
            map.put("boolean", true);
            return map;
        } else {
            for (String field : requiredFields) {
                if (StringUtils.isEmpty(params.get(field))) {
                    map.put("boolean", false);
                    map.put("param", field);
                    return map;
                }
            }
            map.put("boolean", true);
            return map;
        }
    }

    /**
     * 获取请求参数
     */
    public static Map<String, String> getAllRequestParam(HttpServletRequest request) {
        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 (StringUtils.isEmpty(res.get(en))) {
                    res.remove(en);
                }
            }
        }
        return res;
    }

}

扩展

上面是AOP实现的业务逻辑,有的时候需要通过反射去处理 ,比如通过方法来获取方法上的注解信息以及注解里面的值等

java 获取类,属性变量,方法,方法参数上注解的值等

获取类上注解的值

定义注解@Target(ElementType.TYPE)用于类,接口等

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Orange {

    String getName();

    String getValue();
}

获取

@Orange(getName = "3333",getValue = "4444")
public class ParameterNameTest {

   。。。

    @Test
    public void main() throws Exception {

        Class<ParameterNameTest> clazz = ParameterNameTest.class;

        if(clazz.isAnnotationPresent(Orange.class)){
            // 获取 "类" 上的注解
            Orange getAnnotation = clazz.getAnnotation(Orange.class);
            System.out.println("\"类\"上的注解值获取到第一个 :"
                    + getAnnotation.getName()+ ",第二个:"+ getAnnotation.getValue());
        }
}

返回

"类"上的注解值获取到第一个 :3333,第二个:4444

获取属性变量上注解的值

定义注解@Target(ElementType.FIELD)用于属性变量

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Banana {

    String length();
    String price();
}

获取

public class ParameterNameTest {

    @Banana(length = "6666", price = "$888")
    String something = "other information";

    @Test
    public void main() throws Exception {

        Class<ParameterNameTest> clazz = ParameterNameTest.class;
         // 获取 "属性变量" 上的注解的值
        Field[] fields = clazz.getDeclaredFields();
        for(Field field: fields){
            if(field.isAnnotationPresent(Banana.class)){
                Banana bananaAnnotation = field.getAnnotation(Banana.class);
                System.out.println("\"属性变量\"上的注解值获取到第一个 :"
                        + bananaAnnotation.length()+ ",第二个:"+ bananaAnnotation.price());
            }
        }
    }
}

返回

"属性变量"上的注解值获取到第一个 :6666,第二个:$888

获取方法上注解的值

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Apple {

    String color();

    String number();
}

获取

public class ParameterNameTest {

    @Apple(color = "红色", number = "5555")
    public void method1(){
        // ...
    }

    @Test
    public void main() throws Exception {

        Class<ParameterNameTest> clazz = ParameterNameTest.class;

        // 获取 "方法"上的注解的值
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method: methods){
            if(method.isAnnotationPresent(Apple.class)){

                Apple appleAnnotation = method.getAnnotation(Apple.class);
                System.out.println("\"方法\"上的注解值获取到第一个 :"
                        + appleAnnotation.color()+ ",第二个:"+ appleAnnotation.number());
            }
        }
    }
}

返回

"方法"上的注解值获取到第一个 :红色,第二个:5555

获取" 方法参数 " 上注解的值

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cherry {

    String value();
}

获取

public class ParameterNameTest {

   public void method2(@Cherry("1111") String param1, @Cherry("2222") String param2) {
        System.out.println(param1 + param2);
    }

    @Test
    public void main() throws Exception {

        Class<ParameterNameTest> clazz = ParameterNameTest.class;
        // 获取 "方法参数" 上的注解的值
        Method method = clazz.getDeclaredMethod("method2", String.class, String.class);
        String[] parameterNames = getMethodParameterNamesByAnnotation(method);
        System.out.println("\"方法参数\"上的注解值获取到"+Arrays.toString(parameterNames));
    }

    /**
     * 获取给 "方法参数" 进行注解的值
     *
     * @param method 要获取参数名的方法
     * @return 按参数顺序排列的参数名列表
     */
    public static String[] getMethodParameterNamesByAnnotation(Method method) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) {
            return null;
        }
        String[] parameterNames = new String[parameterAnnotations.length];
        int i = 0;
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            for (Annotation annotation : parameterAnnotation) {
                if (annotation instanceof Cherry) {
                    Cherry param = (Cherry) annotation;
                    parameterNames[i++] = param.value();
                }
            }
        }
        return parameterNames;
    }
}

返回

"方法参数"上的注解值获取到[1111, 2222]

小结:主要使用的API是Class类中的实现接口AnnotatedElement的方法

isAnnotationPresent — 检测该元素是否被对应注解修饰

default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }
getAnnotation --- 获取注解对象
<T extends Annotation> T getAnnotation(Class<T> annotationClass);

Aop JoinPoint方法详解

重要API

        /*获取参数的值数组*/
        Object[] args = point.getArgs();                                    //  [1] 参数的值
        
        /*获取目标对象(被加强的对象)*/
        Object target = point.getTarget();
        
        /*获取signature 该注解作用在方法上,强转为 MethodSignature*/
        MethodSignature signature = (MethodSignature) point.getSignature();
        
        /*方法名*/
        String signatureName = signature.getName();                         //  findById
        
        /*参数名称数组(与args参数值意义对应)*/
        String[] parameterNames = signature.getParameterNames();            //  [i] 参数名称
        
        /*获取执行的方法对应Method对象*/
        Method method = signature.getMethod();                              //  public void com.draymond.aop2.service.UserService.findById(int)
        
        /*获取返回值类型*/
        Class returnType = signature.getReturnType();                       //  void
        
        /*获取方法上的注解*/
        WebAnnotation webAnnotation = method.getDeclaredAnnotation(WebAnnotation.class);
        
      // 获取request/response(ThreadLocal模式)
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        HttpServletResponse response = servletRequestAttributes.getResponse();

		@Before("customerJoinPointerExpression()")
		public void beforeMethod(JoinPoint joinPoint){
			joinPoint.getSignature().getName(); // 获取目标方法名
			joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
			joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
			joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
			Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
			joinPoint.getTarget(); // 获取被代理的对象
			joinPoint.getThis(); // 获取代理对象自己

		// 获取目标方法上的注解
		private <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> clazz) {
		    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		    Method method = methodSignature.getMethod();
		    return method.getAnnotation(clazz);
}
}
}

实现

@ApiImplicitParams({
            @ApiImplicitParam(name = "channelParam", value = "闲钱保渠道维护表实体类", dataType = "ChannelParam", paramType = "query", example = "xxx"),
    })
    @ResponseBody
    @ApiResource(name = "测试接口", path = "/test1")
    @CheckNullParams(params = {"custId","inpName"})  //参数不能为空
    public ResponseData test1( InsurancePolicyParam insurancePolicyParam) {
        testUserService.testExceptionAop(insurancePolicyParam);
        return ResponseData.success();
    }

参数 custId,inpName为InsurancePolicyParam实体类的属性。

AOP各种通知

前置通知

方法执行前开始执行

@Before("declareJointPointExpression()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        System.out.println("这是切面开始打印出来的--->The method " + methodName + " begins with " + Arrays.asList(args));
    }
//后置通知
//方法执行后开始执行
@After("declareJointPointExpression()")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("这是切面结束打印出来的--->The method " + methodName + " ends");
}

带返回值的后置通知

方法正常执行后执行

1 @AfterReturning(value = "declareJointPointExpression()",
2             returning = "result")
3     public void afterReturningMethod(JoinPoint joinPoint,Object result) {
4         String methodName = joinPoint.getSignature().getName();
5         System.out.println("The method " + methodName + " ends with " + result);
6     }

异常通知

代码执行中出现异常后执行

@AfterThrowing(value = "exceptionLog()",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());
    }

先执行前置通知,然后代码,然后异常通知,然后后置通知。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,关于SpringBoot自定义注解AOP的问题,我可以为您提供一些基本的介绍和示例代码。 首先,AOP(Aspect-Oriented Programming)是一种编程范式,它可以在不修改业务逻辑代码的情况下,对应用程序进行横切关注点的切面处理。而Spring AOP作为Spring框架的一部分,提供了一种基于代理模式的AOP实现。 在使用Spring AOP的过程中,自定义注解可以作为切点表达式的一部分,通过对注解的解析,实现对被注解的方法或类的切面处理。下面是一个简单的示例代码,演示如何通过自定义注解实现对方法的AOP处理: 首先,定义一个自定义注解: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAnnotation { String value() default ""; } ``` 然后,在需要被拦截的方法上添加该注解: ```java @Service public class MyService { @MyAnnotation("myAnnotation") public void doSomething() { System.out.println("do something..."); } } ``` 接下来,使用AspectJ的@Aspect注解定义一个切面类,并在该类中定义一个切点,用于匹配被@MyAnnotation注解的方法: ```java @Aspect @Component public class MyAspect { @Pointcut("@annotation(com.example.demo.annotation.MyAnnotation)") public void myAnnotationPointcut() {} @Before("myAnnotationPointcut()") public void beforeMyAnnotation() { System.out.println("before myAnnotation..."); } } ``` 最后,启动SpringBoot应用程序,调用MyService的doSomething方法,就可以看到输出结果: ```java before myAnnotation... do something... ``` 以上就是一个简单的SpringBoot自定义注解AOP的示例。通过使用自定义注解,可以更加方便地实现对应用程序的切面处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值