最近我写了一篇关于组件化的开源框架源码分析的文章(传送门在下面儿)。那么如今组件化小有名气的JIMU框架,也是我下一个要给你们分享的源码分析文章。但由于其中涉及到了不少Java Annotation
相关的知识。因此不得不在这里,先安利一下本篇,这也是本篇的由来。java
优秀框架源码分析系列(一)让解耦更轻松!多进程组件化框架-ModularizationArchitectureweb
“注解”,在Java世界里随处可见,但一般状况下,多数人对其是视而不见的。但当咱们设计SDK,设计基础库的时候,运用注解,能够起到简化配置的做用。熟悉ButterKnife
的朋友都知道,它就是经过注解来在编译期间,增长Java代码来实现的。若是你还不知道它是如何实现的,那么相信你食用完本篇和下一篇之后,就会明白这一切了。编程
学习新知识的时候,要掌握正确的进食方法,脑子里必须先对知识结构有预期,学习完以后再回顾结构,根据结构记住知识。本篇将按照导图的结构,来进行讲解。数组
元注解,就是用来修饰注解的注解。bash
@Target
被用来指明此Annotation所修饰的对象范围(即:被描述的注解能够用在什么地方)。Java中,注解(Annotation
)可被用于如下位置 :框架
注解的使用范围,经过Target
的取值来指定。指定好之后,@Target
修饰的元素必定是与其取的value
相匹配的,不然编译会报错。ide
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
指定三个阶段的其中之一,若是注解类型声明中不存在Retention
注解,则保留策略默认为CLASS
:
RetentionPolicy | 含义 |
---|---|
RetentionPolicy.SOURCE | 只在源代码级别保留,编译时就会被忽略 |
RetentionPolicy.CLASS | 在编译时被保留,在class文件中存在,但JVM将会忽略 |
RetentionPolicy.RUNTIME | 被JVM保留,因此他能在运行时被JVM或其余使用反射机制的代码所读取和使用 |
被@Documented修饰的注解,用来表示这个被修饰注解应该被 javadoc工具记录。默认状况下,javadoc是不包括注解的。但若是声明注解时指定了@Documented,则它会被javadoc之类的工具处理,因此注解类型信息也会被包括在生成的文档中。
若是一个用来修饰class的注解,但愿被这个class的sub-class继承,则能够对这个注解使用@Inherited修饰。 上面这句话强调了如下两点:
自定义注解,经过@符号和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()
;default "example.com"
,默认website=”example.com”当咱们在使用时,须要给参数传递值,很简单:
@TestAnno(name="xiaoming", website="example.com", version=1)
复制代码
后文对这个例子中的其余部分,还会有详细的解释。
为了方便学习,咱们拿最多见的Java内建的注解@Override
来食用。把握一下自定义注解实现时的几个步骤。
要自定义注解,首先要建立一个以注解名字命名的Java文件,并使用@interface关键字来定义注解
Override.java
package java.lang
public @interface Override {
}
复制代码
这个也很容易理解,Override注解用来修饰方法,因此做用范围就指定为METHOD。
SOURCE、CLASS、RUNTIME的保留时限依次增长。而对于Override来讲,咱们平常编写代码时,经过这个注解能够知道哪些方法是被重写的。这个提示,也仅仅停留在了源码层面,因此这里使用SOURCE。
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技术论坛,更多原创干货每日推送。