内容简介:最近我写了一篇关于组件化的开源框架源码分析的文章(传送门在下面儿)。那么现在组件化小有名气的JIMU框架,也是我下一个要给大家分享的源码分析文章。但因为其中涉及到了很多“
最近我写了一篇关于组件化的开源框架源码分析的文章(传送门在下面儿)。那么现在组件化小有名气的JIMU框架,也是我下一个要给大家分享的源码分析文章。但因为其中涉及到了很多 Java Annotation
相关的知识。所以不得不在这里,先安利一下本篇,这也是本篇的由来。
优秀框架源码分析系列(一)让解耦更轻松!多进程组件化框架-ModularizationArchitecture
“ 注解 ”,在 Java 世界里随处可见,但通常情况下,多数人对其是视而不见的。但当我们设计SDK,设计基础库的时候,运用注解,可以起到 简化配置 的作用。熟悉 ButterKnife
的朋友都知道,它就是通过注解来在编译期间,增加Java代码来实现的。如果你还不知道它是如何实现的,那么相信你食用完本篇和下一篇以后,就会明白这一切了。
食用路线
学习新知识的时候,要掌握正确的进食方法,脑子里必须先对知识结构有预期,学习完之后再回顾结构,根据结构记住知识。本篇将按照导图的结构,来进行讲解。
扎实打基础
基础知识-四大元注解
元注解,就是用来修饰注解的注解。
@Target(value=ElementType)
@Target
被用来指明此Annotation 所修饰的对象范围 (即:被描述的注解可以用在什么地方)。Java中,注解( Annotation
)可被用于以下位置 :
- package、types(类、接口、枚举、Annotation类型)
- 类型成员(方法、构造方法、成员变量、枚举值)
- 方法参数和本地变量(如循环变量、catch参数)
注解的使用范围,通过 Target
的取值来指定。指定好以后, @Target
修饰的元素一定是与其取的 value
相匹配的,否则编译会报错。
value
取值( ElementType
)常见的有:
ElementType | 含义 |
---|---|
CONSTRUCTOR | 用于描述构造器 |
FIELD | 用于描述域(包括enum常量) |
LOCAL_VARIABLE | 用于描述局部变量 |
METHOD | 用于描述方法 |
PACKAGE | 用于描述包 |
PARAMETER | 用于描述参数 |
TYPE | 用于描述类、接口(包括注解类型) 或enum声明 |
注:PACKAGE,它并不是使用在一般的类中,而是用在固定的文件package-info.java中。这里需要强调命名一定是“package-info”
这里需要特殊说明的是,在以前的Java版本中,开发者只能将注解(Annotation)写在声明中。但从Java 8开始,注解可以写在使用类型的任何地方,例如声明、泛型和强制类型转换等语句:
@Encrypted String str; List<@NonNull String> strs; test = (@Immutable Test) tmpTest; 复制代码
针对这个拓展,JAVA8对原有的@Target的取值做了扩充,引入了新的 类型(TYPE)
注解,即 ElmentType
增加了:
ElementType | 含义 |
---|---|
TYPE_PARAMETER | 表示注解可以用在类型变量的声明语句中(如 class Test {...}) |
TYPE_USE | 表示注解可以用在使用类型的任何语句中(如声明语句、泛型和强转) |
关于 类型 的解释参考上文。
@Retention(value=RetentionPolicy)
@Retention
,翻译为 保留 ,指示了一个注解被保留的时间期限,一个被其修饰的注解会被保留到其 value
指定三个阶段的其中之一,如果注解类型声明中不存在 Retention
注解,则保留策略默认为 CLASS
:
RetentionPolicy | 含义 |
---|---|
RetentionPolicy.SOURCE | 只在源代码级别保留,编译时就会被忽略 |
RetentionPolicy.CLASS | 在编译时被保留,在class文件中存在,但JVM将会忽略 |
RetentionPolicy.RUNTIME | 被JVM保留,所以他能在运行时被JVM或其他使用反射机制的代码所读取和使用 |
@Documented
被@Documented修饰的注解,用来表示这个被修饰注解应该被 javadoc工具记录。默认情况下,javadoc是不包括注解的。但如果声明注解时指定了@Documented,则它会被javadoc之类的 工具 处理,所以注解类型信息也会被包括在生成的文档中。
@Inherited
如果一个用来修饰class的注解,希望被这个class的sub-class继承,则可以对这个注解使用@Inherited修饰。上面这句话强调了以下两点:
- 注解的可继承性。当自定义的注解希望被继承时,就要使用@Inherited修饰
- @Inherited只在修饰class时有效,修饰其他类型时无效
自定义注解如何写-Java Annotation的语法
自定义注解,通过@符号和interface关键字来定义。类似于class的写法,例如:
package com.xm.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Target(ElementType.METHOD) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface TestAnno{ String name(); String website() default "example.com"; int version() default 1; } 复制代码
当注解需要参数的时候,我们需要先定义注解的方法,方法名即参数名。这里有点类似于接口的定义,如果参数需要默认值,则在方法后加 default + 默认值
的方式来实现。
例如本例中,我们指定了三个参数, name、website、version
。分别定义了三个方法, name
没有指定默认值,所以它默认为null, website
指定了默认值为 example.com
,而 version
则指定了1为其默认值。
这里有三点规则强调一下:
- 注解方法不带参数,比如
name()
,website()
; - 注解方法返回值类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型
- 注解方法可有默认值,比如
default "example.com"
,默认website=”example.com”
当我们在使用时,需要给参数传递值,很简单:
@TestAnno(name="xiaoming", website="example.com", version=1) 复制代码
后文对这个例子中的其他部分,还会有详细的解释。
高效学精髓
为了方便学习,我们拿最常见的Java内建的注解 @Override
来食用。把握一下自定义注解实现时的几个步骤。
1. 编写定义注解的Java文件
要自定义注解,首先要创建一个以注解名字命名的Java文件,并使用@interface关键字来定义注解
Override.java
package java.lang public @interface Override { } 复制代码
2. 确定自定义注解的作用范围
这个也很容易理解,Override注解用来修饰方法,所以作用范围就指定为METHOD。
3. 确定自定义注解的作用时限
SOURCE、CLASS、RUNTIME的保留时限依次增加。而对于Override来说,我们日常编写代码时,通过这个注解可以知道哪些方法是被重写的。这个提示,也仅仅停留在了源码层面,所以这里使用SOURCE。
4. 确定自定义注解的参数、方法
Override并没有使用任何的参数和方法,这里也忽略了,后面我们实战例子里会重点介绍。
基于上述分析,我们写出了如下的定义源码:
package java.lang; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } 复制代码
通过以上的三个步骤,我们就定义好了一个注解。当我们使用时,直接通过@符号加注解名称即可。那么,加了注解的元素,有什么用呢?
其一,我们可以通过编译期处理,为注解所在的元素自动生成其他代码。像 ButterKnife
等均是如此,它帮助我们生成了View与组件的绑定,实现了点击监听器的绑定等等。保留到此时限的注解,我们也称其为编译期注解。
其二,我们在运行时,可以通过反射,获取到注解信息,这些注解信息,往往是程序编写者希望传递给运行时使用的一些信息或配置,可以起到简化配置的作用。像 Spring2.5
以后,运用了大量的运行时注解,它在实现AOP方面,有着广泛的应用。需要强调的是,这里的注解,都是 RUNTIME
规则的,只有这样才能保留到运行时。所以我们称其为运行时注解。
本篇我们重点介绍代码编写阶段的提示性注解和运行时注解。下面我们再通过两个实际例子来理解一下。
大胆练实战(一)
提示性注解示例
我们在java中,除了 Override
,还会经常见到一些其他的内建注解。例如 SuppressWarnings
(压制警告),他用于告知编译器忽略特定的警告信息,如在泛型中使用原始数据类型。
package java.lang; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE }) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { /** * The list of warnings a compiler should not issue. */ public String[] value(); } 复制代码
按我们上节所需编写自定义注解的几个步骤,来分别分析一下:
注解的作用范围
SuppressWarnings
可用于除注解类型声明和包名之外的所有元素。所以这里的 Target
做了相应的指定。
注解的作用时限
这里的警告,都是静态语法检查类型的警告信息,所以这个注解也只需要保留在源码层面。即 SOURCE
。
注解的参数方法
SuppressWarning
指定了一个 String
类型的数组。它支持了多个字符串参数。其可取值为需要压制的警告类型,见下表:
参数 | 含义 |
---|---|
deprecation | 使用了过时的类或方法时的警告 |
unchecked | 执行了未检查的转换时的警告 |
fallthrough | 当Switch程序块进入进入下一个case而没有Break时的警告 |
path | 在类路径、源文件路径等有不存在路径时的警告 |
serial | 当可序列化的类缺少serialVersionUID定义时的警告 |
finally | 任意finally子句不能正常完成时的警告 |
all | 以上所有情况的警告 |
使用时,可按如下的方法赋值:
@SupressWarning(value={"uncheck","deprecation"}) 复制代码
运行时注解示例
针对我们上一节中自定义的注解TestAnno,我们来实践一下运行时注解。
准备工作
package com.xm.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Target(ElementType.METHOD) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface TestAnno{ String name(); String website() default "example.com"; int version() default 1; } 复制代码
假设我们在如下的代码块中应用了此注解:
package com.xm.annotation; public class AnnotationDemo { @AuthorAnno(name="xiaoming", website="example.com", version=1) public static void main(String[] args) { System.out.println("I am main method"); } @SuppressWarnings({ "unchecked", "deprecation" }) @AuthorAnno(name="suby", website="example2.com", version=2) public void demo(){ System.out.println("I am demo method"); } } 复制代码
针对注解解析
现在,我们在运行时,通过反射来解析自定义的注解@TestAnno,关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口定义了注释相关的几个核心方法,如下:
返回值 | 方法 | 含义 |
---|---|---|
T | getAnnotation(Class annotationClass) | 当存在该元素的指定类型注解,则返回相应注释,否则返回null |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 当存在该元素的指定类型注解,则返回true,否则返回false |
前面我们自定义的注解,适用对象为Method。类Method继承类AccessibleObject,而类AccessibleObject实现了AnnotatedElement接口,那么可以利用上面的反射方法,来实现解析@TestAnno的功能(AnnotationParser.java),内容如下:
package com.xm.annotation; import java.lang.reflect.Method; public class AnnotationParser { public static void main(String[] args) throws SecurityException, ClassNotFoundException { String clazz = "com.xm.annotation.AnnotationDemo"; Method[] demoMethod = AnnotationParser.class .getClassLoader().loadClass(clazz).getMethods(); for (Method method : demoMethod) { if (method.isAnnotationPresent(TestAnno.class)) { AuthorAnno authorInfo = method.getAnnotation(TestAnno.class); System.out.println("method: "+ method); System.out.println("name= "+ authorInfo.name() + " , website= "+ authorInfo.website() + " , version= "+authorInfo.version()); } } } } 复制代码
程序的输出结果:
method: public void com.xm.annotation.AnnotationDemo.demo() name= suby , website= example2.com , version= 2 method: public static void com.xm.annotation.AnnotationDemo.main(java.lang.String[]) name= xiaoming , website= example.com , version= 1 复制代码
由此可见,我们可以通过在编写代码时,将一些运行时需要的信息,通过注解的方式传递给运行时的代码,以达到信息传递和配置的目的。在Spring中,也大量采用了运行时注解,为程序的配置,以及程序开发,特别是AOP编程,都提供了极大的便利。
下一篇重点介绍编译期注解,在众多的知名框架或工具型SDK中,都能看见它的身影,它可以以一种极度优雅简洁的方式,为我们提供开发上的便捷。
小铭出品,必属精品
欢迎关注xNPE技术论坛,更多原创干货每日推送。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。