注解 Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关 于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。 注解声明 声明一个注解类型 Java中所有的注解,默认实现 Annotation 接口:
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :
@Target和@Rentention
另外还有@Documented 与 @Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类 继承父类中定义的注解。
@Target 注解标记另一个注解,以限制可以应用注解的 Java 元素类型。目标注解指定以下元素类型之一作为其值:
- ElementType.ANNOTATION_TYPE 可以应用于注解类型。
- ElementType.CONSTRUCTOR 可以应用于构造函数。
- ElementType.FIELD 可以应用于字段或属性。
- ElementType.LOCAL_VARIABLE 可以应用于局部变量。
- ElementType.METHOD 可以应用于方法级注解。
- ElementType.PACKAGE 可以应用于包声明。
- ElementType.PARAMETER 可以应用于方法的参数。
- ElementType.TYPE 可以应用于类的任何元素。
不声明Target表示在所有作用域下生效,如果要求注解在多个目标下生效,可以在Target后声明多个目标即可,如
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention 注解指定标记注解的存储方式:
- RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
- RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
- RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。 @Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、 CLASS。下文会介绍他们不同的应用场景。 按照@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:
1、SOURCE
RetentionPolicy.SOURCE ,作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。 在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。 IDE语法检查
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Heria {
String name() default "heria";
int age() default 26;
}
使用时候的语法如下:
@Heria(name = "pdd", age = 29)
public class MyClass {}
又如这个例子
public static void setDrawable(@DrawableRes int id) {
}
也可以利用注解替换枚举,因为即保留了枚举但好处,又省掉了枚举带来但内存开销,因为枚举本质上是一组实例
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
//注解限定,只能在参数中传入限定的值,否则IDE会提示报错,但不影响编译
@IntDef({SUNDAY, MONDAY})
@Retention(RetentionPolicy.SOURCE)
@Target(PARAMETER)
@interface WEEKDAYRefs {
}
public static void setDate(@WEEKDAYRefs int date) {
}
public static void main(String[] args) {
AnnotationTest.setDate(3);
AnnotationTest.setDate(SUNDAY);
}
APT注解处理器 APT全称为:"Anotation Processor Tools",意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文 件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工 具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac 调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用 框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的 是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
2、CLASS
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合 此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。 所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否 登录的判断。 如果我们使用普通的编程方式,需要在代码中进行 if-else 的判断,也许存在十个判断点,则需要在每个判断点加 入此项判断。此时,我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录 两种类型,即两个切面。对于切面的区分即可采用注解。
3、RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。 下面是一个利用反射结合注解动态修改成员变量的例子:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectRes {
@IdRes int value();
}
public class InjectUtil {
public static void injectView(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
//获得此类所有的成员
Field[] declaredFields = cls.getDeclaredFields();
for (Field filed : declaredFields) {
// 判断属性是否被InjectView注解声明
if (filed.isAnnotationPresent(InjectRes.class)) {
InjectRes injectRes = filed.getAnnotation(InjectRes.class);
//获得了注解中设置的id
int id = injectRes.value();
View view = activity.findViewById(id);
//反射设置 属性的值
filed.setAccessible(true); //设置访问权限,允许操作private的属性
try {
//反射赋值
filed.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
public class InjectActivity extends AppCompatActivity {
private int mField1;
private int mFiled2;
@InjectRes(R.id.tv_content)
private TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.inject_actiivty);
InjectUtil.injectView(this);
mTextView.setText("pdddd");
//界面会显示“pdddd”
}
}