java注解以及自定义运行时注解

前言:
Android中比较常用的框架使用了注解技术
ButterKnife,Dagger2通过编译时注解处理技术在编译时期通过apt生成代码完成注入
EventBus通过注解+反射的机制开发的组件间进行通讯的框架,
Retrofit通过注解+动态代理来搭建了一个框架,使我们只需要调用一个方法就能请求一个API
所以说如果想要去理解这些开源框架是如何实现的,那么注解技术是必不可少的需要我们去了解的.

通过如下几个小节:

java注解以及自定义运行时注解_第1张图片
Untitled Diagram(3).png

注解的概念

什么是注解

是java5提供了一种安全的类似注释的机制,她提供了一种安全的类似与注视的机制,用于为java程序提供元数据,为程序的元素添加(类,变量,方法)直接明了的说明.注解不会影响代码的执行.也可以理解为一种接口,程序可以通过反射或者apt等一些注解处理框架来对注解进行使用,程序通过注解对象来获取元数据.


总结来讲注解就是java提供的一种对程序中任何元素(类,方法,变量)和任何元数据关联的一种途径.

什么是元数据

元数据是用来描述数据的数据.通俗的一点来讲,元数据就是用来描述代码之间的关系,或者代码与资源之间的关系.
1.元数据是以标签的形式存放在java代码中
2.元数据描述的信息属于类型安全的
3.元数据需要编译编译器之外的工具而外的处理用来生成java代码

java以及android中常见的注解

@Override 用于覆盖超类提供的方法时使用,如果为覆盖方法,则编译器会给一个异常.

//正确覆盖父类的方法
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }

//方法名写错   错误覆盖父类的方法,编译器会给出错误异常.
  @Override
  protected void onXXX(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 }

@Deprecated 用于标注过时的类或者方法.与javadoc中的deprecated不同,他是编译器所能辨识的注解,而javadoc中的是javadoc所识别的注解用于表示,这个元素过期的原因,以及建议替代的元素

    /**
     * @deprecated Implement {@link #onStartCommand(Intent, int, int)} instead.
     */
    @Deprecated
    public void onStart(Intent intent, int startId) {
    }

@SuppressWarnning 用于忽略编译器检查到的警告

Android 中常见的注解

@NoneNull 检查不允许元素为null
@CallSuper 重写父类方法时必须调用父类中的方法.
@StringRes LayoutRes 用于标识资源的类型,比如需要接受一个字符资源的id就可以用@StringRes标注,这是如果传入的为图片id编译器给出警告
@WorkThread @UIThread 比如 可以制定方法在UI或工作线程,如果错误则给出警告.

那么我们如何根据自己的需求来自定义一个注解呢?

注解类:

在自定义注解时创建@interface表示创建的是一个注解类
在注解类中定义注解方法只能用public 或默认修饰 并且方法中不能有参数. 返回类型为注解参数类型,而方法名为注解参数名,通过defult来定义一个默认的注解参数的值.同时java提供了4种元注解来标识我们自定义注解的功能.

元注解:
是一种标注注解的注解
java提供了4种元注解来标识我们自定义注解的功能.

@Target 用于标注注解的使用范围 java提供了TYPE,METHOD,FIELD等可参数
TYPE:表示该注解能用于类,接口,枚举
METHOD:方法
FIELD:成员变量

@Retention 标注注解生命周期 也就是描述注解在什么范围生效分为三种参数:
Source:编译有效,只在源文件中有效,比如java提供的三个注解都是Source
Class:在编译时有效,注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RUNTIME:在jvm中有效,也就是运行时有效,只有RUNTIME标注过的注解才能通过反射机制获取到Annotations.

@Document 将注解包含在javadoc中

@Inherited 子类默认继承父类的注解

来看下自定义注解的简单使用方式,这里先定义3个运行时注解:

  // 适用类、接口(包括注解类型)或枚举
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassInfo {
    String value();
}
// 适用field属性,也包括enum常量
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldInfo {
    int[] value();
}
// 适用方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodInfo {
    String name() default "long";
    String data();
    int age() default 27;
}

这3个注解分别适用于不同的元素,并都带有不同的属性,在使用注解是需要设置这些属性值。

再定义一个测试类来使用这些注解:

/**
 * 测试运行时注解
 */
@ClassInfo("Test Class")
public class TestRuntimeAnnotation {

    @FieldInfo(value = {1, 2})
    public String fieldInfo = "FiledInfo";

    @FieldInfo(value = {10086})
    public int i = 100;

    @MethodInfo(name = "BlueBird", data = "Big")
    public static String getMethodInfo() {
        return TestRuntimeAnnotation.class. 

();
    }
}

使用还是很简单的,最后来看怎么在代码中获取注解信息:

/**
 * 测试运行时注解
 */
private void _testRuntimeAnnotation() {
    StringBuffer sb = new StringBuffer();
    Class cls = TestRuntimeAnnotation.class;
    Constructor[] constructors = cls.getConstructors();
    // 获取指定类型的注解
    sb.append("Class注解:").append("\n");
    ClassInfo classInfo = cls.getAnnotation(ClassInfo.class);
    if (classInfo != null) {
        sb.append(Modifier.toString(cls.getModifiers())).append(" ")
                .append(cls.getSimpleName()).append("\n");
        sb.append("注解值: ").append(classInfo.value()).append("\n\n");
    }

    sb.append("Field注解:").append("\n");
    Field[] fields = cls.getDeclaredFields();
    for (Field field : fields) {
        FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class);
        if (fieldInfo != null) {
            sb.append(Modifier.toString(field.getModifiers())).append(" ")
                    .append(field.getType().getSimpleName()).append(" ")
                    .append(field.getName()).append("\n");
            sb.append("注解值: ").append(Arrays.toString(fieldInfo.value())).append("\n\n");
        }
    }

    sb.append("Method注解:").append("\n");
    Method[] methods = cls.getDeclaredMethods();
    for (Method method : methods) {
        MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);
        if (methodInfo != null) {
            sb.append(Modifier.toString(method.getModifiers())).append(" ")
                    .append(method.getReturnType().getSimpleName()).append(" ")
                    .append(method.getName()).append("\n");
            sb.append("注解值: ").append("\n");
            sb.append("name: ").append(methodInfo.name()).append("\n");
            sb.append("data: ").append(methodInfo.data()).append("\n");
            sb.append("age: ").append(methodInfo.age()).append("\n");
        }
    }

Log.d(TAG,sb.toString());
}

运行结果如下图:

java注解以及自定义运行时注解_第2张图片
2017-08-10 18:09:01屏幕截图.png

这个自定义运行时注解是很简单的例子,有很多优秀的开源项目都有使用运行时注解来处理问题,有兴趣可以找一些来研究。因为涉及到反射,所以运行时注解的效率多少会受到影响,现在很多的开源项目使用的是编译时注解,关于编译时注解下一章来介绍。

你可能感兴趣的:(java注解以及自定义运行时注解)