除了在第三方库中,你项目中有自己定义注解使用吗

上一篇写动态代理的时候,拿Retrofit来举例子,提到了Retrofit在使用的时候通过注解来进行参数的配置,通过@Get()/@POST()/@Path()/@Query()...等注解进行参数声明,就可以调用OkHttp的时候发送正确的报文

在平时开发使用第三库的时候,使用的时候经常会遇到通过注解去声明参数的情况

  • 使用butterknife通过@BindView()注解就可以将控件的id和声明的控件成员变量进行绑定,省略了findViewById的的手动绑定过程
  • 使用dagger注入成员的变量的时候,通过@Inject注解就可以去获取@Module中通过@Provides声明提供的实例化对象
  • 使用webview和js代码交互的时候,通过@JavascriptInterface注解可以声明web页面可以使用的方法

还有很多在使用第三方库的时候使用注解的情况,除了以上在项目开发中经常用到的,我相信跟我一样在网上搜索过内存优化的朋友,会看到过关于在使用枚举类约束变量参数取值的情况使用@IntDef注解去替代,因为枚举值都会封箱成一个对象,对象就会有额外的内存开销

//使用枚举,枚举值都是一个对象
public enum Type {
    TYPE_1, TYPE_2
}
//枚举的使用
public class Test {
    private static Type mType;

    //参数类型就声明的枚举类型
    public static void setType(Type type) {
        mType = type;
    }
    public void test() {
        //方法调用时参数只能是枚举中声明的类型
        setType(Type. TYPE_1);
    }
}

如果我们使用@IntDef,就需要自己声明一个注解,并且用@IntDef对其注解

public class Test {
    private static int mCurrentType;

    public static final int TYPE_1 = 1;
    public static final int TYPE_2 = 1<<1;

    //通过参数的注解,从而来限定传入参数的值进行校验
    public static void setType(@TypeAnnotation int type) {
        mCurrentType = type;
    }

    public void test() {
        setType(TYPE_1);//可选参数就是在注解中传入的值
        setType(1); //直接传参编译器会有错误提示
    }
}

//声明的参数注解
@IntDef(value = {Test.TYPE_1, Test.TYPE_2}) //通过IntDef元注解,传入对应的int值
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface TypeAnnotation {  //注解
}

相比较两个静态int常量值的内存,使用枚举去进行参数限定会占用更多的内存

什么是注解

举例了很多应用场景,第三方库的封装也经常使用注解,注解应该是一个非常有用的东西,但是实际上它在项目运行中又没有什么实际的作用,不参于运算,不改变执行逻辑,只是在对应的代码出打上标记,开发者在使用场景中,通过拿到注解和注解的值,去添加相应的处理逻辑

注解的使用

注解在使用上也很简单,类似于写一个类通过class关键字进行声明,注解通过@interface进行声明

public @interface BitBaba {}
Target元注解

然后还需要给注解需要标记的位置通过@Target进行限定,能作用在注解上的注解叫做元注解,jdk中也默认提供了一些元注解,@Target就是其中一个

@Target({ElementType.PARAMETER})//可以传入多个值,作用域确定后该注解就限定在对应类型的位置使用
public @interface BitBaba {}

//Target可以的取值类型
public enum ElementType {
    TYPE,//类、接口和枚举
    FIELD,//字段
    METHOD,//方法
    PARAMETER,//参数
    CONSTRUCTOR,//构造函数
    LOCAL_VARIABLE,//本地变量
    ANNOTATION_TYPE,//注解类型
    PACKAGE,//包
    TYPE_PARAMETER,//类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
    TYPE_USE//JavaSE8引进,此类型包括类型声明和类型参数声明,是为了方便设计者进行类型检查
}
Retention元注解

声明了作用域,规定了注解的标记位置,还需要一个通过另一个元注解@Retention确定注解的保留阶段

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)//声明注解的保留阶段
public @interface BitBaba {}

public enum RetentionPolicy {
    SOURCE,//保留在源码阶段,被编译期忽略
    CLASS,//编译时由编译器保留,但是jvm虚拟机会忽略
    RUNTIME//在jvm保留,在运行时环境中可以使用它
}

@Retention中的三个值是包含关系,SOURCE<CLASS<RUNTIME,选择了RUNTIME阶段,那么在源码阶段和编译时都会存在

注解参数

既然是一个标记的作用,那么为了使用场景,当然是有传值的需求,就比如butterknife中需要通过注解传入控件的id

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)//声明注解的保留阶段
public @interface BitBaba {
    String value();   //没有默认值
    int age() default 30; //给默认值
}

//有默认值的参数在使用注解是可以默认不传值
@BitBaba("实际") //如果只是value需要传值的情况下,可以隐式的传参,不用写对应的参数名:
@BitBaba(value ="梦想",age=18)
Object o;
注解应用

上面的部分我们已经声明一个我们自己的注解类,接下来就总结一下这个注解到底可以怎么用
通过@Retention元注解,可以制定注解的保留时期是不一样的,那么自然相应的应用场景也不一样,分开来说明

SOURCE源码级别的注解
  • IDE语法检查
    源码级别的注解,由于在javac编译后的.class文件中不会保留,所以这个注解的应用场景就在javac编译完成之前,比如上面做枚举优化是提到的IntDef注解,就是一个源码级别的注解
@Retention(SOURCE)//源码级别
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    int[] value() default {};
    boolean flag() default false;
    boolean open() default false;
}

这类注解可以用来给IDE进行语法检验,限定参数的传值范围就是其中的一个应用场景

  • APT
    除了语法检查,源码级别的注解的一个常见的应用场景就是做APT(Anotation Processor Tools),也就是注解处理器,注解处理器是javac的一个工具,在执行javac编译的时候,会扫描注册的注解处理器,然后就可以在标记的位置做额外的逻辑,常见的可以自己手写类文件,比如butterknife就是通过APT技术,把注解的值获取手,统一封装了findviewbyid的逻辑
    第三方库在依赖的时候,需要使用annotationProcessor去依赖的,就是使用了APT技术,这个的具体应用后面找文章再写了
CLASS级别的注解

这个级别的注解,会保留在编译后的class文件中,但是虚拟机运行的时候会忽略掉注解编译后的那部分class文件,就是无法在运行时通过反射去获取注解,这个级别的注解的应用场景就是针对字节码进行操作,应用在一些AOP的场景中,比如实现统一的登录逻辑判断,或者权限申请这些
对于字节码的处理,纯手写还是很困难的,借助一些第三方框架,比如AspectJ,就可以对字节码进行更符合语言特性的插入修改,字节码的操作也是群里大佬经常讨论的热修复技术,里面有提到字节码插桩,这部分我没在项目中应用过,后续等我在网上学习一下后写一篇总结

RUNTIME级别的注解

这种情况下注解会保留到运行期,那么我们就可以在运行期通过反射可以获取到注解的信息
在比较早的butterknife版本中,就是通过运行时反射,比如在Activityoncreate()方法中,调用butterknife的注入方法,这个方法就是利用反射获取声明的控件注解,获取到对应的控件id,然后调用findviewbyid进行实例化赋值
找不到源码了,自己弄一个简单的例子

//声明一个运行期的注解,作用在成员变量上
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}

public class ButterKnife {
    public static void bindViews(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        // 获取类的所有域变量
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            // 获取BindView注解
            BindView bindAnno = field.getAnnotation(BindView.class);
            if (bindAnno != null) {
                // 存在有被@BindView注解标记的属性
                int id = bindAnno.value();
                try {
                    // 获取findViewById的Method实例
                    Method bindMethod = clazz.getMethod("findViewById", int.class);
                    // 调用bindMethod来获得View
                    Object view = bindMethod.invoke(activity, id);
                    // 私有变量给与赋值权限
                    field.setAccessible(true);
                    // 将结果赋值给field
                    field.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                } 
            }
        }
    }
}

//最后再对应需要绑定的Activity页面的onCreate后面,调用对用的
ButterKnife.bindViews(this); //在界面需要绑定的地方调用这个方法就行

类似的运行期注解的使用方法如上,主要还是需要结合反射的技术去获取注解和注解的值,使用反射当然比直接在代码中进行绑定性能是有损耗的,但是这样可以将将重复的绑定逻辑进行优化
现在版本的ButterKnife已经使用了APT技术,通过生成代码的方式,优化了反射带来的性能问题

总结

除了使用第三方库,在实际的项目开发中确实不需要使用注解也能写好一个程序,但是不用不代表不需要了解,不了解的话就没法在群里吹逼,大佬们的发言也感觉自己更加的菜狗,要是面试被问到也会一脸懵逼,而且看第三方库也手脚搞不赢
菜如我没去网上学习反射这部分连gradle依赖为什么需要使用“annotationProcessor”而不是用“implementation”都不清楚
写文章也是为了自己学习,还是希望有路过的大佬能指点一下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容

  • 夯实 Java 基础 - 注解 不知道大家有没有一种感觉,当你想要了解某个知识点的时候,就会发现好多技术类 APP...
    醒着的码者阅读 1,062评论 4 7
  • 内容: 注解的定义 注解的语法 源码级别的注解的使用 运行时注解的使用 编译时注解的使用 Android 预置的注...
    凯玲之恋阅读 495评论 0 0
  • 注解 注解声明 声明一个注解类型 Java中所有的注解,默认是实现Annotation接口: 注解的声明使用@in...
    木水Code阅读 356评论 0 1
  • 注解的作用或意义 注解本身没有任何意义,单独的注解就是一种注释。需要结合其他如反射、插桩等技术才有意义。Java注...
    echoSuny阅读 242评论 0 0
  • 注解的定义 Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。 注...
    暮暮频顾惜阅读 418评论 0 0