Java 注解
什么是注解?
Java 注解(Annotation)又称 Java 标注,用于为代码提供元数据。Java 注解从 Java 5 开始引入,注解不直接影响代码的执行。
什么用处?
- 生成文档
- 标识代码,便于查看
- 格式检查(编译时)
- 注解处理(编译期生成代码、xml 文件等;运行期反射解析;常用于三方框架)
基本使用
在 Java 中,注解可以应用到包、类、接口、方法、构造方法、变量等多种元素上。以下是一些基本的 Java 注解:
Java内置了三种标准注解,其定义在 java.lang 中。
- @Override,表示当前的方法定义将覆盖超类中的方法。
- @Deprecated,被此注解标记的元素表示被废弃,如果程序员使用了注解为它的元素,那么编译器会发出警告。
- @SuppressWarnings,关闭不当的编译器警告信息。
元注解
元注解是由 Java 提供的基础注解,负责注解其它注解。
元注解 | 说明 | 取值 |
---|---|---|
@Target | 表示该注解可以用在什么地方 | ElementType.ANNOTATION_TYPE 可以应用于注释类型。 ElementType.CONSTRUCTOR 可以应用于构造函数。 ElementType.FIELD 可以应用于字段或属性。 ElementType.LOCAL_VARIABLE 可以应用于局部变量。 ElementType.METHOD 可以应用于方法级注释。 ElementType.PACKAGE 可以应用于包声明。 ElementType.PARAMETER 可以应用于方法的参数。 ElementType.TYPE 可以应用于类的任何元素。 |
@Retention | 表示需要在什么级别保存该注解信息 | SOURCE: 只在源码中有效 (编译时抛弃) CLASS: 在 class 文件中有效(即 class 保留) RUNTIME: 在运行时有效(即运行时保留) |
@Documented | 表示将此注解包含在Javadoc中 | |
@Inherited | 表示允许子类继承父类中的注解 | |
@Repeatable | 指示注解可以在同一个声明上使用多次。 |
注解的提取
注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
然后通过 getAnnotation() 方法来获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了,比如
public class ReflectionBinding {
public static void bind(Activity activity) {
// 反射方式遍历目标类所有 field
for (Field field : activity.getClass().getDeclaredFields()) {
// if(field.isAnnotationPresent(ReflectionBindView.class))
ReflectionBindView bindView = field.getAnnotation(ReflectionBindView.class);
if (bindView != null) {
try {
// 目标类 field 可能设置为 privite/protected,修改可访问性
field.setAccessible(true);
field.set(activity, activity.findViewById(bindView.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
注解处理器(Annotation Processing)
原理
Annotation Processing 原理: 编译过程中读源码,然后⽣成新的代码⽂件,再放在⼀起进⾏编译
基本用法
Annotation Processing ⽤法:
- resources/META-INF/services/javax.annotation.processing.Processor(或者使用 AutoService)
- 继承 AbstractProcessor
- 重写 getSupportedAnnotationTypes() 和 process()
- 依赖 annotationProcessor先测试⽣成 java ⽂件的功能:手写或使用 javapoet
- 自动生成代码
- 添加依赖
使用示例
继承 AbstractProcessor,并重写getSupportedAnnotationTypes() 和 process()
public class BindingProcessor extends AbstractProcessor {
// 返回用于创建新源、类或辅助文件的文件管理器。
Filer filer;
// 返回用于报告错误、警告和其他通知的消息发送器。
Messager messager;
/**
* ProcessingEnvironment :注释处理工具框架将为注释处理器提供实现该接口的对象,以便处理器可以使用框架提供的设施来编写新文件、报告错误消息以及查找其他实用程序。
* @param processingEnv 访问设施的环境工具框架,提供给处理器
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
/**
* 一个抽象注释处理器,旨在方便大多数具体注释处理器的超类
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(BindView.class.getCanonicalName());
}
/**
* 注解处理器实际处理方法,一般要求子类实现该抽象方法,你可以在在这里写你的扫描与处理注解的代码,以及生成Java文件。其中参数RoundEnvironment ,
* 可以让你查询出包含特定注解的被注解元素,后面我们会看到详细的内容。
*
* @param set the annotation types requested to be processed
* @param roundEnvironment environment for information about the current and prior round
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
double preTime = System.nanoTime();
for (Element element : roundEnvironment.getRootElements()) {
String packageStr = element.getEnclosingElement().toString();
String classStr = element.getSimpleName().toString();
ClassName className = ClassName.get(packageStr, classStr + "Binding");
messager.printMessage(Diagnostic.Kind.NOTE, "------ packageStr=" + packageStr + " classStr=" + classStr + " className=" + className.canonicalName());
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(packageStr, classStr), "activity");
boolean hasBinding = false;
for (Element enclosedElement : element.getEnclosedElements()) {
if (enclosedElement.getKind() == ElementKind.FIELD) {
BindView bindView = enclosedElement.getAnnotation(BindView.class);
if (bindView != null) {
hasBinding = true;
constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
enclosedElement.getSimpleName(), bindView.value());
}
}
}
TypeSpec builtClass = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addMethod(constructorBuilder.build())
.build();
if (hasBinding) {
try {
JavaFile.builder(packageStr, builtClass)
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
messager.printMessage(Diagnostic.Kind.NOTE, "------ 耗时:" + (System.nanoTime() - preTime));
return false;
}
}
调试
Java Annotation Processor注解处理器如何 Debug
Android 注解
Android 注解库
support.annotation
是 Android 提供的注解库,与 Android Studio 内置的代码检查工具配合,注解可以帮助检测可能发生的问题,例如 null 指针异常和资源类型冲突等。
使用前配置 在 Module 的 build.gradle
中添加配置 ”implementation 'com.android.support:support-annotations:版本号'“
Null 性注解
-
@Nullable
可以为 null -
@NonNull
不可为 null
线程注解
线程注解可以检查某个方法是否从特定类型的线程调用。支持以下线程注解:
- @MainThread
- @UiThread
- @WorkerThread
- @BinderThread
- @AnyThread
更多 android 注解请参考(利用注解改进代码检查)