二. 重識Java之夯實註解

不忘初心 砥礪前行, Tomorrow Is Another Day !java

相關文章

引言

本文概要:android

  1. 註解基礎知識
  2. 註解的定義及使用

一. 註解的基礎知識

註解分類:標準註解、元註解.安全

1.1 標準註解

  • @Override : 標記覆蓋超類的方法
  • @Deprecated : 標記棄用
  • @SuppressWarnings : 取消警告
  • @SafeVarargs : 聲明在使用可變長度參數的方法,與泛型類一塊兒使用不會出現類型安全問題

1.2 元註解

簡稱註解的註解,從而建立新的註解.bash

  • @Target : 註解所修飾的類型
  • @Inherited : 註解能夠被繼承
  • @Documented : 標記註解應該被javaDoc工具記錄
  • @Retention : 註解的保留策略

這裏主要介紹@Target和@Retention兩個元註解.jvm

@Target

修飾類型從枚舉類ElementType取值ide

public enum ElementType {
    /** 類,接口(包含註解類型)*/
    TYPE,

    /** 成員變量*/
    FIELD,

    /** 方法*/
    METHOD,

    /** 方法參數或構造方法參數 */
    PARAMETER,

    /** 構造方法  */
    CONSTRUCTOR,

    /** 局部變量 */
    LOCAL_VARIABLE,

    /** 註解類型,可參考元註解Retention聲明  */
    ANNOTATION_TYPE,

    /** 包  */
    PACKAGE,

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

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

複製代碼
@Retention

保留策略從枚舉類RetentionPolicy中取值工具

public enum RetentionPolicy {
    /**
     * 源碼級java文件
     */
    SOURCE,

    /**
     * 編譯時class文件  This is the default behavior.
     */
    CLASS,

    /**
     * 
     * 運行時,加載到jvm虛擬機中.
     * 經過反射獲取該註解信息
     */
    RUNTIME
}
複製代碼

二.註解的定義及使用

  • 定義註解 - 反射機制解析註解 - 使用註解
  • 定義註解 - AbstractProcessor(註解解析器)解析註解 - 使用註解

2.1 運行時註解示例

//定義註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//運行時註解
public @interface Name {
    //定義成員變量,能夠設置默認值
    String value() default "我是默認名";
}

//使用註解
public class UseAnnotation {

    @Name(value = "小學僧")
    public String getName() {
        return "什麼都沒有";
    }

    @Name
    public String getFinalName() {
        return "什麼都沒有";
    }
}

//解析註解
public class Main {

    public static void main(String[] args) {
        //經過反射處理
        Method[] methods = UseAnnotation.class.getDeclaredMethods();
        for (Method method : methods) {
            Name name = method.getAnnotation(Name.class);
            System.out.println("註解的值:"+name.value());
        }
    }
}

//輸出結果
註解的值:小學僧
註解的值:我是默認名
複製代碼

定義了一個註解,而且分別在getName與getFinalName方法上使用;因爲第二個方法上沒有設置value,因此在反射調用時輸出的是默認名.post

2.2 編譯時註解示例

瞭解編譯時註解,須要先了解下Element相關的知識.接下來看Element.學習

2.2.1 Element

Element位於javax.lang.model.element包下,一個Element表明一個程序元素.
對應關係以下:gradle

package annotationDemo.compile; //PackageElement包

public class TestElement {//TypeElement類
    private int value;//VariableElement變量

    public int getValue() {//ExecutableElement方法
        return value;
    }
}

複製代碼

Element的中重要API;

  • getEnclosingElement : 獲取一個元素的外部元素.

    • 好比上述成員變量value,以及getValue方法,它們對應VariableElement、ExecutableElement,那麼獲取到的外部元素則是TestElement類對應的元素.
  • getEnclosedElement : 獲取一個元素的內部元素.

    • 一樣,TestElement類對應typeElement元素,那麼獲取到的是VariableElement和ExecutableElement的元素集合.

2.2.2 使用示例

建立一個java-library,命名爲:annotations
定義兩個註解用來注入int和String類型數據.

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface injectInt {

}

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface injectString {

}
複製代碼

再建立一個java-library,命名爲:compiler
定義註解處理器,解析註解

//build.gradle文件

dependencies {
    implementation project(':annotations')
    implementation 'com.squareup:javapoet:1.11.1'//java文件生成工具
    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    //註冊註解處理,自動在ressources生成META-INF/services/javax.annotation.processing.Processor的文件.文件內容爲:自定義的註解處理器的類的全路徑.
}

複製代碼
@SupportedAnnotationTypes({"com.cjy.lhk_annotations.mylhk,injectInt",
        "com.cjy.lhk_annotations.mylhk.injectString"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class injectProcessor extends AbstractProcessor {

    private static final ClassName CONTEXT =
            ClassName.get("android.content", "Context");


    //待生成java文件的的集合,key爲被註解的類的類名,value爲GenerateJavaFile對象
    private HashMap<String, GenerateJavaFile> mGenerateJavaFiles = new HashMap<>();


    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        for (TypeElement typeElement : set) {//遍歷全部註解類對應的TypeElement
            //獲取註解類對應被註解的對應元素
            //好比註解被用在某個成員變量上,那麼這個就是獲取成員變量對應的元素
            for (Element element : roundEnvironment.getElementsAnnotatedWith(typeElement)) {
                addElementToGenerateJavaFile(element);
            }
        }
        createJavaFile();
        return true;
    }

    /**
     * 解析Element,並添加一個註解元素到對應的GenerateJavaFile對象中
     * (收集信息)
     *
     * @param element 註解元素
     */
    private void addElementToGenerateJavaFile(Element element) {
        //獲取element對應成員變量所在的類,即被註解的類
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();//獲取外部元素
        //System.out.println("---getQualifiedName =---" + typeElement.getQualifiedName());
        String[] split = typeElement.getQualifiedName().toString().split("\\.");
        String className = split[split.length - 1];

        //經過父類的processingEnv獲取報信者,用於在編譯過程當中打印log
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, "add element to generate file " + className);

        //獲取被註解類對應的GenerateJavaFile對象,若是沒有,則建立
        GenerateJavaFile generateJavaFile = mGenerateJavaFiles.get(className);
        if (generateJavaFile == null) {
            GenerateJavaFile file = new GenerateJavaFile();
            //設置待生成java文件的包名
            file.packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
            //設置待生成java文件的類名
            file.className = className + "_Inject";
            //初始化元素集合
            file.elements = new ArrayList<>();
            file.elements.add(element);
            //保存被註解類所對應要生成java類的GenerateJavaFile對象
            mGenerateJavaFiles.put(className, file);
        } else {
            //將註解元素添加到有的generateJavaFile對象中
            generateJavaFile.elements.add(element);
        }
    }

    /**
     * 生成java文件
     */
    private void createJavaFile() {
        //遍歷GenerateJavaFile集合
        for (String className : mGenerateJavaFiles.keySet()) {

            //獲取一個GenerateJavaFile對象
            GenerateJavaFile file = mGenerateJavaFiles.get(className);

            //構建一個構造方法,該構造方法帶有一個Context類型的參數
            MethodSpec.Builder builder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(CONTEXT, "context");

            //遍歷該類中須要處理的註解元素
            for (Element element : file.elements) {
                //若是註解的成員變量是一個int類型
                if (element.asType().toString().equals("int")) {
                    //在構造方法中添加一條語句
                    //例如:((MainActivity)context).one = context.getResources().getInteger(R.integer.one);
                    builder.addStatement("(($N)context).$N = context.getResources().getInteger(R.integer.$N)",
                            file.className.split("_")[0], element.getSimpleName(), element.getSimpleName());
                    //若是註解的是一個String類型
                } else if (element.asType().toString().equals("java.lang.String")) {
                    //在構造方法中添加一條語句
                    //例如:((MainActivity)context).hello = context.getResources().getString(R.string.hello);
                    builder.addStatement("(($N)context).$N = context.getResources().getString(R.string.$N)",
                            file.className.split("_")[0], element.getSimpleName(), element.getSimpleName());
                }
            }
            //構建一個類,添加一個上述的構造方法
            TypeSpec typeSpec = TypeSpec.classBuilder(file.className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(builder.build())
                    .build();
            try {
                //輸出java文件
                JavaFile javaFile = JavaFile.builder(file.packageName, typeSpec).build();
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 待生成的Java文件信息
     */
    private static class GenerateJavaFile {
        String packageName;//包名
        String className;//類名
        List<Element> elements;//程序元素集合
    }
}

複製代碼

註解處理器大體工做流程:

  1. 收集信息
    1. 遍歷全部註解類對應的TypeElement
      1. 經過TypeElement獲取被註解的元素(如類,成員變量,方法對應的元素對象)
      2. 解析Element(被註解對應元素)信息,將被相關信息添加到待生成的文件對象中
  2. 生成java文件
apt與annotationProcessor的做用

對於compiler項目,咱們只需在編譯時使用,運行時無需加載到jvm虛擬機中.因此採用apt的替代品annotationProcessor進行引入.

//android項目
dependencies {
    implementation project(':annotations')
    //對於android項目,因爲插件的默認支持能夠直接使用此方式,無需應用apt插件
    annotationProcessor (:'compiler')

}
複製代碼

因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.

相關文章
相關標籤/搜索