在注解中,一般都会包含一些元素表示某些值,并且可以为这些元素设置默认值,没有元素的注解也称为标记注解(marker annotation)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD})
public @interface LogClassMessage {
public int id () default -1;
public String message() default “”;
}
注:虽然上面的 id 和 message 定义和接口的方法定义很类似,但是在注解中将 id 和 message 称为:int元素id , String 元素 message。而且注解元素的类型是有限制的,并不是任何类型都可以,主要包括:基本数据类型(理论上是没有基本类型的包装类型的,但是由于自动封装箱,所以也不会报错)、String 类型、enum 类型、Class 类型、Annotation 类型、以及以上类型的数组,(小伙伴们没有等字哦,说明目前注解的元素类型只支持上面列出的这几种),否则编译器便会提示错误。
invalid type ‘void ’ for annotation member //例如注解类型为void的错误信息
对于默认值限制 ,Bruce Eckel (美) 在其书中是这样描述的:编译器对元素的默认值有些过分挑剔,首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供注解的值。其次,对于非基本类型的元素,无论在源代码声明中,或者在注解接口中定义默认值时,都不能以 null 作为其值。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为在每个注解的声明中,所有元素都存在,并且都具有相应的值。为了绕开这个约束,我们只能自己定义一些特殊的值,例如空字符串或者负数,以此表示某个元素的不存在,这算得上是一个习惯用法。
3. 参考下系统的标准注解
怎么说呢,接触一种新的知识的途径有很多,可能每一种的结果都是大同小异的,都能让你学到东西,但是实现的方式、实现过程中的规范、方法和思路却并不一定是最佳的,本人选择的是借鉴源码,效果还不错,在这里推荐给大家。
上文讲到的是注解的基本语法,那么系统是怎么用的呢?首先让我们来看一下使用频率最高的 @Override :
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public![](https://www.hualigs.cn/image/61dba891ed8ee.jpg) @interface Override { }
-
1
-
2
-
3
-
4
〔1〕首先系统定义一个没有元素的标记注解 Override ,随后使用元注解 @Target 指明 Override 注解只能应用于方法之上(你可以细想想,是不是在我们实际使用这个注解的时候,只能是重写的方法,没有见过重写类或者字段的吧),使用注解 @Retention 表示当前注解只能存在源代码中,并不会出现在编译之后的 class 文件之中。
@Override protected void onResume() { super.onResume(); }
-
1
-
2
-
3
-
4
〔2〕在活动 activity 中我们可以重写 onResume() 方法,添加注解 @override 之后编译器便会去检查父类中是否存在相同方法,如果不存在便会报错。
〔3〕也许到这里你会感到很疑惑,注解到底是怎么工作的,怎么系统这样定义一个注解 Override 它就能工作了?黑魔法吗,擦擦,完成看不到实现过程嘛(泪流满面),经过查阅了一些资料(非权威)了解到,其实处理过程都编写在了编译器里面,也就是说编译器已经给我们写好了处理方法,当编译器进行检查的时候就会调用相应的处理方法。
4. 注解处理器
介绍之前,先引用 Jeremy Meyer 的一段话:如果没有用来读取注解的工具,那么注解也不会比注释更有用。使用注解的过程中,很重要的一个部分就是创建与使用注解处理器。Java SE5 扩展了反射机制的API,以帮助程序员构造这类工具。同时,它还提供了一个外部工具 apt帮助程序员解析带有注解的 java 源代码。
根据上面描述我们可以知道,注解处理器并不是一个特定格式,并不是只有继承了 AbstractProcessor 这个抽象类才叫注解处理器,凡是根据相关API 来读取注解的类或者方法都可以称为注解处理器。
1、最简单的注解处理器莫过于,直接使用反射机制的 getDeclaredMethods 方法获取类上所有方法(字段原理是一样的),再通过调用 getAnnotation 获取每个方法上的特定注解,有了注解便可以获取注解之上的元素值,方法如下:
public void getAnnoUtil(Class<?> cl) { for(Method m : cl.getDeclaredMethods()) { LogClassMessage logClassMessage = m.getAnnotation(LogClassMessage .class); if(logClassMessage != null) { int id = logClassMessage.id(); String method = logClassMessage.message(); } } }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
2、由于反射对性能会有一定的损耗,所以上述类型的注解处理器并不占主流,现在使用最多的还是 AbstractProcessor 自定义注解处理器,因为后者并不需要通过反射实现,效率和直接调用普通方法没有区别,这也是为什么编译期注解比运行时注解更受欢迎,但是并不是说为了性能运行期注解就不能用了,只能说不能滥用,要在性能方面给予考虑。目前主要的用到运行期注解的框架差不多都有缓存机制,只有在第一次使用时通过反射机制,当再次使用时直接从缓存中取出。好了,说着说着就跑题,我们还是来聊一下这个 AbstractProcessor 类吧,到底有何魅力让这么多人为她沉迷,方法如下(注意得在 java Moudle 下哦,android project 没有提供相关的包):
/** * Created by zmj on 2017/6/19. */ public class MyFirstProcessor extends AbstractProcessor { /** * 做一些初始化工作,注释处理工具框架调用了这个方法, * 当我们使用这个方法的时候会给我们传递一个 ProcessingEnvironment * 类型的实参。 * 如果在同一个对象多次调用此方法,则抛出IllegalStateException异常 * @param processingEnvironment 这个参数里面包含了很多工具方法 */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { /** * 返回用来在元素上进行操作的某些工具方法的实现 */