Java注解之运行时注解

栏目: Java · 发布时间: 4年前

内容简介:Java注解在JDK1.5引入的一种技术,配合反射可以在运行时处理注解,配合apt tool可以在编译期处理注解。在JDK1.6的时候,apt tool被整合到了javac中。注解是一种元数据(metadata),元数据就是用来描述数据的数据,html标签就是一种元数据。实际的数据的标签里面的文本,color和size都是描述文本的属性。注解本身只起到一个标记和传值的作用,有没有注解都不影响程序的运行,注解的作用取决于对于注解元素的处理。Java本身内置了一些注解,例如最常见的@Override,标识一个方
In the Java computer programming language, an annotation is a form of syntactic metadata that can be added to Java source code

Java注解在JDK1.5引入的一种技术,配合反射可以在运行时处理注解,配合apt tool可以在编译期处理注解。在JDK1.6的时候,apt tool被整合到了javac中。注解是一种元数据(metadata),元数据就是用来描述数据的数据,html标签就是一种元数据。

<font color="#000" size="10">注解</font>
复制代码

实际的数据的标签里面的文本,color和size都是描述文本的属性。

作用

注解本身只起到一个标记和传值的作用,有没有注解都不影响程序的运行,注解的作用取决于对于注解元素的处理。Java本身内置了一些注解,例如最常见的@Override,标识一个方法是重写的父类方法,@Deprecated标识一个方法过时了

入门

定义

注解使用@interface关键字来定义。

public @interface MyAnnotation {
}

复制代码

元注解

仅仅定义一个注解还不能够使用,还需要一些元注解的修饰,所谓元注解,就是注解的注解。Java中的元注解包括@Retention,@Target,@Documented,@Inherited,@Repeatable五种。

  • @Retention用于描述注解的生命周期,表示注解在什么范围内有效。它有三个取值
类型 作用
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译的时候这种注解会被丢弃,@Override和@SuppressWarnings都属于源码注解,这种注解是是给IDE的做代码检查使用的
RetentionPolicy.CLASS 注解会在编译时期保留,但是当 Java 虚拟机加载class文件的时候就会被丢弃,这个也是@Retention的默认值。@Deprecated和@NonNull属于编译期注解
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,在程序中可以通过反射获取到
  • @Target表示注解作用对象的类型,有如下取值
类型 作用
ElementType.TYPE 类,接口,枚举类型,对应图中的2,11,12
ElementType.FIELD 类属性,对应图中的4,5
ElementType.METHOD 方法类型,对应图中的10
ElementType.PARAMETER 参数类型,对应图中的7,8
ElementType.CONSTRUCTOR 构造函数类型,对应图中的6
ElementType.LOCAL_VARIABLE 本地变量类型,对应图中的9
ElementType.ANNOTATION_TYPE 注解类型,@Target和@Retention这种元注解都是这种类型
ElementType.PACKAGE 包类型,只能用于package-info.java文件中,位置对应图中的1,但是不能用于普通java文件
ElementType.TYPE_PARAMETER 1.8后才支持,泛型类型,对应图中的3
ElementType.TYPE_USE 1.8后才支持,除了PACKAGE外的所有类型
Java注解之运行时注解
  • @Documented元注解的作用是能够将注解中的元素包含到Javadoc中

  • @Inherited是继承的意思,但是并不是注解可以继承,而是如果一个注解被该元注解标注的话,然后用这个注解标注了类A,那么它的子类B可以继承这个注解

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MyAnnotation {
    }
    
    @MyAnnotation
    public class Parent {
    }
    
    // Child本身并没有任何注解,但是由于@MyAnnotation被@Inherited标记了,所以Child可以从父类上继承@MyAnnotation注解。
    public class Child extends Parent{
    }
    复制代码
  • @Repeatable是JDK1.8才加入的一个元注解,这个的作用就是允许同一个注解在一个类型上标注多次。例如我们要做一个@Filter注解来过滤一些字符串,在JDK1.8之前可以这样做

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Filter {
        String[] value();
    }
    
    public interface StringFilter{
        @Filter({"111","222"})
        public void doFilter();
    }
    复制代码

    但是在JDK1.8之后可以使用@Repeatable

    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Filters.class)
    public @interface Filter {
        String value();
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @interface Filters {
        Filter[] value();
    }
    
    public interface StringFilter{
        @Filter("111")
        @Filter("222")
        public void doFilter();
    }
    复制代码

属性

注解本身只起到标记的作用,如果想要给标记的对象传递数据,还需要给注解定义一些属性,注解的成员变量在注解中以“无参方法”形式定义,方法名就是就是该成员变量的名字,返回值就是该成员变量的类型。

@Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        String name();
        int age();
    }
复制代码

上面代码给MyAnnotation注解添加了name和age属性,在使用的时候需要对属性进行赋值,多个属性用逗号隔开。

@MyAnnotation(name = "hao",age = 22)
    public Person person;
复制代码

注解的属性还支持设置默认值

@Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        String name() default "aaa";
        int age();
    }
复制代码

有默认值的属性在使用的时候可以选填

@MyAnnotation(age = 22)
    public Person person;
复制代码

还有一种情况是当注解只有一个属性且这个属性的名字为value时,则在使用的时候可以将属性名直接省略。

@Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        String value();
    }

    @MyAnnotation("1111")
    public String name;
复制代码

运行时注解

运行时注解就是在程序运行的时候通过反射获取到注解然后做处理。这里需要介绍一下一个接口 AnnotatedElement

Java注解之运行时注解

从上面的类图我们可以看出,反射可以获取到的元素都继承自这个接口,然后通过这个接口的getAnnotation方法就可以获取到标注在该元素上的注解。 运行时注解的应用场景很多,比如EventBus,通过运行时注解和反射实现对事件的处理;著名网络框架Retrofit2也是用运行时注解加动态代理实现非常的简洁的restful api接口。

实战

下面我们自己动手一步一步的实现一个基于运行时注解的ButterKnife。

  1. 定义注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface BindViewRuntime {
        int value() default -1;
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface OnClickRuntime {
        int value() default -1;
    }
    复制代码
  2. 定义 工具 类InjectionRuntime

    public class InjectionRuntime {
         public void inject(Activity activity) {
             inject(activity, activity.getWindow().getDecorView());
         }
    
         public void inject(Object target, View view) {
             injectView(target, view);
             injectListener(target, view);
         }
    
         private void injectView(Object target, View rootView) {
    
         }
    
         private void injectListener(Object target, View rootView) {
    
         }
     }
    复制代码
  3. 实现injectView方法

    private void injectView(Object target, View rootView) {
         // 获取类中的所有成员变量
         Field[] fields = target.getClass().getDeclaredFields();
         if (fields.length == 0) {
             return;
         }
         // 遍历属性找到被BindViewRuntime注解的字段,然后通过反射赋值
         for (Field field : fields) {
             BindViewRuntime annotation = field.getAnnotation(BindViewRuntime.class);
             if (annotation != null) {
                 int viewId = annotation.value();
                 if (viewId == -1) {
                     throw new IllegalArgumentException("-1 is not a validate viewId");
                 }
                 try {
                     field.setAccessible(true);
                     field.set(target, rootView.findViewById(viewId));
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }
     }
    复制代码
  4. 实现injectListener方法,这个实现稍微有点复杂,用了两层反射才能注入成功

    private void injectListener(Object target, View rootView) {
         // 获取类中的所有方法
         Method[] methods = target.getClass().getDeclaredMethods();
         if (methods.length == 0) {
             return;
         }
         // 遍历找出被OnClickRuntime注解的方法
         for (Method method : methods) {
             OnClickRuntime annotation = method.getAnnotation(OnClickRuntime.class);
             if (annotation != null) {
                 int viewId = annotation.value();
                 if (viewId == -1) {
                     throw new IllegalArgumentException("-1 is not a validate viewId");
                 }
                 View view = rootView.findViewById(viewId);
                 try {
                     // 反射获取View中的setOnClickListener方法,并赋值一个匿名的OnClickListener
                     Method viewMethod = view.getClass().getMethod("setOnClickListener", View.OnClickListener.class);
                     viewMethod.setAccessible(true);
                     viewMethod.invoke(view, (View.OnClickListener) v -> {
                         try {
                             // 执行被OnClickRuntime注解的方法
                             method.setAccessible(true);
                             method.invoke(target);
                         } catch (IllegalAccessException e) {
                             e.printStackTrace();
                         } catch (InvocationTargetException e) {
                             e.printStackTrace();
                         }
                     });
                 } catch (NoSuchMethodException e) {
                     e.printStackTrace();
                 } catch (IllegalAccessException e) {
                     e.printStackTrace();
                 } catch (InvocationTargetException e) {
                     e.printStackTrace();
                 }
             }
         }
     }
    复制代码
  5. 为了方便使用,我们可以新建一个Activity基类

    JK大神的Butterknife是基于编译期注解,使用时需要在每个页面调用bind和unbind方法,但是我们基于运行时注解的实现是不需要那么麻烦的,我们可以把查找id和赋值view的操作都放到基类中。

    public abstract class BaseActivity extends FragmentActivity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutId());
            new InjectionRuntime().inject(this);
            initView();
        }
    
        protected abstract int getLayoutId();
    
        protected abstract void initView();
    }
    复制代码
  6. 在BaseActivity的子类中使用BindViewRuntime

    public class AnnotationTestActivity extends BaseActivity {
        @BindViewRuntime(R.id.runtime_add_btn)
        private TextView runtimeAddBtn;
        @BindViewRuntime(R.id.runtime_sum_tv)
        private TextView runtimeSumTv;
    
        @Override
        protected int getLayoutId() {
            return R.layout.activity_annotation_test;
        }
    
        @Override
        protected void initView() {
        }
    
        @OnClickRuntime(R.id.runtime_add_btn)
        void runTimeAdd() {
            String text = runtimeSumTv.getText().toString();
            runtimeSumTv.setText(String.valueOf(Integer.parseInt(text) + 1));
        }
    }
    复制代码
  7. 运行效果

    Java注解之运行时注解

这样一个简单的ButterKnife就完成了,与编译期注解的实现相比,这种方式在实现上更加简单,使用更加方便,不用每个页面bind和unbind,对属性名的访问权限也没有要求。唯一的缺点也是所有运行时注解相对编译期注解最大缺点就是性能。

demo下载地址


以上所述就是小编给大家介绍的《Java注解之运行时注解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms Unlocked

Algorithms Unlocked

Thomas H. Cormen / The MIT Press / 2013-3-1 / USD 25.00

Have you ever wondered how your GPS can find the fastest way to your destination, selecting one route from seemingly countless possibilities in mere seconds? How your credit card account number is pro......一起来看看 《Algorithms Unlocked》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码