1. 学习背景
因为现在很多框架都几乎用到了注解,而使用传统xml进行配置的情况已经逐渐成为了过去,但是仅仅会使用注解是不够的,还需要掌握其原理,这样我们在使用的时候才会自信
2. Annotation的由来
从java5.0版本发布以来,5.0平台提供了一个正式的annotation功能;允许开发者定义、使用自己的annotation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotation的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt annotation processing tool)组成
3. Annotation的作用
annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响,annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取
4. Annotation入门
我们先学习几个已知的、熟悉的注解,从而进一步深入学习Annotation
4.1. Override注解学习
首先看一段程序,通过程序来学习Annotation,如下代码编译时会报错:
public class OverrideTest {
@Override
public String tostring() { //关键看此处
return "This is OverrideTest";
}
public static void main(String[] args) {
OverrideTest test = new OverrideTest();
System.out.println(test);
}
}
编译器提示如下:tostring()方法必须覆盖或实现一个父类方法
总结:Override注解表示子类要重写(override)父类的对应方法。
4.2. Deprecated注解学习
通过一段程序来学习Annotation,被修饰Deprecated注解的方法在调用时编译器的显示效果如下,调用方法doSomething之时会有删除线:
总结:Deprecated注解表示方法是不建议被使用的
4.3. SuppressWarnings注解学习
先看如下程序,在编译器中的显示效果如下,存在多条黄线(警告):
编译器的具体提示如下:
使用@SuppressWarnings后的情况:
发现当调用map的相关方法时,警告被抑制住了,(map.put方法上没有黄色线条了)
总结:SuppressWarnings注解表示抑制警告
5. Annotation的定义方式
新建一个Annotation,名字填写为AnnotationTest,双击新建的注解后如下图所示:
这样我们就自定义了一个注解
6. Annotation的使用方式
新建一个类AnnotationUsage,并使用自定义注解AnnotationTest,具体代码如下所示:
以上代码可以总结如下两点:
当注解中的属性名为value时,在对其赋值时可以不指定属性的名称而直接写上属性值即可,除了value以外的其他值都需要使用name=value这种赋值方式,即明确指定给谁赋值
当自定义一个注解后(该注解没有任何元注解修饰的情况下,之后会讲),它可以使用在任何地方(类上、成员变量上、方法上、方法形参上、局部变量上)
7. Annotation使用细节
7.1. 注解默认值的定义方式
7.2. 注解默认值的使用方式
7.3. 注解属性是数组类型
如果自定义注解中的属性类型是数组类型,例如下图所示:
那么在使用的时候语法规则有点不同,如下图所示:
总结:如果自定义注解的属性是数组类型,那么在使用的时候,如果只需要赋予一个值得话,那么就可以直接key=value的方式赋值,当然也可以用花括号的方式,如果想要赋予多个值得话,那么必须用花括号的方式
8. 注解底层实现细节
我们讲当我们自定义一个枚举的时候,枚举是自动继承了java.lang.Enum这个类,那么对于注解来说类似,使用@interface自行定义Annotation形态时实际上是自动继承了java.lang.annotation.Annotation接口,由编译程序自动为您完成其他产生的细节,和枚举是一样的。
如果我们定义了一个接口,并且让该接口继承自Annotation,那么我们所定义的接口仍然还是接口,而不是注解
Annotation本身是接口而不是注解,可以与Enum类比
有一点需要注意:在定义Annotation时是不能继承其他类或实现其他接口的,否则编译通不过
9. 包管理注解
定义Annotation型态时也可以使用包来管理类别,方式类同于类的导入功能:之前的Override、Deprecated、SuppressWarnnings因为都是java.lang包下的所以不用import
10. JDK5内建Annotation
10.1. Retention元注解介绍
Retention元注解是为自定义注解服务的,它来指示编译程序该如何对待我们自定义的注解
Retention注解中仅有一个属性value它的类型是RetentionPolicy类型,而RetentionPolicy是一个枚举类,它拥有三个实例:
- SOURCE:注解仅存于源文件中
- CLASS:注解会存在于.class文件中,但在运行期不能被VM获取到 (使用反射获取不到注解)
- RUNTIME:注解会存在于class文件中,但在运行期能被VM获取到 (能通过反射获取到注解)
缺省情况下(也就是我们自定义的注解没有使用@Retention注解的时候),编译程序会将Annotation信息保留在.class文件中,但不被虚拟机读取,而仅用于编译程序或工具程序运行时提供信息,可以简单的理解为在缺省情况下,我们自定义的注解被@Retention(RetentionPolicy.Class)所修饰
对于SuppressWarnings注解中Retention使用的情况如下图所示:
对于上图代码可以有如下总结:
- 因为在Retention注解中属性名为value,所以在赋予值的时候可以忽略属性名
- 对于SuppressWarnings注解而言仅在编译时期告知编译程序来抑制警告,所以不必将这个信息存储与.class文件中
- 注解可以被其它注解修饰
一般而言,我们在自定义注解的时候,通常会用元注解@Retention(RetentionPolicy.RUNTIME)来修饰,这样就可以在运行时用反射来做一些文章了,这样注解的作用才会被最大化
10.2. 如何处理@Retention
想要通过反射来处理注解,有一个前提就是只有当自定义注解被@Retention(RetentionPolicy.RUNTIME)修饰的时候,这才有可能发生
java提供了与注解相关的反射接口java.lang.reflect.AnnotatedElement,它有如下几个方法:
- public Annotation getAnnotation(Class annotationType);
- public Annotation[] getAnnotations();
- public Annotation[] getDeclaredAnnotations();
- public boolean isAnnotationPresent(Class annotationType);
并且Class、Constructor、Field、Method、Package等类别都实现了AnnotatedElement接口
以下有一个例子用来展现注解的作用:
自定义注解Checked:
方法中带有注解的类Test:
测试类Client:
输出结果:
hello world
good day
上述例子就是通过方法上是否存在注解@Checked以及它的value值是否为hello world来决定是否执行该方法
总结:注解配合JDK给我们提供的AnnotatedElement API可以发挥很大作用,SpringMVC就用到了大量注解来代替XML,当然底层还是使用了反射技术
10.3. Target元注解介绍
首先Target注解也是一个元注解(即注解的注解)使用java.lng.annotation.Target可以定义我们自定义注解的使用时机,在定义时要指定java.lang.annotation.ElementType的枚举值之一
缺省情况下,我们自定义的注解可以使用在任何地方(类上、成员变量上、方法上、方法形参上、注解上等)
我们自定义一个注解如下:
再定义一个被该注解修饰的类如下:
编译器给的错误提示如下所示:
因为该注解的Target的value值为ElementType.Method,所以我们自定义的注解@Passed只能修饰在方法上如下所示:
如果我们想让自定义的注解修饰在多处那怎么办呢?因为Target中的value属性是用ElementType[]修饰的,这表明我们可以使用数组的形式给value赋值,具体如下图所示:
10.4. Documented元注解介绍
当我们自己开发了一个系统,客户想要我们的javadoc时,如果你想让客户在查阅doc中一些类或方法的时候,相关的注解也能有所体现,那么在我们自定义的注解上需要加上元注解@Documented
例如注解@Passed加上Documented注解如下图所示:
这时生成的javadoc是这样的:
如果@Passed没有加上@Documented注解,那么生成的javadoc是这样的:
10.5. Inherited注解介绍
在缺省情况下父类别的Annotation并不会被继承至子类别中,可以在定义Annotation形态时加上java.lang.annotation.Inherited形态的Annotation,这样我们自定义的注解,就可以被继承至子类别中了
现定义了如下注解:
定义了一个被@InheritAnno修饰的类:
定义了一个子类:
测试类:
控制台输出结果如下:
true
true
当@InheritAnno去掉@Inherited时,输出结果如下:
true
false
该demo已经体现出了@Inherited的作用了
另一个例子
自定义一个注解,并没有用@Inherited注解修饰:
自定义一个类,使用了注解@InheritAnno:
自定义一个类,继承了InheritTest:
测试类:
控制台输出结果:
总结:子类继承父类后,如果父类中的方法有被注解修饰,那么子类继承过来后,该注解也会携带过来,但如果是修饰在Class上的注解,那么不会继承过来,除非修饰的注解有用@Inherited元注解修饰
11. 代码实战
我们都知道junit3.8版本使用方法是定义一个类并继承TestCase,然后该类中的方法以test打头,这样在运行的时候,这些方法就会被自动执行。而Junit 4.X 使用了注解的方式,测试类不需要去继承TestCase,并且待测试的方法不需要以test打头,而是用@Test注解修饰这些待测试的方法即可
他们之间底层的代码差异应该如下:
Junit3.8:
Junit4.X:
12. 总结
Annotation这块内容还是很重要的,如果有较好的掌握,那么对于之后的框架学习是非常有帮助的
需要记住以下几点:
- 自定义注解如果想配合反射来使用那么该注解需要修饰@Retention(RetentionPolicy.RUNTIME)
- 掌握JDK提供的AnnotatedElement接口API下的几个方法:
- isAnnotationPresent(Class<? extends Annotation> cls)
- getAnnotation(Class<? extends Annotation> cls)
- getAnnotations()
- 掌握Annotation语法
- Annotation定义关键字@interface
- Annotation属性value在赋值的时候可以缺省
- Annotation属性如果是数组类型,那么赋值的时候如果想赋予多个值那么需要使用花括号,如果只赋予一个值那么花括号可用可不用
- Annotation属性可以定义默认值 关键字为default
- 掌握元注解的使用:
- @Retention 重要
- @Target 重要
- @Document 了解
- @Inherited 了解
13. 文章引用来源
<<JavaSE 张龙系列>>