注解的定义
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
常见的注解如下图所示:
注解主要作用
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等
- 跟踪代码依赖性,实现替代配置文件功能,替代xml的作用,SpringMVC中比较多少使用。
- 在编译时进行格式检查,如 @Overrider @CallSuper @Nullable 和 @NonNull等
- Android中常见用法
1)以及资源注解@LayoutRes、@IntegerRes, @StringRes、@ColorRes等
2)权限注解@RequiresPermission
3)进程类注解@UiThread,@BinderThread,@MainThread,@WorkerThread
4)ButterKnife 、EventBus、Dagger2框架
元注解:
那么注解是怎么定义的呢,我们首先看下常用的Override 注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这里就涉及到元注解,我们一般用元注解来定义注解,元注解主要有以下几个。下面我们会一一进行介绍。
- @Target
- @Retention
- @Documented
- @Inherited
- @Reptable JAVA8(新增)
@Target
描述注解的使用范围(即:被修饰的注解可以用在什么地方)。主要为以下类型:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Documented
描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
@Inherited
Inherited注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。
@Retention
Retention注解取值都在RetentionPolicy中
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
- SOURCE:在源文件中有效(即源文件保留), @Override, @SuppressWarnings都属于这类注解;
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
RetentionPolicy.Source
我们来看下RetentionPolicy.Source 的使用场景:由于在编译的过程中这个注解还被保留着,所以在编译过程中可以针对这个policy进行一些操作。
- 枚举类型
有时候我们会使用Enum来定义枚举,对于枚举来说占用的内存往往是使用静态常量的两倍,但是我们更推荐用注解定义枚举类型。
使用方式如下:
@IntDef({Color.RED , Color.YELLOW ,Color.BLUE})
@Retention(RetentionPolicy.SOURCE)
public @interface Color{
int RED = 1;
int YELLOW = 2;
int BLUE= 3;
}
- lombok类似场景
还有一种场景是比如在自动生成java代码的场景下使用。最常见的就是lombok的使用了,可以自动生成field的get和set方法以及toString方法,构造器等
详细实现方式及原理见:https://www.jianshu.com/p/fc06578e805a
RetentionPolicy.Class
CLASS:在class文件中有效(即class保留)。
编译时注解注解处理器的实现主要依赖于AbstractProcessor来实现,这个类是在javax.annotation.processing包中,同时为了我们自己生成java源文件方便,我们还需要引入一些第三方库,主要包括
javapoet 用于生成java源文件,可参考https://github.com/square/javapoet
auto-service 主要用于生成一些辅助信息,例如META-INF/services 一些信息等.
典型案例框架:Butterknife
基本原理如下:
注解处理器
首先来了解下什么是注解处理器,注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。
你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。一个注解的注解处理器,以java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。
自定义RetentionPolicy.Class 注解场景
这里仅介绍AbstractProcessor相关接口,后续后做自定义注解的说明。
public class MyProcessor extends AbstractProcessor {
private Elements mElementsUtils;
/**
* 注解初始化接口,一般用于做一些初始化准备操作;
* <p>
* processingEnvironment 提供了一些工具
* 包括操作元素、打印信息、文件管理器以及其他工具
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// 元素操作工具
Elements elementUtils = processingEnvironment.getElementUtils();
mElementsUtils = elementUtils;
// 用于创建java源文件或者class文件
Filer filer = processingEnvironment.getFiler();
// 区域信息
Locale locale = processingEnvironment.getLocale();
// 传递给注释处理工具的特定于处理器的选项
Map<String, String> options = processingEnvironment.getOptions();
//任何生成的 source和 class文件应符合的源版本
SourceVersion sourceVersion = processingEnvironment.getSourceVersion();
//一些用于对类型进行操作的实用程序方法的实现
Types typeUtils = processingEnvironment.getTypeUtils();
//返回用于报告错误,警告和其他通知的消息。
Messager messager = processingEnvironment.getMessager();
}
/**
* 注解处理接口,注解真正实现的逻辑
* 我们可以通过JavaPoet 生成java源文件
*
* @param annotations 请求处理的注释类型
* @param roundEnvironment 有关当前和上一轮信息的环境
* @return 如果返回true, 声明注释类型,并且不会要求后续处理器处理它们; 如果返回false ,则注释类型无人认领,可能会要求后续处理器处理它们
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
//如果在前一轮处理中引发错误,则返回true ; 否则返回false
boolean b = roundEnvironment.errorRaised();
// 返回前一轮生成的注释处理的root elements
Set<? extends Element> rootElements = roundEnvironment.getRootElements();
//如果此轮生成的类型不受后续轮注释处理的影响,则返回true ; 否则返回false 。
boolean b1 = roundEnvironment.processingOver();
// 返回使用给定注释类型注释的元素。
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(MyAnnotations.class);
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element;
//获取注解的值
MyAnnotations annotation = typeElement.getAnnotation(MyAnnotations.class);
int value = annotation.value();
//获取此类型元素的完全限定名称
String s = typeElement.getQualifiedName().toString();
//获取当前注解的包路径
PackageElement packageElement = mElementsUtils.getPackageOf(typeElement);
String packageName = packageElement.getQualifiedName().toString();
}
return true;
}
/**
* 此处理器支持的注释类型的名称
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
// 本处理器仅处理MyAnnotations的注解
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add(MyAnnotations.class.getCanonicalName());
return supportedAnnotationTypes;
}
/**
* 返回此注释处理器支持的最新源版本
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.CLASS)
private @interface MyAnnotations {
int value();
}
}
RetentionPolicy.RUNTIME
RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。
注解在各种元素的提供了一些注解相关的操作,包括Class、Method、Field等。我们分别来看下他们提供了哪些注解相关的方法,主要是 AnnotatedElement接口。
/**
* 如果此元素上 存在指定类型的注解,则返回true,否则返回false
*/
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
/**
* 如果此元素上存在指定注释类型,则返回此元素的注释,否则为null
*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/**
* 返回此元素上存在的注释。 如果没有存在于此元素上注解,返回值是长度为0这种方法的调用者可以随意修改返回的数组的数组; 它对返回给其他调用者的数组没有影响。
*/
Annotation[] getAnnotations();
/**
* 返回与此元素关联的注释。 如果没有与此元素关联的注释,则返回值是长度为0的数组。
*/
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) ;
/**
* 如果直接存在这样的注释,则返回指定类型的此元素的注释,否则返回null。 此方法忽略继承的注释。
*/
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) ;
/**
* 如果此类注释直接存在或间接存在 ,则返回指定类型的此元素的注释。
*此方法忽略继承的注释。此方法与[`getDeclaredAnnotation(Class)`] )
* 之间的区别在于此方法检测其参数是否为*可重复注释类型*
*/
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass);
/**
* 返回直接出现在此元素上的注释。 此方法忽略继承的注释。 如果此元素上没有直接存在注释,则返回值为长度为0的数组
*/
Annotation[] getDeclaredAnnotations();
以下为简单的使用样例
@AnnotationDemo.TypeRuntimeAnnotation(11)
public class AnnotationDemo {
private static final String TAG = "AnnotationDemo";
public static void testAnnotationDemo() {
Log.d(TAG, "testAnnotationDemo() called");
Class<AnnotationDemo> testAnnotationsClass = AnnotationDemo.class;
//查找class的注解
TypeRuntimeAnnotation[] annotations = new TypeRuntimeAnnotation[0];
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
annotations = testAnnotationsClass.getAnnotationsByType(TypeRuntimeAnnotation.class);
}
for (TypeRuntimeAnnotation annotation : annotations) {
int value = annotation.value();
Log.d(TAG,"TypeRuntimeAnnotation: " + value);
}
//寻找注解的方法
Method[] declaredMethods = testAnnotationsClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
MethodRuntimeAnnotation annotation = declaredMethod.getAnnotation(MethodRuntimeAnnotation.class);
if (annotation != null) {
Log.d(TAG, "declaredMethod: " + declaredMethod.getName());
boolean value = annotation.value();
Log.d(TAG, "annotation value: " + value);
}
}
//寻找注解的域
Field[] declaredFields = testAnnotationsClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
FieldRuntimeAnnotation annotation = declaredField.getAnnotation(FieldRuntimeAnnotation.class);
if (annotation != null) {
Log.d(TAG, "declaredMethod: " + declaredField.getName());
int value = annotation.value();
Log.d(TAG, "annotation value: " + value);
}
}
}
@FieldRuntimeAnnotation(1)
public int mValue;
@MethodRuntimeAnnotation(true)
public void testMethodRuntimeAnnotation() {
}
/**
* 方法注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodRuntimeAnnotation {
boolean value();
}
/**
* 域注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldRuntimeAnnotation {
int value();
}
/**
* 类,接口注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeRuntimeAnnotation {
int value();
}
}