天天看點

自定義注解——基于AOP實作注解實作類

先了解幾個元注解

分别是@Target, @Retention,@Documented,@Inherited

@Target:決定自定義的注解用在什麼地方

屬性值 作用
ElementType.CONSTRUCTOR 用在構造函數的聲明上
ElementType.FIELD 定義在成員變量上
LOCAL_VARIABLE 局部變量的聲明
METHOD 定義在方法上面
PACKAGE 包的聲明
PARAMETER 用在參數的聲明上
TYPE 類、接口、枚舉的聲明

比如我們常用的@Override的定義

@Target(ElementType.METHOD)  //聲明該注解用于方法上
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
           

值得一提,和@RequestMapping類似,@Target(ElementType.METHOD)等同于@Target(value=ElementType.METHOD)

@Retention

這個注解的保留級别

屬性值 作用
RetentionPolicy.SOURCE 注解将被編譯器丢棄
RetentionPolicy.CLASS 注解在class檔案中可用, 但會被VM丢棄
RetentionPolicy.RUNTIME VM将運作期也保留注解資訊,是以可用通過反射機制來讀取注解的資訊

我們之前使用注解做過一個AOP的參數合法性校驗,使用的自定義注解就是RUNTIME,因為要通過反射擷取參數值

而Class的取值可用,則有點類似于lombok的@Data注解,主要用來在class檔案中生成代碼

@Documented @Inherited

這兩個注解比較簡單,@Documented 的主要作用是用來生成注釋,表示需要在什麼級别儲存該注釋資訊,用于描述注解的生命周期(即:被描述的注解在什麼範圍内有效)

@Inhrited 是一個标記注解,@Inherited闡述了某個被标注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation将被用于該class的子類。

注解實作類

一般有兩種方法,一種是通過AOP,攔截所有擁有某注解的類/方法/枚舉…,另一種就是apt,反射使用太多比較消耗性能,但APT好像才安卓中用比較多,這裡隻是簡單描述。

AOP實作自定義注解處理器

aop切面中定義五種切面方法

前置通知:@Before 在目标業務方法執行之前執行

後置通知:@After 在目标業務方法執行之後執行

傳回通知:@AfterReturning 在目标業務方法傳回結果之後執行

異常通知:@AfterThrowing 在目标業務方法抛出異常之後

環繞通知:@Around 功能強大,可代替以上四種通知,還可以控制目标業務方法是否執行以及何時執行

寫一個擷取方法參數并将參數進行加一操作的小栗子。

定義注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ArgsFilter {
    String[] rules() default  {};
}
           

定義切面處理類,确定切點位置(下面例子中切點和具體執行方法分開處理)

@Aspect
@Component
public class ArgsFiletImpl  {
   //等價于 @Pointcut("[email protected](argsFilter)")
    //定義哪些方法會被切面攔截,這裡定義的是ArgsFilter注解使用的地方
    @Pointcut("@annotation(argsFilter)")
 public void checkAnnoation(ArgsFilter argsFilter){
    }
           

切點Pointcut中的參數

由下列方式來定義或者通過 &&、 ||、 !、 的方式進行組合:

execution:用于比對方法執行的連接配接點;

within:用于比對指定類型内的方法執行;

this:用于比對目前AOP代理對象類型的執行方法;注意是AOP代理對象的類型比對,這樣就可能包括引入接口也類型比對;

target:用于比對目前目标對象類型的執行方法;注意是目标對象的類型比對,這樣就不包括引入接口也類型比對;

args:用于比對目前執行的方法傳入的參數為指定類型的執行方法;

@within:用于比對是以持有指定注解類型内的方法;

@target:用于比對目前目标對象類型的執行方法,其中目标對象持有指定的注解;

@args:用于比對目前執行的方法傳入的參數持有指定注解的執行;

@annotation:用于比對目前執行方法持有指定注解的方法

具體處理方法

@Around(value = "auditLogPointcut(auditLog)")
//ProceedingJoinPoint僅用于around方法中,新增兩個方法 執行目标方法和新增參數執行目标方法
public Object around(ProceedingJoinPoint joinPoint, AuditLog auditLog) throws Throwable {
    //擷取請求對象
  HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    Map<String, String[]> parameterMap = request.getParameterMap();
    StringBuffer requestURL = request.getRequestURL();
    MethodSignature signature = (MethodSignature)joinPoint.getSignature(); // 代理對象
    Method targetMethod = signature.getMethod(); //擷取參數所在方法
    final Class declaringType = signature.getDeclaringType();//擷取聲明所在類
    String declaringTypeName = signature.getDeclaringTypeName();//類名
    Object object=joinPoint.proceed();//執行目标方法
    return object;
}
           

ProceedingJoinPoint對象

roceedingJoinPoint對象是JoinPoint的子接口,該對象隻用在@Around的切面方法中,

自定義注解——基于AOP實作注解實作類

apt介紹

apt(annotation processing tool) 注解處理工具,就是操作Java源檔案,當處理完源檔案後編譯它們,在系統建立的過程中會自動建立一些新的源檔案,這些新檔案會在新的一輪中的注解處理器中接受檢查,直到不再有新的源檔案産生為止。這個過程中是發生在編譯期間(compile time),而非運作期間,

我們可以使用ProcessingEnvironment擷取一些實用類以及擷取選項參數等:

public interface ProcessingEnvironment {
    Map<String, String> getOptions(); //傳回指定的參數選項
    Messager getMessager(); //傳回實作Messager接口的對象,用于報告錯誤資訊、警告提醒
    Filer getFiler(); //傳回實作Filer接口的對象,用于建立檔案、類和輔助檔案
    Elements getElementUtils(); //傳回實作Elements接口的對象,用于操作元素的工具類
    Types getTypeUtils(); //傳回實作Types接口的對象,用于操作類型的工具類
    SourceVersion getSourceVersion();
    Locale getLocale();
}
           

apt一般是需要繼承AbstractProcessor 類

public class ArgsFiletImpl extends AbstractProcessor {
    protected ArgsFiletImpl() {
        super();
    }
  //指定該處理器是工作于哪個注解的
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(ArgsFilter.class.getCanonicalName());
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {//指定目前jdk版本
        return  SourceVersion.latestSupported();
    }
   //初始化方法
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

   //核心方法
    //這些注解是否由此 Processor 處理,該方法傳回ture表示該注解已經被處理, 後續不會再有其他處理器處理; 傳回false表示仍可被其他處理器處理
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

}