SpringBoot四:通过自定义注解实现参数校验及AOP

8 篇文章 0 订阅

在项目中,我们总会或多或少接触过一些注解,如常见的@Override,@Autowired。我们分别点进去看一下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

我们发现它们的类型都是@interface,而且有2个相同的注解@Target和@Retention,这两个注解是我们写新注解时必须要有的注解,它们都是元注解。

元注解

元注解的作用就是注解其他注解,一般我们使用自定义注解时,就需要用元注解来标注我们自己的注解。一共有四个元注解。

@Target

它指定了注解修饰的范围,在哪些作用域上起作用。我们可以看到它里面的参数都是ElementType枚举类。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    //用于描述类,接口(包括注解类型),或enum声明
    TYPE,

    /** Field declaration (includes enum constants) */
    //用于属性声明(包括enum常量)
    FIELD,

    /** Method declaration */
    //方法声明
    METHOD,

    /** Formal parameter declaration */
    //形式参数声明
    PARAMETER,

    /** Constructor declaration */
    //构造方法声明
    CONSTRUCTOR,

    /** Local variable declaration */
    //局部变量声明(方法内部变量)
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    //注解类型声明,可参见@Taeget注解
    ANNOTATION_TYPE,

    /** Package declaration */
    //包的声明
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Retention

定义了注解的声明周期

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     * 在源文件中保留(注解被编译器忽略)
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * 在class文件中保刘(注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     * 运行时保留(注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
     */
    RUNTIME
}

@Documented

被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

@Inherited

该注解表示子类可以集成加载父类上的注解。

1、自定义注解实现参数校验

我们先新建一个自定义注解的类,然后在继续说明一些注解的知识。
 

@Target(FIELD)
@Retention(RUNTIME)
@Documented
//@Constraint(validatedBy = AllowValueValidator.class)
public @interface AllowValue {

    String message() default "无效值";

    String[] allowValues() default {};

    /**
     *
     * @return
     */
    Class<?>[] groups() default {};

    /**
     *
     * @return
     */
    Class<? extends Payload>[] payload() default {};
}

我们先看前两行,message()说明了错误信息,allowValues()则是我们需要校验的参数,他支持的数据类型有

1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组

 default后面则是指定了默认的参数。

下面两个方法是我们需要对注解进行校验时用到的默认方法,必须存在。

下面我们来编写一个验证器,需要实现ConstraintValidator接口,然后@AllowValue注解需要加上@Constraint(validatedBy = AllowValueValidator.class)。这样我们使用自定义注解时,就会根据isValid方法去校验参数。

public class AllowValueValidator implements ConstraintValidator<AllowValue, Object> {

    String[] allowValues;

    @Override
    public void initialize(AllowValue constraintAnnotation) {
        allowValues = constraintAnnotation.allowValues();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        if (allowValues == null || allowValues.length == 0) {
            throw new IllegalArgumentException("allowValues can not be null or empty");
        }
        String valueStr;
        if (value instanceof Number) {
            valueStr = value.toString();
        } else if (value instanceof String) {
            valueStr = (String) value;
        } else {
            throw new IllegalArgumentException("must be Number or String");
        }
        if(StringUtils.isBlank(valueStr)) {
            return true;
        }

        boolean check = false;
        for (String str : allowValues) {
            if (StringUtils.isBlank(str)) {
                throw new IllegalArgumentException("allowValues can not be null or empty");
            }
            if (valueStr.equals(str)) {
                check = true;
                break;
            }
        }
        return check;
    }
}

2、自定义注解实现AOP

自定义注解:

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

    String action() default "";
}

切面类:

@Aspect
@Component
public class RequestLogAspect {


    @Pointcut("execution(* com.demo.controller.*.*(..))")
    public void controllerAspect(){}

    @Before("controllerAspect()")
    public void before(){
        System.out.println("@Before 打印方法信息前执行");
    }

    @Around("controllerAspect() && @annotation(requestLog)")
    public Object doing(ProceedingJoinPoint joinPoint, RequestLog requestLog){
        printMsg(joinPoint, requestLog);
        try {
            Object result =  joinPoint.proceed();
            System.out.println("@Around环绕结束");
            return result;
        } catch (Throwable throwable) {
            throw new BaseException(throwable.getMessage());
        }
    }

    @After("controllerAspect()")
    public void after(){
        System.out.println("@After 打印方法信息后执行");
    }

    private void printMsg(ProceedingJoinPoint joinPoint, RequestLog requestLog){
        String action = requestLog.action();
        System.out.println(action);
        Class target = joinPoint.getTarget().getClass();
        String className = target.getName();
        System.out.println(className);
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0){
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                System.out.println(arg);
            }
        }
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
    }
}

@Aspect注解表示这是一个切面类,用于切面的类必须有。

@Pointcut是切点,表示需要用到注解的方法。

execution()是方法描述匹配,格式为:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?) 
modifiers-pattern:修饰符模式,如public等,可省略。
ret-type-pattern:返回类型,用*表示任何返回值。
declaring-type-pattern:类型声明,可省略
name-pattern:指定方法名, *代表所有 (上述第一个*表示该包下的所有类,第二个*表示类的方法)。
param-pattern:指定方法参数,(..)代表所有参数,(*)代表一个参数。
throws-pattern:异常声明,可省略。
注意:每个pattern之间都有一个空格,尤其ret-type-pattern用*表达时后面不要忘记

例子中的execution()的表达式格式为execution(ret-type-pattern name-pattern(param-pattern)),含义是controller包下所有类的所有方法。更多可以点击Spring AOP 中pointcut expression表达式解析及配置了解

还可以使用@annotation,表示有这个注解的方法进入这个切面。例

@Pointcut("@annotation(com.demo.validate.annotation.RequestLog)")

@Around可以让我们在拦截方法前后做一些操作。格式里必须有切点的方法,如果我们要用到注解里的参数,则必须加上@annotation(),里面的名字必须和方法中的参数名一致。用joinPoint.proceed()来执行被拦截的方法,如果原来的方法有返回值,则我们必须return joinPoint.proceed()的结果。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值