1.什么是注解
从JDK 5 开始,Java 增加了注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运
行时被读取,并执行相应的处理。通过使用注解,开发人员可以在不改变原有逻辑的情况下,
在源文件中嵌入一些补充的信息。
2.注解的分类
注解包含:标准注解和元注解。
-
标准注解
- @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中
的方法,则编译器会发出错误警告。 - @Deprecated:对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,
将会在编译时显示提示信息。 - @SuppressWarnings:选择性地取消特定代码段中的警告。
- @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起
使用时不会出现类型安全问题。
- @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中
-
元注解: 它用来注解其他注解,从而创建新的注解。
- @Targe:注解所修饰的对象范围。
- ElementType.TYPE:能修饰类、接口或枚举类型。
- ElementType.FIELD:能修饰成员变量。
- ElementType.METHOD:能修饰方法。
- ElementType.PARAMETER:能修饰参数。
- ElementType.CONSTRUCTOR:能修饰构造方法。
- ElementType.LOCAL_VARIABLE:能修饰局部变量。
- ElementType.ANNOTATION_TYPE:能修饰注解。
- ElementType.PACKAGE:能修饰包。
- ElementType.TYPE_PARAMETER:类型参数声明。
- ElementType.TYPE_USE:使用类型。
- @Inherited:表示注解可以被继承。
- @Documented:表示这个注解应该被 JavaDoc 工具记录。
- @Retention:用来声明注解的保留策略。
- RetentionPolicy.SOURCE:源码级注解。注解信息只会保留在.java 源码中,源码在编译
后,注解信息被丢弃,不会保留在.class 中。 - RetentionPolicy.CLASS:编译时注解。注解信息会保留在.java 源码以及.class 中。当运
行 Java 程序时,JVM 会丢弃该注解信息,不会保留在 JVM 中。 - RetentionPolicy.RUNTIME:运行时注解。当运行 Java 程序时,JVM 也会保留该注解信
息,可以通过反射获取该注解信息。
- RetentionPolicy.SOURCE:源码级注解。注解信息只会保留在.java 源码中,源码在编译
- @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次
使用。
- @Targe:注解所修饰的对象范围。
3.注解的使用
使用@interface来申明注解,注解里边可以添加属性,属性默认是public的并且只能是public的,
可以使用default 来设置默认值。
public @interface SoulMaster {
String name() default "唐三";
int age() default 24;
}
定义运行时注解
@Retention(RetentionPolicy.RUNTIME)
public @interface SoulMaster {
String name() default "唐三";
int age() default 24;
}
如果将@Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解
@Retention(RetentionPolicy.CLASS)
public @interface SoulMaster {
String name() default "唐三";
int age() default 24;
}
4.注解处理器
如果没有处理注解的工具,那么注解也不会有什么大的作用。对于不同的注解有不同的注解处理器。
- 针对运行时注解会采用反射机制处理。
- 针对编译时注解会采用 AbstractProcessor 来处理。
5.运行时注解处理器
下面来通过一个例子演示:获取运行时注解的属性
首先定义一个运行时注解,在方法上使用
@Documented //JavaDoc 应该包含这个注解
@Target(ElementType.METHOD) //在方法上修饰
@Retention(RetentionPolicy.RUNTIME) //运行时注解
public @interface POST {
String value(); //带一个参数
}
声明一个类使用这个注解
public class LoginManager {
@POST(value = "https://login.meishe.com")
public void login(){
}
}
测试方法来获取这个注解,并输出注解的value
public class AnnotationTest {
/**
* 获取运行时注解
*/
@Test
public void testLogin(){
Method[] declaredMethods = LoginManager.class.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getName().equals("login")){
POST annotation = declaredMethod.getAnnotation(POST.class);
System.out.println("注解的value="+annotation.value());
}
}
}
}
输出:
注解的value=https://login.meishe.com
Process finished with exit code 0
6.编译时注解处理器
编译时在编译的时候也会存在的注解,这类的注解,如果想拿到注解的内容并且执行一定的代码逻辑,就需要借助注解处理器以及poet才行。
定义一个编译时注解,只有一个属性value
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindClass {
String value();
}
定义一个类使用这个注解,这个类没有内容,只是添加了上面定义的注解
@BindClass(value = "注解处理器")
public class BindClassView {
}
准备工作做好了,下面是就是注解处理器的部分了
注解处理器部分需要添加gradle依赖
// 这是Google对注解处理器所提供的服务,目的是为了生成注解配置,并 可以生成java文件代码等
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
// 此JavaPoet可以去生成Java文件 以面向对象的思维,生成代码
implementation 'com.squareup:javapoet:1.9.0'
注解处理器源码
/**
* Google的这个AutoService可以去生成配置文件
*/
@AutoService(Processor.class)
/**
* 配置版本(Java编译时的版本)
*/
@SupportedSourceVersion(SourceVersion.RELEASE_7)
/**
* 允许注解处理器 可以去处理的注解,不是所有的注解处理器都可以去处理
*/
@SupportedAnnotationTypes({"com.meishe.annotation.BindClass"})
/**
* 注解处理器能够接收的参数(例如:如果想把AndroidApp信息传递到这个注解处理器(Java工程),是没法实现的,所以需要通过这个才能接收到)
*/
@SupportedOptions("value")
public class BindClassProcess extends AbstractProcessor {
/**
* 注解节点
*/
Elements mElementTool;
/**
* 类信息
*/
Types mTypesTool;
/**
* 专用日志
*/
Messager mMessager;
/**
* 过滤器
*/
Filer mFiler;
/**
* 用于做一些初始化的操作
* @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementTool =processingEnv.getElementUtils();
mTypesTool =processingEnv.getTypeUtils();
mMessager =processingEnv.getMessager();
mFiler =processingEnv.getFiler();
String value = processingEnv.getOptions().get("value");
mMessager.printMessage(Diagnostic.Kind.NOTE,"init---->从Android App Gradle中传递过来的value="+value);
}
/**
* 只要使用了定义好的注解,可以统一的执行一定的代码逻辑
* @param annotations 使用注解的节点数组
* @param roundEnv 封装的环境内容,使用注解的类的包名字 类名等信息都可以在这个上面拿到
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement element : annotations) {
String packageName = mElementTool.getPackageOf(element).getQualifiedName().toString();
// 获取简单的 类名
String className = element.getSimpleName().toString();
mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName---"+packageName+" className---"+className);
// 打印一下信息
// 最终要生成的类名
String finalResultClassNmae = className + "$BinderViewClass";
mMessager.printMessage(Diagnostic.Kind.NOTE,"finalResultClassNmae----"+finalResultClassNmae);
// 开始真正的使用JavaPoet的方式来生成 Java代码文件
MethodSpec methodSpec = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "刘恬希 is my daughter")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder(finalResultClassNmae)
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpec)
.build();
JavaFile javaFile=JavaFile.builder(packageName,typeSpec).build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
mMessager.printMessage(Diagnostic.Kind.NOTE,e.getMessage());
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "代码注入完成...");
}
return true;
}
}
- init()方法一般就是进行一些初始化的操作。
- process 这个方法是关键的部分,用于根据使用注解的类,生成相应的代码部分。使用javaPoet输出代码非常友好方便。
然后进行项目构建,在build->intermediates->javac->debug->classes->包名下面就能看到通过注解处理期生成的class源码。
public class BindClass$BinderViewClass {
public static void main(String[] args) {
System.out.println("刘恬希 is my daughter");
}
}
这个方式进行的源码注入,仅仅是在编译的时候进行的,不影响运行时的运行速度,越来越多的开源框架采用这个方式替代反射的方式。