本篇博文针对具备注解基础的读者,主要讲解如何进行自定义注解。关于注解的具体基础知识点,网上这方面的学习资料非常多,可自行学习。
注解目前在主流的框架,比如Android中的Glide、Retrofit;Java Web方向的Spring等都有大量的使用。在给开发者带来巨大方便的同时,作为开发者有必要了解学习注解及其自定义,甚至可以自定义自己的注解库呦。
以下分析均在Android Studio中进行;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeBind {
int value();
}
RetentionPolicy.RUNTIME:表明这是一个运行时的注解,如果是编译时注解,则应该是RetentionPolicy.CLASS。
public class RuntimeAnnotationProcessor {
public static void bind2(Activity activity){
if (activity==null) return;
Field[] fields = activity.getClass().getDeclaredFields();
if (fields == null || fields.length == 0) return;
for(int i=0;i
可以看到,代码量还是很小的。首先,我们通过反射获取到当前Activity都有哪些字段,然后再判断每个字段是不是在使用RuntimeBind注解,如果使用了,则获取该注解的值,同时将该注解的值赋值给对应的字段就可以了。
public class MainActivity extends AppCompatActivity {
@RuntimeBind(R.id.tv_runtime)
TextView tv_runtime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RuntimeAnnotationProcessor.bind2(this);
tv_runtime.setText("运行时注解成功");
}
}
在使用的时候,只需要调用RuntimeAnnotationProcessor.bind2(..)即可完成繁琐的findViewById工作了。是不是很方便。但是,如果存在大量的通过运行时注解,则在一定程度上会影响程序的性能的。因此,编译时注解的优势就发挥出来了。
public interface IInject {
void inject(T t);
}
因此,我们生成的java源文件也必须实现这个接口。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Bind {
int value();
}
注意:一定要是RetentionPolicy,CLASS才是编译时注解。
方法 | 作用 |
---|---|
init(ProcessingEnvironment processingEnv) | 该方法有注解处理器自动调用,其中ProcessingEnvironment类提供了很多有用的工具类:Filter,Types,Elements,Messager等 |
getSupportedAnnotationTypes() | 该方法返回字符串的集合表示该处理器用于处理那些注解 |
getSupportedSourceVersion() | 该方法用来指定支持的java版本,一般来说我们都是支持到最新版本,因此直接返回SourceVersion.latestSupported() 即可 |
process(Set annotations, RoundEnvironment roundEnv) | 该方法是注解处理器处理注解的主要地方,我们需要在这里写扫描和处理注解的代码,以及最终生成的java文件。其中需要深入的是RoundEnvironment类,该用于查找出程序元素上使用的注解 |
public interface RoundEnvironment {
boolean processingOver();
//上一轮注解处理器是否产生错误
boolean errorRaised();
//返回上一轮注解处理器生成的根元素
Set getRootElements();
//返回包含指定注解类型的元素的集合
Set getElementsAnnotatedWith(TypeElement a);
//返回包含指定注解类型的元素的集合
Set getElementsAnnotatedWith(Class a);
}
3、
ProcessingEnvironment
public interface ProcessingEnvironment {
Map getOptions();
//Messager用来报告错误,警告和其他提示信息
Messager getMessager();
//Filter用来创建新的源文件,class文件以及辅助文件
Filer getFiler();
//Elements中包含用于操作Element的工具方法
Elements getElementUtils();
//Types中包含用于操作TypeMirror的工具方法
Types getTypeUtils();
SourceVersion getSourceVersion();
Locale getLocale();
}
好了,材料准备好了,可以开始做饭了。
@SupportedAnnotationTypes("com.example.Bind")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class BindAnnotationProcessor extends AbstractProcessor {
private Elements elementsUtils;
private Filer fileUtil;
private Map annotationInfoMap;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementsUtils = processingEnv.getElementUtils();
fileUtil = processingEnv.getFiler();
annotationInfoMap = new HashMap<>();
}
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
Set eleSet = roundEnv.getElementsAnnotatedWith(Bind.class);
for (Element ele : eleSet) {
if (!checkIsField(ele)) {
System.out.println("non filed is error inject ");
continue;
}
if (checkIsPrivate(ele)) {
System.out.println("filed cant not be private ");
continue;
}
VariableElement variableElement = (VariableElement) ele;
// full class name
String className = ((TypeElement) variableElement.getEnclosingElement()).getQualifiedName().toString();
AnnotationInfo annotationInfo = new AnnotationInfo(elementsUtils.getPackageOf(variableElement), (TypeElement) variableElement.getEnclosingElement());
int value = variableElement.getAnnotation(Bind.class).value();
annotationInfo.bindData.put(value, variableElement);
annotationInfoMap.put(className, annotationInfo);
}
for (String key : annotationInfoMap.keySet()) {
AnnotationInfo annotationInfo = annotationInfoMap.get(key);
JavaFileObject sourceFile = null;
try {
sourceFile = fileUtil.createSourceFile(annotationInfo.classEle.getSimpleName().toString() + "$$Injector");
Writer writer = sourceFile.openWriter();
writer.write(generateJavaSourceFile(annotationInfo));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private boolean checkIsPrivate(Element ele) {
if (ele.getModifiers().contains(Modifier.PRIVATE))
return true;
return false;
}
private boolean checkIsField(Element ele) {
if (ele.getKind().isField())
return true;
return false;
}
public String generateJavaSourceFile(AnnotationInfo annotationInfo) {
StringBuilder sb = new StringBuilder();
sb.append("//auto generate do not modify\n");
sb.append("package " + annotationInfo.packageEle.getQualifiedName().toString() + ";\n")
.append("import android.app.Activity;\n")
.append("import com.example.annotation_api.IInject;\n")
.append("public class " + annotationInfo.classEle.getSimpleName().toString() + "$$Injector")
.append(" implements IInject<" + annotationInfo.classEle.getQualifiedName() + "> {")
.append("@Override\n")
.append("public void inject(" + annotationInfo.classEle.getQualifiedName() + " activity) {");
for (int key : annotationInfo.bindData.keySet()) {
Element ele = annotationInfo.bindData.get(key);
sb.append("activity." + ele.getSimpleName() + " = (" + ele.asType() + ")activity.findViewById(" + key + ");\n");
}
sb.append("}}\n");
return sb.toString();
}
}
@SupportedAnnotationTypes("com.example.Bind"):声明支持Bind注解;
public class AnnotationInfo {
public Map bindData;
public PackageElement packageEle;
public TypeElement classEle;
public AnnotationInfo(PackageElement packageName,TypeElement className){
this.packageEle = packageName;
this.classEle = className;
bindData = new HashMap<>();
}
}
Mappackage com.example.xxx.myannotations;
import com.example.annotation_api.IInject;
public class MainActivity$$Injector implements IInject {
@Override
public void inject(com.example.xiuli.myannotations.MainActivity activity) {
activity.tv_annotationtest = (android.widget.TextView)activity.findViewById(2131492944);
}
}
public class Injector {
private static String suffix = "$$Injector";
public static void bind(Activity activity){
if (activity==null)
return;
StringBuilder sb = new StringBuilder();
sb.append(activity.getClass().getCanonicalName());
sb.append(suffix);
//查找注解生成的Java文件是否存在
if(!findAnnotationGenerateJavaFile(sb.toString()))
return;
try {
//通过接口调用Java 类的绑定方法inject,将注解的值绑定到字段
IInject inject = (IInject) Class.forName(sb.toString()).newInstance();
inject.inject(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
private static boolean findAnnotationGenerateJavaFile(String fullName){
try {
Class.forName(fullName);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}
Injector的主要作用就是根据既定的规则寻找已生成的Java源文件,然后加载并调用生成的Java类中的绑定方法。
public class MainActivity extends AppCompatActivity {
@Bind(R.id.tv_annotationtest)
TextView tv_annotationtest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Injector.bind(this);
tv_annotationtest.setText("编译型注解成功");
}
}