注解与反射

本文详细介绍了Java注解的原理,包括注解的声明、元注解(@Target和@Retention)的作用及不同保留策略SOURCE、CLASS、RUNTIME的应用场景,以及注解在IDE、字节码增强和反射中的实际运用。
摘要由CSDN通过智能技术生成

一、注解

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");
    }
}

运行效果

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值