一、注解
Java 注解(Annotation)又称Java 标注,是JDK5.0 引入的一种注释机制。
开发框架和架构时有用
Java中所有的注解,默认实现Annotion接口
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
声明
注解的声明通过@interface关键字
public @interface Teacher{
}
增加默认值 default
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation (元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个:@Target @Retention
@Target
元注解之一,用来限制应用注解的Java元素类型.即注解可以在哪些地方用,定义时可以指定多个,作用在自定义注解上.
target注解指定以下元素类型作为其值
-
ElementType.ANNOTATION_TYPE 作用在类上
-
ElementType.CONSTRUCTOR 可以应用于构造函数
-
ElementType.FIELD 可以应用于字段或属性。
-
ElementType.LOCAL VARIABLE 可以应用于局部变量,
-
ElementType.METHOD 可以应用于方法级注解
-
ElementType.PACKAGE 可以应用于包声明
-
ElementType.PARAMETER 可以应用于方法的参数
-
ElementType.TYPE 可以应用于类的任何元素
@Retention
标记注解的存储方式,即注解的保留级别
- RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中,并被编译器忽略
- RetentionPolicy.CLASS -标记的注解信息会保留在.java和.class文件里,在执行时,会被Java虚拟机丢弃,不会加载到Java虚拟机中
- RetentionPolicy.RUNTIME标记的注解由JVM 保留,因此运行时环境可以使用它.
@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE.CLASS。
如下代码,表示该注解Teacher既可以应用在字段也可以应用在方法的参数,保留级别为源码级
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface Teacher {
}
public class MainDemo {
@Teacher
private int mAge;
public static void main(String[] args) {
}
private void setAge(@Teacher int age) {
this.mAge = age;
}
}
通过ASM插件分别看SOURCE 和CLASS编译后的MainDemo.class文件
注解应用场景
SOURCE
RetentionPolicy.SOURCEIDE,作用于源码级别的注解,可以用来提供给IDE做语法检查、APT等场景使用.
IDE语法检查
在Android开发中, support-annotations 与 androidx.annotation中均有提供 @IntDef 注解,此注解的定义如:
@Retention(SOURCE)//源码级别
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
此注解的意义在于能够取代枚举,实现方法入参限制.比如我们定义方法test,此方法接收参数 teacher 需要在:Joy、Bob中选择一个。如果使用枚举实现为:
public enum Teacher{
Joy,Bob
}
public void test(Teacher teacher){
}
test(Teacher.Joy);
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中,比常量多5到10倍的内存占用。现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:
public static final int Joy = 1;
public static final int Bob = 2;
public void test(int teacher){
}
然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:
public static final int Joy = 1;
public static final int Bob = 2;
// public void test(int teacher){
//无法限定参数
// }
@IntDef(value = {Joy,Bob})//限定
@Target(ElementType.PARAMETER)//作用于参数的注解
@Retention(RetentionPolicy.SOURCE)//源码级别的注解
public @interface Teacher{
}
public void test(@Teacher int teacher){
}
此时,我们再去调用 test 方法,如果传递的参数不是 Joy或者Bob则会显示 Inspection 警告(编译不会报错)。
CLASS
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。CLASS注解映用在字节码增强场景,所谓字节码增强,就是源码编译成.class字节码文件时,再在字节码中写入或修改代码,实现源代码未实现的功能,如埋点、性能监控、日志。因为字节码是一堆字母数字,阅读性很差,为了方便操作,提供了很多字节码操作框架。在这些工具中就用到CLASS注解,常见的字节码操作框架有ASM、Byte Buddy等。
RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
比如下面这个例子,页面中就只有一个TextView,我们利用注解完成TextView完成和id绑定,利用反射把id赋值给TextView,最终实现不用findViewById()也能使用TextView的目的.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAllCaps="false"
android:text="hello world!" />
</LinearLayout>
声明注解
@Retention(RetentionPolicy.RUNTIME)//注解保留至运行期
@Target(ElementType.FIELD)//应用在字段
public @interface InjectView {
//限制传值类型需要为Resource Id
@IdRes int viewId();
}
反射赋值
public class InjectUtils {
public static void injectView(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
//获得此类所有的成员
Field[] declaredFields = cls.getDeclaredFields();
for (Field filed : declaredFields) {
// 判断属性是否被InjectView注解声明
if (filed.isAnnotationPresent(InjectView.class)){
InjectView injectView = filed.getAnnotation(InjectView.class);
//获得了注解中设置的id
int id = injectView.viewId();
View view = activity.findViewById(id);
//反射设置 属性的值
filed.setAccessible(true); //设置访问权限,允许操作private的属性
try {
//反射赋值
filed.set(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
使用
public class RuntimeActivity extends AppCompatActivity {
private int mAge = 20;
private String mName = "Joy";
@InjectView(viewId = R.id.tv)
private TextView tv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectUtils.injectView(this);
tv.setText("不用findViewById");
}
}
运行效果