SpringAOP使用示例, 自定義Java註解

註解和 class, interface 一樣屬於一種類型, 在 Spring 中, 註解就被經常使用, 最典型的用法就是註解來注入屬性值, 日常開發中我也經常會用到反射和註解配合使用的的方式動態的處理一些代碼, 來完成某些業務代碼的解耦

註解的作用

  • 編寫文檔, 如 JDK 中用於幫助生成文檔的註解 @param, @return
  • 編譯檢測, 讓編譯器能實現基本的編譯檢查, 如 @Override 註解在重寫父類父類方法時幫助檢查方法的正確性, 如果不是重寫的父類的方法, 則編譯器會報錯
  • 動態處理代碼, 如 Spring 使用反射 + 註解配合使用完成依賴注入

註解的定義

class 定義一個類, interface 定義一個接口, @interface 定義一個註解, 示例如下:

public interface AopAnnotation {
}

註解的分類

  • 元註解 (註解的註解), @Retention, @Target, @Inherited, @Documented, @Repeatable 五種
  • 標準註解, JDK 中使用的註解, 如典型的 @Override, @Deprecated, @SuppressWarnings, @FunctionalInterface (JDK 1.8 新增, 標識接口爲函數式接口) 等
  • 自定義註解

註解的註解

註解的註解, 即元註解也是一種特殊的註解, 用於描述一個普通註解的基礎信息, 可以理解爲註解的註解, JDK 中一共提供了五種元註解

@Retention

@Retention 註解描述了一個註解的存活時間, 有以下值, 只能選一個

  • RetentionPolicy.SOURCE 註解只在源碼階段保留, 在編譯器進行編譯時它將被丟棄忽視
  • RetentionPolicy.CLASS 註解只被保留到編譯進行的時候, 並不會被加載到 JVM 中
  • RetentionPolicy.RUNTIME 註解可以保留到程序運行的時候, 會被加載進入到 JVM 中, 在程序運行時可以獲取到它們

@Target

@Target 註解描述了這個註解可以被用到的地方, 如註解可以被用於包上, 類上或者成員屬性或成員方法上, 有以下值, 可以配置多個值

  • ElementType.ANNOTATION_TYPE 可以給一個註解進行註解
  • ElementType.CONSTRUCTOR 可以給構造方法進行註解
  • ElementType.FIELD 可以給屬性進行註解
  • ElementType.LOCAL_VARIABLE 可以給局部變量進行註解
  • ElementType.METHOD 可以給方法進行註解
  • ElementType.PACKAGE 可以給一個包進行註解
  • ElementType.PARAMETER 可以給一個方法內的參數進行註解
  • ElementType.TYPE 可以給一個類型進行註解, 比如類, 接口, 枚舉

@Inherited

註解的繼承, 一個類註解了使用了@Inherited 註解的註解, 如果這個類被繼承, 如果子類沒有使用其他任何註解, 就會繼承父類的註解

@Documented

用於文檔的生成, 能夠將註解中的元素包含到 Javadoc 中去

@Repeatable

JDK 1.8 新增的註解, 允許我們使用重複的註解, 使用多個值, 例如定義如下一個註解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface AopAnnotation {
    String value() default "";
}

如果我們像如下使用, 編譯器則會直接報錯

@AopAnnotation(value = "A")
@AopAnnotation(value = "B")
public void test(String name,String userId) {
    log.info("[aopTest] name {}, userId = {}",name,userId);
}

創建一個新的 AopAnnotations 註解, 到 AopAnnotation 註解上添加 @Repeatable(AopAnnotations.class) 註解後就可以重複的添加註解了

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface AopAnnotations {
    AopAnnotation[] value();
}

註解的屬性

註解的屬性, 使用示例如下, 這樣就定義了一個 value 屬性, 在使用的時候可以這樣爲屬性賦值 @AopAnnotation(value = “A”), 接着如果我們獲取到了註解實例, 那我們就可以通過 aopAnnotation.value() 的方式獲取到值 “A”

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface AopAnnotation {
    String value() default "";
}

代碼中提起註解信息

當開發者使用了註解後, 這些註解不會自己生效, 必須由開發者提供相應的代碼提取並處理 Annotation 信息, 這些提取和處理註解的代碼統稱爲 APT (Annotation Processing Toll), 獲取 Annotation 信息主要是 java.lang,annotation.Annotation 接口和 java.lang.reflect.AnnotatedElement 兩個接口, Class 類實現 AnnotatedElement 接口, 通過反射獲取到 Class 類的實例後就可以通過相關方法獲取註解信息了, 源碼如下:

Annotation接口, 其中最主要的方法是annotationType方法, 用於返回該註解的java.lang.Calss, Annotation 是所有註解的父類

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

AnnotatedElemetnt 接口, 反射中的 Method 實現了這個藉口, 在通過反射獲取到 Method 後就可以調用這裏的方法來獲取註解信息了

public interface AnnotatedElement {
    // 判斷程序元素上是否有指定的類型的註解
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    // 獲取程序元素上指定類型的註解, 如果不存在返回null
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    // 獲取程序元素上所有的註解, 如果不存在返回null
    Annotation[] getAnnotations();

    // 獲取指定類型的註解
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
         T[] result = getDeclaredAnnotationsByType(annotationClass);
         if (result.length == 0 && 
             this instanceof Class && 
             AnnotationType.getInstance(annotationClass).isInherited()) { 
             Class<?> superClass = ((Class<?>) this).getSuperclass();
             if (superClass != null) {
                 result = superClass.getAnnotationsByType(annotationClass);
             }
         }
         return result;
    }

    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
         Objects.requireNonNull(annotationClass);
         for (Annotation annotation : getDeclaredAnnotations()) {
             if (annotationClass.equals(annotation.annotationType())) {
                 return annotationClass.cast(annotation);
             }
         }
         return null;
    }

    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return AnnotationSupport.getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).collect(Collectors.toMap(Annotation::annotationType, Function.identity(), ((first,second) -> first), LinkedHashMap::new)),
        annotationClass);
    }

    Annotation[] getDeclaredAnnotations();
}

實例, 通過反射判斷一個類中的方法是否使用了註解, 如果有使用註解, 打印出註解的元數據:

public class AnnotationTest {

    @Test(name = "xiaoming")
    public void testFunction1() {

    }

    public void testFunction2() {

    }

    public static void main(String[] args) {
        Class annotationTestClass;
        try {
            annotationTestClass = Class.forName("com.example.demo.AnnotationTest");
            for (Method method : annotationTestClass.getMethods()) {
                //判斷指定類型的註解是否存在於此元素上,存在返回true,沒有返回false
                if (method.isAnnotationPresent(Test.class)) {
                    System.out.println("被@Test註解修飾的方法: " + method.getName());
                    System.out.println("@Test元數據name的值: " + method.getAnnotation(Test.class).name());
                    System.out.println("@Test元數據age的值: " + method.getAnnotation(Test.class).age());
                } else {
                    System.out.println("未被@Test註解修飾的方法: " + method.getName());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

輸出結果

被@Test註解修飾的方法: testFunction1
@Test元數據name的值: xiaoming
@Test元數據age的值: 18
未被@Test註解修飾的方法: testFunction2

在Spring Aop中使用示例

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在類上面添加 @Aspect 註解:

@Aspect
@Slf4j
@Component
public class LogAop {

    @Pointcut(value = "@annotation(com.example.demo.aop.AopAnnotations)")
    public void aopAnnotation() {

    }

    //前置通知, 在方法執行之前執行
    @Before(value = "aopAnnotation()")
    public void before(JoinPoint point) {
        // 使用 ProceedingJoinPoint 時 IDEA 提示只能在 @Around 註解的方法中被接收
        // 簡單參數值
        Object[] args = point.getArgs();
        Arrays.stream(args).forEach(arg -> log.info("[LogAop] arg = {}", arg));

        //代理類全類名, 獲取到全類名, 就能獲取Class的實例對象,也能使用反射了
        String className = point.getThis().getClass().getName();
        log.info("[LogAop] classType = {}", className);

        //當前被調用方法的方法信息,裏面有參數名, 參數類型
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        String[] params = methodSignature.getParameterNames();
        log.info("[LogAop] methodName = {}", methodSignature.getMethod().getName());
        log.info("[LogAop] param0 = {}, param1 = {}", params[0], params[1]);
    }

    //@After: 後置通知, 在方法執行之後執行 。
    @After(value = "aopAnnotation()")
    public void after(JoinPoint point) {
        log.info("[logAop] 1----------");
    }

    //@AfterReturning: 返回通知, 在方法返回結果之後執行
    @AfterReturning(returning = "o", pointcut = "aopAnnotation()")
    public Object afterReturning(JoinPoint point, Object o) {
        log.info("[LogAop] 2----------");
        return null;
    }

    //@Around: 後置通知, 在方法執行之後執行
    @Around(value = "aopAnnotation()")
    public Object around(ProceedingJoinPoint point) {
        //在Before之前, 在After之後執行
        //@Around("execution(* com.example.demo.controller.*(..))")
        Object[] args = point.getArgs();
        try {
            //獲取返回方法返回值, 這裏對args進行修改後再用新的值調用方法
            Object returnValue = point.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //對返回值進行修改後再返回新的值
        return null;
    }
}

定義的註解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AopAnnotations {
    AopAnnotation[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(AopAnnotations.class)
public @interface AopAnnotation {
    String value() default "";
}

在方法上使用添加 @AopAnnotations 註解, 就會調用定義的切點方法:

@Slf4j
@RestController
public class AopTestController {
    @PostMapping("/aop/test")
    @AopAnnotation(value = "A")
    @AopAnnotation(value = "B")
    public void test(String name, String userId) {
        log.info("[aopTest] name = {}, userId = {}", name, userId);
    }
}

測試結果

2019-11-20 18:57:37.867  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [LogAop] arg = 小明
2019-11-20 18:57:37.868  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [LogAop] arg = 1
2019-11-20 18:57:37.868  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [LogAop] classType = com.example.demo.controller.AopTestController$$EnhancerBySpringCGLIB$$fed7d001
2019-11-20 18:57:37.869  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [LogAop] methodName = test
2019-11-20 18:57:37.869  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [LogAop] param0 = name, param1 = userId
2019-11-20 18:57:37.878  INFO 91542 --- [nio-8080-exec-2] c.e.demo.controller.AopTestController    : [aopTest] name = 小明, userId = 1
2019-11-20 18:57:37.879  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [logAop] 1----------
2019-11-20 18:57:37.879  INFO 91542 --- [nio-8080-exec-2] com.example.demo.aop.LogAop              : [LogAop] 2----------

其它: Spring AOP @Before @Around @After 等 advice 的執行順序

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