美文网首页
Java注解

Java注解

作者: Heezier | 来源:发表于2020-11-30 23:26 被阅读0次

    Java注解

    一、注解的定义和作用

    • 定义:注解是一种标识

      1.注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。

      2.Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。

    • 作用:标识 / 解释 Java 代码

    二、应用场景

    根据注解的保留级别不同,对注解的使用自然存在不同场景。

    级别 技术 说明
    源码 APT 在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。
    字节码 字节码增强 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。
    运行时 反射 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。

    在Android开发过,有很多框架都采用了注解来实现功能,

    比如JUnit单元测试框架:

    使用@Test 标记了要进行测试的方法Method() 
    
    public class ExampleUnitTest {
        @Test
        public void Method() throws Exception {
              ...
        }
    }
    

    Http网络请求库Retrofit:

    public interface GetRequest {
    
     @GET("url")
        Call<Translation> getCall();
    
    }
    
    

    ButterKnife:

    @BindView(R.id.test)
    TextView mTv;
    

    三、注解的类型

    1.元注解

    是一种 Android系统内置的注解

    在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。声明的注解允许作用于哪些节点使用@Target声明;保留级别由@Retention 声明。其中保留级别如下。

    RetentionPolicy.SOURCE
    标记的注解仅保留在源级别中,并被编译器忽略。

    RetentionPolicy.CLASS
    标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。

    RetentionPolicy.RUNTIME
    标记的注解由 JVM 保留,因此运行时环境可以使用它。

    • 元注解作用于注解 & 解释注解

    2.元注解类型介绍

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @Inherited
    @Repeatable()
    @Documented
    

    @Retention

    • 定义:保留注解
    • 作用:解释 / 说明了注解的生命周期

    @Documented

    • 定义:Java文档注解
    • 作用:将注解中的元素包含到 Javadoc文档中

    @Target

    • 定义:目标注解
    • 作用:限定了注解作用的目标范围,包括类、方法等等
    <-- @Target取值参数说明 -->
    // ElementType.PACKAGE:可以给一个包进行注解
    // ElementType.ANNOTATION_TYPE:可以给一个注解进行注解
    // ElementType.TYPE:可以给一个类型进行注解,如类、接口、枚举
    // ElementType.CONSTRUCTOR:可以给构造方法进行注解
    // ElementType.METHOD:可以给方法进行注解
    // ElementType.PARAMETER 可以给一个方法内的参数进行注解
    // ElementType.FIELD:可以给属性进行注解
    // ElementType.LOCAL_VARIABLE:可以给局部变量进行注解
    

    @Inherited

    • 定义:继承注解
    • 作用:使得一个 被@Inherited注解的注解 作用的类的子类可以继承该类的注解
    // 元注解@Inherited 作用于 注解Carson_Annotation
    @Inherited
    public @interface Carson_Annotation {
    }
    
    
    // 注解Carson_Annotation 作用于A类
    @Carson_Annotation
    public class A {
      }
    
    // B类继承了A类,即B类 = A类的子类,且B类没被任何注解应用
    // 那么B类继承了A类的注解 Carson_Annotation
    public class B extends A {}
    

    @Repeatable

    • 定义:可重复注解

    Java 1.8后引进

    • 作用:使得作用的注解可以取多个值
    // 1. 定义 容器注解 @ 职业
    public @interface Job {
        Person[]  value();
    }
    <-- 容器注解介绍 -->
    // 定义:本身也是一个注解
    // 作用:存放其它注解
    // 具体使用:必须有一个 value 属性;类型 = 被 @Repeatable 注解的注解数组
    // 如本例中,被 @Repeatable 作用 = @Person ,所以value属性 = Person []数组
    
    // 2. 定义@Person 
    // 3. 使用@Repeatable 注解 @Person
    // 注:@Repeatable 括号中的类 = 容器注解
    @Repeatable(Job.class)
    public @interface Person{
        String role default "";
    }
    
    // 在使用@Person(被@Repeatable 注解 )时,可以取多个值来解释Java代码
    // 下面注解表示:Carson类即是产品经理,又是程序猿
    @Person(role="coder")
    @Person(role="PM")
    public class Carson{
    
    }
    

    可以使用多个元注解来作用你自定义的注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @Inherited
    @Documented
    public @interface InjectView {
        @IdRes int value();
    }
    

    2.Java内置的注解

    @Deprecated

    • 定义:过时注解
    • 作用:标记已过时 & 被抛弃的元素(类、方法等)

    @Override

    • 定义:复写注解
    • 作用:标记该方法需要被子类复写

    @SuppressWarnings

    • 定义:阻止警告注解
    • 作用:标记的元素会阻止编译器发出警告提醒

    @SafeVarargs

    • 定义:参数安全类型注解

    Java 1.7 后引入

    • 作用:提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告

    • 具体使用

    // 以下是官方例子
    // 虽然编译阶段不报错,但运行时会抛出 ClassCastException 异常
    // 所以该注解只是作提示作用,但是实际上还是要开发者自己处理问题
    @SafeVarargs // Not actually safe!
        static void m(List<String>... stringLists) {
        Object[] array = stringLists;
        List<Integer> tmpList = Arrays.asList(42);
        array[0] = tmpList; // Semantically invalid, but compiles without warnings
        String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
    }
    

    @FunctionalInterface

    • 定义:函数式接口注解

    Java 1.8 后引入的新特性

    • 作用:表示该接口 = 函数式接口

    函数式接口 (Functional Interface) = 1个具有1个方法的普通接口

    • 具体使用
    // 多线程开发中常用的 Runnable 就是一个典型的函数式接口(被 @FunctionalInterface 注解)
    @FunctionalInterface
    public interface Runnable {
       
        public abstract void run();
    }
    
    <--额外:为什么要用函数式接口标记 -->
    // 原因:函数式接口很容易转换为 Lambda 表达式
    // 这是另外一个很大话题,此处不作过多讲解,感兴趣的同学可自行了解
    

    四、自定义注解

    Java中所有的注解,默认实现 Annotation 接口:

    package java.lang.annotation; 
    
        public interface Annotation { 
    
        boolean equals(Object obj); 
    
        int hashCode(); 
    
        String toString(); 
    
        Class<? extends Annotation> annotationType(); 
    
    }
    

    与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:

    public @interface Test{ }
    

    1.声明自定义注解:

    可以使用元注解来标记你的自定义注解的规则:

    @Retention(RetentionPolicy.RUNTIME)//注解保留在运行时
    @Target(ElementType.FIELD)// 允许在类与类属性上标记该注解
    public @interface InjectView {
        int value();
    }
    

    2.注解的属性

    注解的属性 = 成员变量

    注解只有成员变量,没有方法

    <-- 1. 定义 注解的属性 -->
    public @interface Test {
        // 注解@Test中有2个属性:age 和 name 
        int age();
        String name() default "zhang" ;
    
        // 说明:
          // 注解的属性以 “无形参的方法” 形式来声明
          // 方法名 = 属性名
          // 方法返回值 = 属性类型 = 8 种基本数据类型 + 类、接口、注解及对应数组类型
          // 用 default 关键值指定 属性的默认值,如上面的name的默认值 = ”zhang“
    }
    

    3.注解属性赋值

    注解的属性在使用时进行赋值

    注解属性的赋值方式 = 注解括号内以 “value=”xx” “ 形式;用 ”,“隔开多个属性

    备注:若注解只有一个属性,则赋值时”value“可以省略

    <-- 2. 赋值 注解的属性 -->
    // 注解Test 作用于A类
    // 在作用 / 使用时对注解属性进行赋值
    @Test(age=18,name="zhangsan")
    public class A {
      }
    

    4.注解的应用

    我们利用注解,在Activity中不使用findViewbyID方法,来实现动态加载Android View组件。

    步骤一:

    定义一个注解InjectView.java,用来标识View

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface InjectView {
        @IdRes int value();
    }
    

    步骤二:

    编写注解的处理规则:

    获取使用注解的Activity类,并获取该类下的使用了InjectView注解的成员变量,然后执行findViewbyID注入view。

    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.value();
                    View view = activity.findViewById(id);
                    //反射设置 属性的值
                    filed.setAccessible(true); //设置访问权限,允许操作private的属性
                    try {
                        //反射赋值
                        filed.set(activity,view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    步骤二:

    在MainActivity中声明需要用的组件TextView,用InjectView注解标识,在onCreate中调用injectView方法完成View的注入:

    public class MainActivity extends AppCompatActivity {
    
        @InjectView(R.id.tv)
        TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            InjectUtils.injectView(this);
            textView.setText("注解+反射注入View");
        }
    }
    

    运行效果:

    image-20201130230506922

    注意:该方式即为xUtils实现View绑定的方式,但是由于该注解作用于RUNTIME时期,在运行时期使用了反射将会降低程序运行的效率,所有目前View注入一般使用ButterKnife,ButterKnife在编译期间根据注解自动生成View注入的类,效率会好很多,但是xUtils是一个轻量级的,而且功能强大,我们可以根据项目实际需求选择。

    另外Kotlin语言已经实现了View的自动注入,在写代码的时候不需要大量的去写findViewbyID,所以使用Kotlin将不再需要View注入的代码。

    xUtils ViewInject源码:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ViewInject {
    
        int value();
    
        /* parent view id */
        int parentId() default 0;
    }
    
    
    

    注入过程:

    private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
    
            if (handlerType == null || IGNORED.contains(handlerType) || handlerType.getName().startsWith("androidx.")) {
                return;
            }
    
            // 从父类到子类递归
            injectObject(handler, handlerType.getSuperclass(), finder);
    
            // inject view
            Field[] fields = handlerType.getDeclaredFields();
            if (fields != null && fields.length > 0) {
                for (Field field : fields) {
    
                    Class<?> fieldType = field.getType();
                    if (
                        /* 不注入静态字段 */     Modifier.isStatic(field.getModifiers()) ||
                            /* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||
                            /* 不注入基本类型字段 */  fieldType.isPrimitive() ||
                            /* 不注入数组类型字段 */  fieldType.isArray()) {
                        continue;
                    }
    
                    ViewInject viewInject = field.getAnnotation(ViewInject.class);
                    if (viewInject != null) {
                        try {
                            View view = finder.findViewById(viewInject.value(), viewInject.parentId());
                            if (view != null) {
                                field.setAccessible(true);
                                field.set(handler, view);
                            } else {
                                throw new RuntimeException("Invalid @ViewInject for "
                                        + handlerType.getSimpleName() + "." + field.getName());
                            }
                        } catch (Throwable ex) {
                            LogUtil.e(ex.getMessage(), ex);
                        }
                    }
                }
            } // end inject view
    
            // inject event
            Method[] methods = handlerType.getDeclaredMethods();
            if (methods != null && methods.length > 0) {
                for (Method method : methods) {
    
                    if (Modifier.isStatic(method.getModifiers())
                            || !Modifier.isPrivate(method.getModifiers())) {
                        continue;
                    }
    
                    //检查当前方法是否是event注解的方法
                    Event event = method.getAnnotation(Event.class);
                    if (event != null) {
                        try {
                            // id参数
                            int[] values = event.value();
                            int[] parentIds = event.parentId();
                            int parentIdsLen = parentIds == null ? 0 : parentIds.length;
                            //循环所有id,生成ViewInfo并添加代理反射
                            for (int i = 0; i < values.length; i++) {
                                int value = values[i];
                                if (value > 0) {
                                    ViewInfo info = new ViewInfo();
                                    info.value = value;
                                    info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                                    method.setAccessible(true);
                                    EventListenerManager.addEventMethod(finder, info, event, handler, method);
                                }
                            }
                        } catch (Throwable ex) {
                            LogUtil.e(ex.getMessage(), ex);
                        }
                    }
                }
            } // end inject event
    
        }
    

    参考:https://www.jianshu.com/p/9f29fb37c840

    相关文章

      网友评论

          本文标题:Java注解

          本文链接:https://www.haomeiwen.com/subject/okhjwktx.html