什么是注解
注解,可以看作是对 一个 类/方法 的一个扩展的模版,每个 类/方法 按照注解类中的规则,来为 类/方法 注解不同的参数,在用到的地方可以得到不同的 类/方法 中注解的各种参数与值。
从JDK5开始,java增加了对元数据(描述数据属性的信息)的支持。其实说白就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署。
注解声明
声明一个注解类型
java中所有的注解,默认实现Annotation接口
Annotation源码
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
注解的声明使用@interface关键字
/**
* @ProjectName:jetpackDemo
* @Description:
* 声明一个注解
* @Author: arrom
* @createTime:2022/1/18
*/
public @interface Arrom {
}
元注解
所谓元注解就是标记其他注解的注解
/**
* @ProjectName:jetpackDemo
* @Description:
* 声明一个注解
* @Author: arrom
* @createTime:2022/1/18
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Arrom {
}
@Target 用来约束注解可以应用的地方,ElementType是枚举类型
package java.lang.annotation;
public enum ElementType {
TYPE,//标明该注解可以用于类、接口(包括注解类型)或enum声明
FIELD,//标明该注解可以用于字段(域)声明,包括enum实例
METHOD,//标明该注解可以用于方法声明
PARAMETER,//标明该注解可以用于参数声明
CONSTRUCTOR,//标明注解可以用于构造函数声明
LOCAL_VARIABLE,//标明注解可以用于局部变量声明
ANNOTATION_TYPE,//标明注解可以用于注解声明(应用于另一个注解上)
PACKAGE,//标明注解可以用于包声明
TYPE_PARAMETER,//标明注解可以用于类型参数声明
TYPE_USE,//类型使用声明
MODULE; //标明注解可以用于模块
private ElementType() {
}
}
@Retention用来约束注解的生命周期,分别有三个值,
- RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
- RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
- RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。
注解元素和数据类型
通过上述对@Arrom注解的定义,我们了解了注解定义的过程,由于@Test内部没有定义其他元素,所以@Arrom也称为标记注解(marker annotation),但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用。
/**
* 对应数据表注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name() default "";
}
上述定义一个名为DBTable的注解,该用于主要用于数据库表与Bean类的映射(稍后会有完整案例分析),与前面Arrom注解不同的是,我们声明一个String类型的name元素,其默认值为空字符,但是必须注意到对应任何元素的声明应采用方法的声明方式,同时可选择使用default提供默认值,@DBTable使用方式
@DBTable(name = "USER")
public class User {
//
}
注解支持的元素数据类型
- 所有基本类型(int,float,boolean,byte,double,char,long,short)
- String
- Class
- enum
- Annotation
- 上述类型的数组
在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值
注解应用场景
RetentionPolicy.SOURCE
作用于源码级别的注解,可提供给IDE语法检查,APT等场景使用。在类中使用source级别的注解,编译之后class中会被丢弃。
在Android开发中,support-annotations与androidx.annotation中均有提供@IntDef注解
@Retention(SOURCE) //源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
/**
* Whether any other values are allowed. Normally this is
* not the case, but this allows you to specify a set of
* expected constants, which helps code completion in the IDE
* and documentation generation and so on, but without
* flagging compilation warnings if other values are specified.
*/
boolean open() default false;
}
此注解的意义在于能够取代枚举,实现方法入参显示。
使用枚举
public class Main {
public enum WeekDays {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
private WeekDays currentDay = WeekDays.SUNDAY;
public static void main(String[] args) {
// TODO Auto-generated method stub
Main obj = new Main();
obj.setCurrentDay(WeekDays.WEDNESDAY);
WeekDays today = obj.getCurrentDay();
switch (today) {
case SUNDAY:
System.out.println("Today is SUNDAY");
break;
case MONDAY:
System.out.println("Today is MONDAY");
break;
case TUESDAY:
System.out.println("Today is TUESDAY");
break;
case WEDNESDAY:
System.out.println("Today is WEDNESDAY");
break;
case THURSDAY:
System.out.println("Today is THURSDAY");
break;
case FRIDAY:
System.out.println("Today is FRIDAY");
break;
case SATURDAY:
System.out.println("Today is SATURDAY");
break;
default:
break;
}
}
public void setCurrentDay(WeekDays currentDay) {
this.currentDay = currentDay;
}
public WeekDays getCurrentDay() {
return currentDay;
}
}
使用@IntDef
public class MainActivity extends Activity {
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
@IntDef({SUNDAY, MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY})
@Retention(RetentionPolicy.SOURCE)
public @interface WeekDays {}
@WeekDays int currentDay = SUNDAY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setCurrentDay(WEDNESDAY);
@WeekDays int today = getCurrentDay();
switch (today){
case SUNDAY:
break;
case MONDAY:
break;
case TUESDAY:
break;
case WEDNESDAY:
break;
case THURSDAY:
break;
case FRIDAY:
break;
case SATURDAY:
break;
default:
break;
}
}
public void setCurrentDay(@WeekDays int currentDay) {
this.currentDay = currentDay;
}
@WeekDays
public int getCurrentDay() {
return currentDay;
}
}
在android系统中使用枚举会创建多个对象,消耗内存会变大。
APT注解处理器的原理
APT 的原理就是在需要使用的元素上(类、变量、方法、参数等)加上我们的注解,然后在编译时把使用了这个注解的元素都收集起来,做统一的处理。
创建一个注解处理器分为如下几步:
- 创建注解类
- 创建一个继承自 AbstractProcessor 的类,这就是 APT 的核心类
- 创建公开 API 及辅助工具
- 创建配置文件
- 使用
AbstractProcessor类中主要方法
getSupportedAnnotationTypes 返回一个注解处理器支持的注解类型。 process 处理解析注解元素并生成java代码
public abstract boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
第一个参数 annotations 就是当前注解处理器支持的注解集合,第二个 roundEnv 表示当前的 APT 环境,其中提供了很多API,可以通过它获取到注解元素的一些信息。 其中最重要的就是 getElementsAnnotatedWith 方法,通过它可以获取到所有使用了该注解的元素。
//获取所有使用了 BindView 注解的元素
val bindViewElementSet = roundEnv.getElementsAnnotatedWith(xxx::class.java)
RetentionPolicy.CLASS
编译时注解(RetentionPolicy.CLASS),指@Retention(RetentionPolicy.CLASS)作用域class字节码上,生命周期只有在编译器间有效。编译时注解注解处理器的实现主要依赖于AbstractProcessor来实现,这个类是在javax.annotation.processing包中,同时为了我们自己生成java源文件方便。
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
编译时注解的核心就是实现AbstractProcessor的process() 方法,一般来说主要有以下两个步骤
1 搜集信息,包括被注解的类的类信息,方法,字段等信息,还有注解的值
2 生成对应的java源代码,主要根据上一步的信息,生成响应的代码
编译时注解的Demo
注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Bind {
int value();
}
新建BindProcessor继承与AbstractProcessor并实现其process方法
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor{
/**
* java源文件操作相关类,主要用于生成java源文件
*/
private Filer mFiler;
/**
* 注解类型工具类,主要用于后续生成java源文件使用
* 类为TypeElement,变量为VariableElement,方法为ExecuteableElement
*/
private Elements mElementsUtils;
/**
* 日志打印,类似于log,可用于输出错误信息
*/
private Messager mMessager;
private static final ClassName sClassName = ClassName.get("com.qiyei.ioc.api", "Test");
/**
* 初始化,主要用于初始化各个变量
* @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElementsUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
}
/**
* 支持的注解类型
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> typeSet = new LinkedHashSet<>();
typeSet.add(Bind.class.getCanonicalName());
return typeSet;
}
/**
* 支持的版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
*
* 1.搜集信息
* 2.生成java源文件
* @param annotations
* @param roundEnv
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!annotations.isEmpty()){
//获取Bind注解类型的元素,这里是类类型TypeElement
Set<? extends Element> bindElement = roundEnv.getElementsAnnotatedWith(Bind.class);
try {
generateCode(bindElement);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
return false;
}
/**
*
* @param elements
*/
private void generateCode(Set<? extends Element> elements) throws IOException{
for (Element element : elements){
//由于是在类上注解,那么获取TypeElement
TypeElement typeElement = (TypeElement) element;
//获取全限定类名
String className = typeElement.getQualifiedName().toString();
mMessager.printMessage(Diagnostic.Kind.WARNING,"className:" + className);
//获取包路径
PackageElement packageElement = mElementsUtils.getPackageOf(typeElement);
String packageName = packageElement.getQualifiedName().toString();
mMessager.printMessage(Diagnostic.Kind.WARNING,"packageName:" + packageName);
//获取用于生成的类名
className = getClassName(typeElement,packageName);
//获取注解值
Bind bindAnnotation = typeElement.getAnnotation(Bind.class);
int value = bindAnnotation.value();
System.out.println("value:" + value);
//生成方法
MethodSpec.Builder methodBuilder = MethodSpec
.methodBuilder("sayHello")
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.INT,"n")
.returns(TypeName.INT);
//$L表示字面量 $T表示类型
methodBuilder.addStatement("return $L",value);
//生成的类
TypeSpec type = TypeSpec
.classBuilder(className + "$$" + value)
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build();
//创建javaFile文件对象
JavaFile javaFile = JavaFile.builder(packageName,type).build();
//写入源文件
javaFile.writeTo(mFiler);
}
}
/**
* 根据type和package获取类名
* @param type
* @param packageName
* @return
*/
private static String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen)
.replace('.', '$');
}
}
在Activity上使用Bind
@Bind(10)
public class MainActivity extends BaseSkinActivity {
private RecyclerView mRecyclerView;
private static final int MY_PERMISSIONS_REQUEST_WRITE_STORE = 1;
/**
* ViewModel
*/
private MainMenuViewModel mMenuViewModel;
private MainMenuAdapter mMenuAdapter;
/**
* 标题栏
*/
private CommonTitleBar mTitleBar = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// PermissionManager.requestAllDangerousPermission(this);
initData();
initView();
LogManager.i(TAG,"onCreate");
}
.......
}
RetentionPolicy.RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
总结
注解本质就是继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过动态代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法