不忘初心 砥礪前行, Tomorrow Is Another Day !
相關文章
引言
本文概要:
- 註解基礎知識
- 註解的定義及使用
一. 註解的基礎知識
註解分類:標準註解、元註解.
1.1 標準註解
- @Override : 標記覆蓋超類的方法
- @Deprecated : 標記棄用
- @SuppressWarnings : 取消警告
- @SafeVarargs : 宣告在使用可變長度引數的方法,與泛型類一起使用不會出現型別安全問題
1.2 元註解
簡稱註解的註解,從而建立新的註解.
- @Target : 註解所修飾的型別
- @Inherited : 註解可以被繼承
- @Documented : 標記註解應該被javaDoc工具記錄
- @Retention : 註解的保留策略
這裡主要介紹@Target和@Retention兩個元註解.
@Target
修飾型別從列舉類ElementType取值
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,所以在反射呼叫時輸出的是預設名.
2.2 編譯時註解示例
瞭解編譯時註解,需要先了解下Element相關的知識.接下來看Element.
2.2.1 Element
Element位於javax.lang.model.element包下,一個Element代表一個程式元素.
對應關係如下:
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;//程式元素集合
}
}
複製程式碼
註解處理器大致工作流程:
- 收集資訊
- 遍歷所有註解類對應的TypeElement
- 通過TypeElement獲取被註解的元素(如類,成員變數,方法對應的元素物件)
- 解析Element(被註解對應元素)資訊,將被相關資訊新增到待生成的檔案物件中
- 生成java檔案
apt與annotationProcessor的作用
對於compiler專案,我們只需在編譯時使用,執行時無需載入到jvm虛擬機器中.所以採用apt的替代品annotationProcessor進行引入.
//android專案
dependencies {
implementation project(':annotations')
//對於android專案,由於外掛的預設支援可以直接使用此方式,無需應用apt外掛
annotationProcessor (:'compiler')
}
複製程式碼
由於本人技術有限,如有錯誤的地方,麻煩大家給我提出來,本人不勝感激,大家一起學習進步.