1.什么是注解
从JDK 5 开始,Java 增加了注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运
行时被读取,并执行相应的处理。通过使用注解,开发人员可以在不改变原有逻辑的情况下,
在源文件中嵌入一些补充的信息。
2.注解的分类
注解包含:标准注解和元注解。
-
标准注解
- @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中
的方法,则编译器会发出错误警告。 - @Deprecated:对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,
将会在编译时显示提示信息。 - @SuppressWarnings:选择性地取消特定代码段中的警告。
- @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起
使用时不会出现类型安全问题。
-
元注解: 它用来注解其他注解,从而创建新的注解。
- @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 也会保留该注解信
息,可以通过反射获取该注解信息。
- @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次
使用。
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
@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依赖
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.9.0'
注解处理器源码
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({"com.meishe.annotation.BindClass"})
@SupportedOptions("value")
public class BindClassProcess extends AbstractProcessor {
Elements mElementTool;
Types mTypesTool;
Messager mMessager;
Filer mFiler;
@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);
}
@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);
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");
}
}
这个方式进行的源码注入,仅仅是在编译的时候进行的,不影响运行时的运行速度,越来越多的开源框架采用这个方式替代反射的方式。
源码链接
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)