前言
理解一个概念,需要从:为什么是它?它怎么用?它用在哪些场景?从这三个方面去阐述一个概念、术语从而更好的理解它。
网上不少关于注解的文章写的挺好的,不过大多集中在注解的使用上,本次我们围绕以上三个角度去阐述注解的概念。
通过本篇文章,你将了解到:
- 为什么需要注解?
- 注解的原理
- 注解的常规用法
- 注解用在哪些场景?
1. 为什么需要注解?
注释
平时我们写代码,比如新建一个类的时候会自动/手动在类前面添加注释如:
java
package com.fish.annotation;
/**
* Dog类用于展示作者信息
* 该类包含一个display方法,用于输出作者信息
*
* @author 小鱼人
*/
public class Dog {
public void display(String author) {
System.out.println("Dog author:" + author);
}
}
注释的作用是:
- 让自己温故知新,让别人了解前因后果
- 是给人看的
查看Dog.class:
可以看出,注释只在.java里显示,编译为.class后就没了,因此注释是给人看的。
同样的,不仅是类上有注释,我们可以在源码的任何地方添加注释,前提是我们得让编译器知道我们这是注释。
大部分的语言注释标准是:"//" 或者 "/** */",包含在里面的内容都被编译器当成注释,编译时会丢弃这部分的信息。
注释只能给人看,现在想要在代码运行的时候能够知道这个类的编写者是谁?
你可能会说:这简单啊,在类里定义一个属性(author),默认设置为编写者,这不就妥了吗?
java
/**
* Dog类用于展示作者信息
* 该类包含一个display方法,用于输出作者信息
*
* @author 小鱼人
*/
public class Dog {
String author = "小鱼人";
public void display(String author) {
System.out.println("Dog author:" + author);
}
}
增加属性当然是可以的,但考虑以下两点:
- 它是否是该类的非必须的属性(没有它编译是否正常、运行是否正常)
- 它是否仅用于标记代码的额外功能(给代码打上标签)?
因此,我们需要一种额外的方式来给代码添加标签,在Java里,这种标签叫注解(注释也是一种标签)。
注解
注释、注解仅一字之差,它们都是用来给代码打标签的,只是用在不同的场景。
那编译器怎么区分两者呢?
- "//" 之后,包裹在"/** */" 里面的表示注释
- "@"之后的代表注解
定义简单的注解如下:
java
package com.fish.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnnotation {
String author();
}
该注解名为AuthorAnnotation,有一个author()方法,用来记录作者名字。
然后再用它去修饰Dog类:
java
/**
* Dog类用于展示作者信息
* 该类包含一个display方法,用于输出作者信息
*
* @author 小鱼人
*/
@AuthorAnnotation(author = "小鱼人")
public class Dog {
public void display(String author) {
System.out.println("Dog author:" + author);
}
}
当我们运行程序的时候,就可以从Dog取出AuthorAnnotation注解,从而取到它的author值。
即使我们不使用注解,也不会影响Dog的编译、运行。
2. 注解的原理
注解的原理
定义注解:
java
public @interface AuthorAnnotation {
String author();
}
查看AuthorAnnotation.class文件:
发现它是抽象的注解类型,并实现了Annotation接口。
@interface表示注解类型。
注解里的每个方法代表一个属性。
元注解
java
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnnotation {
String author();
}
用于注解其他注解的注解,@AuthorAnnotation是我们自定义的注解,@Retention是系统提供的,用来注解AuthorAnnotation,因此@Retention称为元注解,除了它还有:@Target、@Documented等。
3. 注解的常规用法
注解的生命周期(保留策略)
注解有三种生命周期:
- 注解只在源码级别(.java)生效
- 注解在源码级别(.java)、编译级别(.class)生效
- 注解在源码级别(.java)、编译级别(.class)生效、运行级别(运行时)生效
可以看出,注解的三种生命周期范围是逐级递增,注解的生命周期可以使用元注解:@Retention指定。
1. 定义第1种生命周期注解:
java
@Retention(RetentionPolicy.SOURCE)
public @interface AuthorAnnotation {
String author();
}
在另一个类引用它:
java
@AuthorAnnotation(author = "小鱼人")
public class Dog {
}
查看Dog.class:
发现它里面并没有AuthorAnnotation注解的任何信息。是因为我们设置了: @Retention(RetentionPolicy.SOURCE),意思是注解只在源码级别保留,因此.class里并没有它。
那么此种注解有什么作用呢?通常提供给编译器使用,比如我们熟知的:@Override 注解
java
@Override
public String getName() {
return name;
}
查看 @Override定义:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
也是源码级别的,编译器随时监测.java文件里的编译规范,一旦发现某个方法需要重写父类方法就会提示使用@Override修饰,因此它的生命周期停留在源码阶段足矣。
除了@Override,@Native、@SuppressWarnings的生命周期也是源码级别的。
此种级别的生命周期主要用在:编译器的代码检测和提示等。
2. 定义第2种生命周期注解:
java
@Retention(RetentionPolicy.CLASS)
public @interface AuthorAnnotation {
String author();
}
在另一个类引用它:
java
@AuthorAnnotation(author = "小鱼人")
public class Dog {
}
并查看Dog.class:
可以看出Dog.class里存有AuthorAnnotation,并且后面有个invisible注释其为运行期不可见,也就是不能通过反射读取Dog类上的注解。
Android里比较流行的路由库ARouter使用注解配置路由信息,其注解如下:
此种级别的生命周期主要用在:编译期代码自动生成,常用在一些生成中间代码文件的框架。
大致流程是:扫描生成的.class文件,从中提取出注解信息(借助:AbstractProcessor),从而决定是否需要生成.java文件。
3. 定义第3种生命周期注解:
java
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnnotation {
String author();
}
查看Dog.class:
改class文件里不仅保留了AuthorAnnotation,并且运行时也可以通过反射读取Dog类上的注解。
此种级别的生命周期主要用在:运行期间通过反射读取注解信息。
广泛用于各种Java框架里,包括著名的SpringBoot,如下为它处理网络请求的类:
这些注解的生命周期都是运行时。
我们主要关注第3种生命周期的注解,如若注解不显式指明@Retention,那么默认是RetentionPolicy.CLASS。
注解的作用域
一个类里的关键元素有:类本身、构造函数、方法、方法参数、属性。
若要限定注解只能在某些元素上使用,可使用元注解:@Target
它可取的值以及对应能修饰的不同元素如下:
ElementType.TYPE:类、接口、枚举
ElementType.FIELD:字段
ElementType.METHOD:方法
ElementType.PARAMETER:参数
ElementType.CONSTRUCTOR:构造器
ElementType.LOCAL_VARIABLE:局部变量
ElementType.ANNOTATION_TYPE:注解类型
ElementType.PACKAGE:包
ElementType.TYPE_PARAMETER:类型参数
ElementType.TYPE_USE:类型使用
如若不显示指明@Target,那么默认的作用域是所有类型。
我们通常使用到的是前五种类型,接下来看看如何在运行期解析注解。
1. 注解修饰类
定义注解:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotation {
String author();
}
使用注解:
java
@ClassAnnotation(author = "小鱼人")
public class Dog {
}
解析注解:
java
public class Main {
public static void main(String[] args) throws Exception {
Dog dog = new Dog();
if (dog.getClass().isAnnotationPresent(ClassAnnotation.class)) {
ClassAnnotation annotation = dog.getClass().getAnnotation(ClassAnnotation.class);
String author = annotation.author();
System.out.print("author:"+author);
}
}
}
通过反射获得Dog类上的注解,拿到注解对象后即可调用注解里的方法(实际获取的是属性值)。
需要注意的是获取注解前最好先判断注解是否存在(isAnnotationPresent)。
当我们需要修改Dog作者时,只需要修改注解的值即可。
2. 注解修饰方法和属性
定义注解:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
String author() default "小鱼人马甲";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAnnotation {
String author() default "小鱼人马甲";
}
使用注解:
java
@ClassAnnotation(author = "小鱼人")
public class Dog {
@FieldAnnotation(author = "小鱼人-属性")
private String name;
@MethodAnnotation(author = "小鱼人-方法")
private void displayAuthor(String author) {
System.out.println("author:" + author);
}
}
解析注解:
java
public class Main {
public static void main(String[] args) throws Exception {
Dog dog = new Dog();
Class<?> dogClass = dog.getClass();
Method method = dogClass.getDeclaredMethod("displayAuthor", String.class);
if (method.isAnnotationPresent(MethodAnnotation.class)) {
//获取方法上的注解
MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
String author = methodAnnotation.author();
//调用方法
method.setAccessible(true);
method.invoke(dog, author);
}
Field field = dogClass.getDeclaredField("name");
if (field.isAnnotationPresent(FieldAnnotation.class)) {
//获取字段上的注解
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
String author = fieldAnnotation.author();
//调用方法
field.setAccessible(true);
field.set(dog, author);
System.out.println("name:" + field.get(dog));
}
}
}
通过以上对类、方法、属性上的注解的动态解析,我们明白了注解作用域的使用方式,其它未列出的方式与此类似。
如果注解里只有一个属性名为:value,那么在引用该注解赋值时,可以省略名称,如下:
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DefaultValueAnnotation {
String value() default "fish";
}
@DefaultValueAnnotation("I'm default")//无需明确写 @DefaultValueAnnotation(value = "I'm default")
public class Cat {
}
4. 注解用在哪些场景?

其中运行期解析很常见,SpringBoot里大量的注解在运行期解析,它的流程大致如下:
- 启动时扫描组件,解析标记了 @Controller、@RestController 的类,并生成Bean对象
- 将 @RequestMapping注解的方法和注解的路径绑定
- 当有接口请求到来后,根据接口的路径匹配第2步的缓存,找到对应的方法,反射调用
使用注解即可实现了解耦的目的,也让你写的代码高大上起来,当熟悉了注解的使用后,我们就可以写写框架练手了。
本篇到此就结束了,若是有帮助,请一键三连~
下篇将重点分析代理,尤其是动态代理,敬请期待。