註解處理
在開發中,碰見很多註解,如@Override
、@Documented
等,還有像現在很多依賴注入庫如ARouter,Dagger 2等·,他的內部如何處理自己的註解,通過什麼途徑獲取自己的註解,實現自己的業務邏輯。看一下ARouter,當添加ARouter依賴後,在需要的Activity
添加註解,編譯就會看到在build文件夾下生成如下圖的文件,文件生成是通過在註解處理器中編寫解析和生成代碼格式。
註解處理器:
是一個在javac中的,用來編譯時掃描和處理的註解的工具。你可以爲特定的註解,註冊你自己的註解處理器。
註解處理器可以生成Java代碼,這些生成的Java代碼會組成 .java 文件,但不能修改已經存在的Java類(即不能向已有的類中添加方法)。而這些生成的Java文件,會同時與其他普通的手寫Java源代碼一起被javac編譯。
註解處理器抽象類AbstractProcessor
:
public class AnnotionTest extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
}
init(ProcessingEnvironment processingEnvironment)
初始化數據,相當於構造器,初始化一些工具類,如下。
Map<String, String> getOptions();
Messager getMessager();
Filer getFiler();
Elements getElementUtils();
Types getTypeUtils();
SourceVersion getSourceVersion();
Locale getLocale();
getSupportedAnnotationTypes()
必須重寫,指明要註解哪個註解,如
Set<String> setClazzName = new LinkedHashSet<>();
setClazzName.add(TestSelfAnnotation.class.getName());
return setClazzName;
getSupportedSourceVersion()
支持的java版本。
process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
業務邏輯的處理。
如何使用註解處理器:
- 首先我們需要創建java庫,在庫中定義自己的註解,如下:
@Documented
@Target({ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface TestSelfAnnotation {
int value() default 0;
}
- 創建一個java庫,添加如下依賴,編寫自己的註解處理器繼承
AbstractProcessor
,生成代碼使用JavaPoet庫,生成.java源文件。
compile 'com.google.auto.service:auto-service:1.0-rc4'
compile 'com.squareup:javapoet:1.8.0'
implementation project(':annotationLib')
@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {
private Map<String, String> getOptions;
private Messager getMessagers;
private Filer getFilers;
private Elements elementUtils;
private Types typeUtils;
private SourceVersion getSourceVersion;
private Locale getLocale;
private static final String RANDOM_SUFFIX = "$$BindView";
private HashMap<String, HashMap<String, String>> entryKey = new HashMap<>();
private HashMap<String, Element> elementIndexMap = new HashMap<>();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(TestSelfAnnotation.class);
for (final Element element : elementsAnnotatedWith)
if (element.getKind() == ElementKind.FIELD) {
TestSelfAnnotation annotation = element.getAnnotation(TestSelfAnnotation.class);
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
ClassName className = ClassName.get(enclosingElement);
if (entryKey.containsKey(className.simpleName())) {
HashMap<String, String> stringStringHashMap = entryKey.get(className.simpleName());
stringStringHashMap.put(element.getSimpleName().toString(), annotation.value() + "");
// entryKey.put(element.getEnclosingElement().getSimpleName().toString(), stringStringHashMap);
} else {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(element.getSimpleName().toString(), annotation.value() + "");
elementIndexMap.put(className.simpleName(), element);
entryKey.put(className.simpleName(), hashMap);
}
}
writeClazz(entryKey, elementIndexMap);
return false;
}
private void writeClazz(HashMap<String, HashMap<String, String>> entryKey, HashMap<String, Element> elementIndexMap) {
for (Map.Entry<String, HashMap<String, String>> map : entryKey.entrySet()) {
String key = map.getKey();
HashMap<String, String> value = map.getValue();
Element element = elementIndexMap.get(key);
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
ClassName className = ClassName.get(enclosingElement);
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(className, "activity");
if (value.size() > 0) {
for (HashMap.Entry<String, String> entry : value.entrySet()) {
CodeBlock of = CodeBlock.of("$N." + entry.getKey() + "=" + "$N." + "findViewById("
+ entry.getValue() + ");\n", "activity", "activity");
builder.addCode(of);
}
}
TypeSpec build = TypeSpec.classBuilder(getClazzName(element) + RANDOM_SUFFIX)
.addModifiers(Modifier.PUBLIC)
.addMethod(builder.build())
.build();
try {
JavaFile javaFile = JavaFile.builder(getPackageName(element), build).build();
javaFile.writeTo(getFilers);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String getClazzName(Element element) {
Element enclosingElement = element.getEnclosingElement();
return enclosingElement.getSimpleName().toString();
}
private String getPackageName(Element element) {
PackageElement packageOf = elementUtils.getPackageOf(element);
return packageOf.getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
getMessagers = processingEnvironment.getMessager();
getFilers = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> setClazzName = new LinkedHashSet<>();
System.out.println(" " + TestSelfAnnotation.class.getCanonicalName());
setClazzName.add(TestSelfAnnotation.class.getName());
return setClazzName;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}
@AutoService(Processor.class)
幫我們自動生成生成如下文件。
3. module依賴註解後,使用註解,編譯,我們可以看到,在apt文件下生成文件。
文件結構如下:
@TestSelfAnnotation(R.id.tv_button)
TextView tv_button;
@TestSelfAnnotation(R.id.bt_ok)
Button bt_button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shop_main);
HelpAnnotation.inject(this);
}
HelpAnnotation
工具類:
public class HelpAnnotation {
private static final String RANDOM_SUFFIX = "$$BindView";
/**
* 通過反射的方式找到對應的輔助類,並調用對應的方法實現屬性的注入
*
* @param object 被注入的對象
*/
public static void inject(Object object) {
try {
Class bindingClass = Class.forName(object.getClass().getCanonicalName() + RANDOM_SUFFIX);
//noinspection unchecked
Constructor constructor = bindingClass.getConstructor(object.getClass());
constructor.newInstance(object);
} catch (ClassNotFoundException e) {
Log.e("TAG", "Meaningful Message", e);
} catch (NoSuchMethodException e) {
Log.e("TAG", "Meaningful Message", e);
} catch (IllegalAccessException e) {
Log.e("TAG", "Meaningful Message", e);
} catch (InstantiationException e) {
Log.e("TAG", "Meaningful Message", e);
} catch (InvocationTargetException e) {
Log.e("TAG", "Meaningful Message", e);
}
}
}
常見的幾個API:
Name simpleName = element.getSimpleName();//註解的元素名稱
//如果元素在{}內,則返回類名稱。
如果這是頂級類型,則返回其包。
如果這是一個包,null則返回。
如果這是一個類型參數, 則返回參數類型。
element.getEnclosingElement();
ClassName className = ClassName.get(enclosingElement);//獲得類名
源碼ComponentAppliction