不忘初心 砥礪前行, Tomorrow Is Another Day !java
本文概要:android
註解分類:標準註解、元註解.安全
簡稱註解的註解,從而建立新的註解.bash
這裏主要介紹@Target和@Retention兩個元註解.jvm
修飾類型從枚舉類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
}
複製代碼
保留策略從枚舉類RetentionPolicy中取值工具
public enum RetentionPolicy {
/**
* 源碼級java文件
*/
SOURCE,
/**
* 編譯時class文件 This is the default behavior.
*/
CLASS,
/**
*
* 運行時,加載到jvm虛擬機中.
* 經過反射獲取該註解信息
*/
RUNTIME
}
複製代碼
//定義註解
@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
瞭解編譯時註解,須要先了解下Element相關的知識.接下來看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 : 獲取一個元素的外部元素.
getEnclosedElement : 獲取一個元素的內部元素.
建立一個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文件
對於compiler項目,咱們只需在編譯時使用,運行時無需加載到jvm虛擬機中.因此採用apt的替代品annotationProcessor進行引入.
//android項目
dependencies {
implementation project(':annotations')
//對於android項目,因爲插件的默認支持能夠直接使用此方式,無需應用apt插件
annotationProcessor (:'compiler')
}
複製代碼
因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.