本章知识点
- JDK内置系统注解
- 元注释以及自定义注释
- 注解解析器实现
- Android中的专用注解
JDK内置系统注解
Java提供了三种内建注解。
@Override 这个估计开发中是最常见的一个注解了,它用于表示所注解的方法是一个用于覆盖父类的一个方法,一旦存在任何的拼写错误导致方法签名对不上被覆盖的方法,编译器就会发出错误提示。 @Deprecated 这个一般用于标记某个方法不在使用了,通过这个注释,开发者可以了解到哪些接口是处于遗弃状态的。 @SuppressWarnings 这个注释用于忽视某个警告信息。
|
元注释以及自定义注释
Java中提供了四种元注释用于定义自定义注释,如下所示:
@Target 表示该注解可以用在什么地方,由ElementType枚举定义 ,当注解未指定Target值时,该注解可以使用任何元素之上
PACKAGE:包声明 TYPE:类、接口 注解类型,enum声明 ANNOTATION_TYPE:注解声明(应用于另一个注解上) FIELD:域声明(包括enum实例) CONSTRUCTOR:构造器的声明 METHOD:方法声明 LOCAL_VARIABLE:局部变量声明 PARAMETER:参数声明 TYPE_PARAMETER:类型参数声明(1.8新加入) TYPE_USE:类型使用声明(1.8新加入)
|
@Retention 表示需要在什么级别保存该注解信息,由RetentionPolicy枚举定义,当注解未定义Retention值时,默认值是CLASS
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里) CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中) RUNTIME:VM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)
|
@Documented 表示注解会被包含在javaApi文档中
@Inherited 允许子类继承父类的注解,表示注解类型能被自动继承。 如果一个类使用了 @Inherited 类型的注解,则此类的子类也将含有该注解。
自定义注解语法:
import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface AnatationTest { }
|
需要注意的是注解也将会编译成class文件,我们通过javac将上述的注解编译为字节码,然后使用javap对其进行反编译,将会得到如下的代码:
public interface AnatationTest extends java.lang.annotation.Annotation { }
|
也就是说使用@interface 关键字声明一个注解,它会自动继承 java.lang.annotation.Annotaion 接口。
自定义注解的时候需要注意如下几点:
- 参数成员访问修饰符只能使用 public 或者 default 这个和枚举是一样的
- 注解方法不能有参数。
- @interface 里面的每一个方法表示声明了一个可配置的参数,方法名即位参数名。返回值类型就是参数的类型,下面是能够允许的类型:
所有基本类型(int,float,boolean,byte,double,char,long,short) String Class enum Annotation 以及上述类型所组成的 数组
|
- 如果需要声明默认值可以使用default 关键字。
- 注解元素必须有确定的值,要么在定义注解元素时默认值指定,要么使用此注解时指定。非基本类型注解元素的值不可为 null
注解解析器实现
注释的解析有两种方式:
- 运行时解析
运行时注解指的是@Retention的值为RUNTIME的注解。java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:
getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
getAnnotations() 返回此元素上存在的所有注解
getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。
isAnnotationPresent(Class<? extends Annotation> annotationClass) 当存在该元素的指定类型注解,则返回true,否则返回false
使用这些接口就可以在运行时解析对应的注释。
下面是运行时解析的例子:
@Target(ElementType.PACKAGE) @Retention(RetentionPolicy.RUNTIME) public @interface PackageAnnotation { String desc() default ""; }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TypeAnnotation { String desc() default ""; }
@Target(ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.RUNTIME) public @interface ConstructAnnotaion { String desc() default "";
}
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface FieldAnnotation { String desc() default ""; }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MethodAnnotation { String desc() default ""; }
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ParamAnnotation { String desc() default ""; }
|
@TypeAnnotation(desc = "This is TypeAnnotation") public class TestClass { @FieldAnnotation(desc = "This is FieldAnnotation") public String mTestField = "mTestField";
@ConstructAnnotaion(desc = "This is ConstructAnnotaion") public TestClass() { }
@MethodAnnotation(desc = "This is MethodAnnotation") public void method(@ParamAnnotation(desc = "This is ParamAnnotation")String param) { String localvalue = ""; System.out.println("method!!!!!"+param); } }
|
package-info.java
@PackageAnnotation(desc = "This is a package annotation!") package com.javabase.annotationbase.testclass; import com.javabase.annotationbase.annotations.PackageAnnotation;
|
public class RuntimeAnnotationProcessor {
public void testPackageAnnotation(Class<?> clzz) { Package packageName = clzz.getPackage(); if(packageName.isAnnotationPresent(PackageAnnotation.class)) { PackageAnnotation packageAnnotation = packageName.getAnnotation(PackageAnnotation.class); System.out.println(packageAnnotation.desc()); } }
public void testClassAnnotation(Class<?> clz) { if (clz.isAnnotationPresent(TypeAnnotation.class)) { TypeAnnotation typeAnnotation = clz.getAnnotation(TypeAnnotation.class); System.out.println(typeAnnotation.desc()); } }
public void testConstructureAnnotation(Class<?> clz) { Constructor[] constructors = clz.getConstructors(); for(Constructor constructor : constructors) { if(constructor.isAnnotationPresent(ConstructAnnotaion.class)) { ConstructAnnotaion annotation = (ConstructAnnotaion) constructor.getAnnotation(ConstructAnnotaion.class); System.out.println(annotation.desc()); } } }
public void testFieldAnnotation(Class<?> clz) { Field[] fields = clz.getFields(); if(fields != null && fields.length > 0) { for (Field field: fields) { if(field.isAnnotationPresent(FieldAnnotation.class)) { FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class); System.out.println(fieldAnnotation.desc()); } } } }
public void testMethodAnnotation(Class<?> clz) { Method[] methods = clz.getMethods(); if (methods != null && methods.length > 0) { for (Method method : methods) { if (method.isAnnotationPresent(MethodAnnotation.class)) { MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class); try { method.invoke(clz.newInstance(), ""); } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { e.printStackTrace(); } System.out.println(methodAnnotation.desc()); } } } }
public void testParamAnnotation(Class<?> clz) { Method[] methods = clz.getMethods(); if (methods != null && methods.length > 0) { for (Method method : methods) { if (method.isAnnotationPresent(MethodAnnotation.class)) { Parameter[] parameters = method.getParameters(); if (parameters != null && parameters.length > 0) { for (Parameter parameter : parameters) { if(parameter.isAnnotationPresent(ParamAnnotation.class)) { ParamAnnotation paramAnnotation = parameter.getAnnotation(ParamAnnotation.class); System.out.println(paramAnnotation.desc()); } } } } } } }
}
|
编译时解析
编译时注解指的是@Retention的值为CLASS的注解。对于这类注解的解析,我们需要自定义一个派生自 AbstractProcessor的“注解处理类”并重写process 函数。
APT(Annotation Processing Tool)是一种处理注释的工具
Annotation处理器在处理注释时可以根据源文件中的Annotation生成额外的源文件和其它的文件,APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
整个注解处理器的可以由如下几个部分构成:
注解处理器(AbstractProcess)+代码处理(JavaPoet)+处理器注册(AutoService)+apt
整个过程分成如下几个部分:
1.定义注解(如@automain)
2.定义注解处理器
3.在处理器里面完成处理方式,通常是生成java代码,这个就需要用到代码处理。
4.注册处理器,这里就需要用到处理器注册。
5.利用APT进一步处理。
建立一个APT 项目,并添加apt依赖:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.3' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
|
添加apt 插件
//添加APT plugin apply plugin: 'com.neenbedankt.android-apt'
|
新建一个model 命名为compiler 用于存放注解处理器:
并添加autoservice 和javapoet依赖:
apply plugin: 'java'
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.squareup:javapoet:1.8.0' compile 'com.google.auto.service:auto-service:1.0-rc2' }
sourceCompatibility = "1.7" targetCompatibility = "1.7"
|
创建AnnotationProcessor
package com.example;
import com.google.auto.service.AutoService; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec;
import java.io.IOException; import java.util.Collections; import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement;
@AutoService(Processor.class) public class AnnotationProcessor extends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(TestAnnotation.class.getCanonicalName()); }
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } return false; } }
|
这时候编译后会在/app/build/generated/source/apt/debug/目录下生成HelloWorld.java文件
参考材料
http://www.race604.com/annotation-processing/
http://www.jianshu.com/p/1942ad208927
http://blog.csdn.net/lmj623565791/article/details/43452969
https://github.com/taoweiji/DemoAPT?utm_source=tuicool&utm_medium=referral
http://www.cnblogs.com/lbangel/p/3523741.html
http://www.jianshu.com/p/94979c056b20
http://alighters.com/blog/2016/05/10/apt-code-generate/