Java学习之注解Annotation实现原理
前言:
最近学习了 EventBus、BufferKinfe、GreenDao、Retrofit 等优秀开源框架,它们新版本无一另外的都使用到了注解的方式,我们使用在使用的时候也尝到不少好处,基于这种想法我觉得有必要对注解有个更深刻的认识,今天中午把公司的项目搞完了,晚上加个班学习总结一下 Java 的注解。
什么是注解?
对于很多初次接触的开发者来说应该都有这个疑问?Annontation 是 Java5 开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation 像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
注解的用处:
1、生成文档。这是最常见的,也是 java 最早提供的注解。常用的有 @param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比如 Dagger 2 依赖注入,未来 java 开发,将大量注解配置,具有很大用处;
3、在编译时进行格式检查。如 @override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
元注解:
java.lang.annotation 提供了四种元注解,专门注解其他的注解:
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
1.)@Retention– 定义该注解的生命周期
- RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings 都属于这类注解。
- RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
- RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
举例:bufferKnife 8.0 中 @BindView 生命周期为 CLASS
@Retention(CLASS) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value();}
2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的 ElementType 参数包括
- ElementType.CONSTRUCTOR: 用于描述构造器
- ElementType.FIELD: 成员变量、对象、属性(包括 enum 实例)
- ElementType.LOCAL_VARIABLE: 用于描述局部变量
- ElementType.METHOD: 用于描述方法
- ElementType.PACKAGE: 用于描述包
- ElementType.PARAMETER: 用于描述参数
- ElementType.TYPE: 用于描述类、接口 (包括注解类型) 或 enum 声明
举例 Retrofit 2 中 @Field 作用域为参数
@Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface Field {String value();/** Specifies whether the {@linkplain #value()name} and value are already URL encoded. */
boolean encoded() default false;
}
3.)@Documented–一个简单的 Annotations 标记注解,表示是否将注解信息添加在 java 文档中。
4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。
常见标准的 Annotation:
1.)Override
java.lang.Override 是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java 编译器将以一个编译错误来警示。
2.)Deprecated
Deprecated 也是一种标记类型注解。当一个类型或者类型成员使用 @Deprecated 修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为 @Deprecated,但编译器仍然要报警。
3.)SuppressWarnings
SuppressWarning 不是一个标记类型注解。它有一个类型为 String[] 的成员,这个成员的值为被禁止的警告名。对于 javac 编译器来讲,被 -Xlint 选项有效的警告名也同样对 @SuppressWarings 有效,同时编译器忽略掉无法识别的警告名。
@SuppressWarnings("unchecked")
自定义注解:
这里模拟一个满足网络请求接口,以及如何获取接口的注解函数,参数执行请求。
1.)定义注解:
@ReqType 请求类型
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface ReqType {</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 请求方式枚举 * </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">enum</span><span style="color: rgba(0, 0, 0, 1)"> ReqTypeEnum{ GET,POST,DELETE,PUT}; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 请求方式 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> ReqTypeEnum reqType() </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> ReqTypeEnum.POST;
}
@ReqUrl 请求地址
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface ReqUrl {String reqUrl() default ""; }
@ReqParam 请求参数
@Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface ReqParam {String value() default ""; }
从上面可以看出注解参数的可支持数据类型有如下:
1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String 类型
3.Class 类型
4.enum 类型
5.Annotation 类型
6. 以上所有类型的数组
而且不难发现 @interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。
2.)如何使用自定义注解
public interface IReqApi {}@ReqType(reqType </span>= ReqType.ReqTypeEnum.POST)<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">声明采用post请求</span> @ReqUrl(reqUrl = "www.xxx.com/openApi/login")<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">请求Url地址</span> String login(@ReqParam("userId") String userId, @ReqParam("pwd") String pwd);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">参数用户名 密码</span>
3.)如何获取注解参数
这里强调一下,Annotation 是被动的元数据,永远不会有主动行为,但凡 Annotation 起作用的场合都是有一个执行机制 / 调用者通过反射获得了这个元数据然后根据它采取行动。
通过反射机制获取函数注解信息
Method[] declaredMethods = IReqApi.class.getDeclaredMethods(); for (Method method : declaredMethods) {Annotation[] methodAnnotations = method.getAnnotations(); Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();}
也可以获取指定的注解
ReqType reqType =method.getAnnotation(ReqType.class);
4.)具体实现注解接口调用
这里采用 Java 动态代理机制来实现,将定义接口与实现分离开,这个后期有时间再做总结。
private void testApi() { IReqApi api = create(IReqApi.class); api.login("whoislcj", "123456");}</span><span style="color: rgba(0, 0, 255, 1)">public</span> <T> T create(<span style="color: rgba(0, 0, 255, 1)">final</span> Class<T><span style="color: rgba(0, 0, 0, 1)"> service) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> (T) Proxy.newProxyInstance(service.getClassLoader(), <span style="color: rgba(0, 0, 255, 1)">new</span> Class<?><span style="color: rgba(0, 0, 0, 1)">[]{service}, </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InvocationHandler() { @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Object invoke(Object proxy, Method method, Object... args) </span><span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable {</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Annotation[] methodAnnotations = method.getAnnotations();</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">拿到函数注解数组</span> ReqType reqType = method.getAnnotation(ReqType.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); Log.e(TAG, </span>"IReqApi---reqType->" + (reqType.reqType() == ReqType.ReqTypeEnum.POST ? "POST" : "OTHER"<span style="color: rgba(0, 0, 0, 1)">)); ReqUrl reqUrl </span>= method.getAnnotation(ReqUrl.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); Log.e(TAG, </span>"IReqApi---reqUrl->" +<span style="color: rgba(0, 0, 0, 1)"> reqUrl.reqUrl()); Type[] parameterTypes </span>=<span style="color: rgba(0, 0, 0, 1)"> method.getGenericParameterTypes(); Annotation[][] parameterAnnotationsArray </span>= method.getParameterAnnotations();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">拿到参数注解</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i < parameterAnnotationsArray.length; i++<span style="color: rgba(0, 0, 0, 1)">) { Annotation[] annotations </span>=<span style="color: rgba(0, 0, 0, 1)"> parameterAnnotationsArray[i]; </span><span style="color: rgba(0, 0, 255, 1)">if</span> (annotations != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { ReqParam reqParam </span>= (ReqParam) annotations[0<span style="color: rgba(0, 0, 0, 1)">]; Log.e(TAG, </span>"reqParam---reqParam->" + reqParam.value() + "==" +<span style="color: rgba(0, 0, 0, 1)"> args[i]); } } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">下面就可以执行相应的网络请求获取结果 返回结果</span> String result = "";<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里模拟一个结果</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result; } }); }</span></pre>
打印结果:
以上通过注解定义参数,通过动态代理方式执行函数,模拟了最基本的 Retrofit 2 中网络实现原理。