SpringBoot四:通過自定義註解實現參數校驗及AOP

在項目中,我們總會或多或少接觸過一些註解,如常見的@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()的結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章