Java 注解及其底层原理

打印 上一主题 下一主题

主题 866|帖子 866|积分 2600

目录

作者:小牛呼噜噜 | https://xiaoniuhululu.com
计算机内功、JAVA底层、面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」
什么是注解?

当我们开发SpringBoot项目,我们只需对启动类加上@SpringBootApplication,就能自动装配,不需要编写冗余的xml配置。当我们为项目添加lombok依赖,使用@Data来修饰实体类,我们就不需要编写getter和setter方法,构造函数等等。@SpringBootApplication,@Data等像这种以@开头的代码 就是注解,只需简简单单几个注解,就能帮助我们省略大量冗余的代码,这是一个非常不可思议的事情!
但我们往往知道在哪些地方加上合适的注解,不然IDE会报错,却不知道其中的原理,那究竟什么是注解呢?
注解(Annotation ),  是 Java5 开始引入的新特性,是放在Java源码的类、方法、字段、参数前的一种特殊“注释”,是一种标记、标签。注释往往会被编译器直接忽略,能够被编译器打包进入class文件,并执行相应的处理。
按照惯例我们去看下注解的源码:
先新建一个注解文件:MyAnnotation.java
  1. public @interface MyAnnotation {
  2. }
复制代码
发现MyAnnotation 是被@interface修饰的,感觉和接口interface很像。
我们再通过idea来看下其的类继承:

MyAnnotation 是继承Annotation接口的。
我们再反编译一下:
  1. $ javac MyAnnotation.java
  2. $ javap -c MyAnnotation
  3. Compiled from "MyAnnotation.java"
  4. public interface com.zj.ideaprojects.test3.MyAnnotation extends java.lang.annotation.Annotation {
  5. }
复制代码
发现生成的字节码中 @interface变成了interface,MyAnnotation而且自动继承了Annotation
我们由此可以明白:注解本质是一个继承了Annotation 的特殊接口,所以注解也叫声明式接口
注解的分类

一般常用的注解可以分为三大类:
Java自带的标准注解

例如:


  • @Override:让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。
  • @Deprecated:标记过时的元素,这个我们经常在日常开发中经常碰到。
  • @FunctionalInterface:表明函数式接口注解
元注解

元注解是能够用于定义注解的注解,或者说元注解是一种基本注解,包括@Retention、@Target、@Inherited、@Documented、@Repeatable 等
元注解也是Java自带的标准注解,只不过用于修饰注解,比较特殊。
@Retention

注解的保留策略, @Retention 定义了Annotation的生命周期。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的参数:
RetentionPolicy.SOURCE注解只在源码阶段保留,在编译器进行编译时它将被丢掉RetentionPolicy.CLASS注解只被保留到编译进行的时候,它并不会被加载到 JVM 中RetentionPolicy.RUNTIME注解可以保留到程序运行中的时候,它会被加载进 JVM 中,在程序运行中也可以获取到它们
如果@Retention不存在,则该Annotation默认为RetentionPolicy.CLASS
示例:
  1. @Retention(RetentionPolicy.RUNTIME)
  2. public @interface TestAnnotation {
  3. }
复制代码
我们自定义的TestAnnotation 可以在程序运行中被获取到
@Documented

它的作用是 用于制作文档,将注解中的元素包含到 doc 中
一般不怎么用到,了解即可
@Target

@Target 指定了注解可以修饰哪些地方, 比如方法、成员变量、还是包等等
当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
常用的参数如下:
ElementType.ANNOTATION_TYPE给一个注解进行注解ElementType.CONSTRUCTOR给构造方法进行注解ElementType.FIELD给属性进行注解ElementType.LOCAL_VARIABLE给局部变量进行注解ElementType.METHOD给方法进行注解ElementType.PACKAGE给包进行注解ElementType.PARAMETER给一个方法内的参数进行注解ElementType.TYPE给一个类型进行注解,比如类、接口、枚举@Inherited

@Inherited 修饰一个类时,表明它的注解可以被其子类继承,缺省情况默认是不继承的。
换句话说:如果一个子类想获取到父类上的注解信息,那么必须在父类上使用的注解上面 加上@Inherit关键字
注意:

  • @Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效
  • @Inherited 不是表明 注解可以继承,而是子类可以继承父类的注解
我们来看一个示例:
定义一个注解:
  1. @Inherited
  2. @Target(ElementType.TYPE)
  3. public @interface MyReport {
  4.     String name() default "";
  5.     int value() default 0;
  6. }
复制代码
使用这个注解:
  1. @MyReport(value=1)
  2. public class Teacher {
  3. }
复制代码
则它的子类默认继承了该注解:
  1. public class Student extends Teacher{
  2.    
  3. }
复制代码
idea 查看类的继承关系:

@Repeatable

使用@Repeatable这个元注解来申明注解,表示这个声明的注解是可重复的
@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
比如:一个人他既会下棋又会做饭,他还会唱歌。
  1. @Repeatable(MyReport.class)
  2. @Target(ElementType.TYPE)
  3. public @interface MyReport {
  4.     String name() default "";
  5.     int value() default 0;
  6. }
  7. @MyReport(value=0)
  8. @MyReport(value=1)
  9. @MyReport(value=2)
  10. public class Man{
  11. }
复制代码
自定义注解

我们可以根据自己的需求定义注解,一般分为以下几步:

  • 新建注解文件, @interface定义注解
  1. public @interface MyReport { }
复制代码

  • 添加参数、默认值
  1. public @interface MyReport {
  2.     String name() default "";
  3.     int value() default 0;
  4. }
复制代码

  • 用元注解配置注解
  1. @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface MyReport {
  2.     String name() default "";
  3.     int value() default 0;
  4. }
复制代码
我们一般设置 @Target和@Retention就够了,其中@Retention一般设置为RUNTIME,因为我们自定义的注解通常需要在程序运行中读取。
自定义注解的读取

读到这里,相信大家已经明白了 如何定义和使用注解,我们接下来 就需要如何将注解利用起来。
我们知道读取注解, 需要用到java的反射
推荐阅读笔者之前写过关于反射的文章:https://mp.weixin.qq.com/s/_n8HTIjkw7Emcunpb4-Iwg
我们先来写一个简单的示例--反射获取注解
通过前文的了解,先来改造一下MyAnnotation.java
  1. @Retention(RetentionPolicy.RUNTIME)//确保程序运行中,能够读取到该注解!!!
  2. public @interface MyAnnotation {
  3.     String msg() default "no msg";
  4. }
复制代码
我们再用@MyAnnotation来修饰Person类的类名、属性、和方法
  1. @MyAnnotation(msg = "this person class")//注解 修饰类
  2. public class Person {
  3.     private String name;//姓名
  4.     private String sex;//性别
  5.     @MyAnnotation(msg = "this person field public")//注解 修饰 public属性
  6.     public int height;//身高
  7.     @MyAnnotation(msg = "this person field private")//注解 修饰 private属性
  8.     private int weight;//体重
  9.     public void sleep(){
  10.         System.out.println(this.name+"--"+ "睡觉");
  11.     }
  12.     public void eat(){
  13.         System.out.println("吃饭");
  14.     }
  15.     @MyAnnotation(msg = "this person method")//注解 修饰方法
  16.     public void dance(){
  17.         System.out.println("跳舞");
  18.     }
  19. }
复制代码
最后我们写一个测试类
  1. public class TestAn {
  2.     public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
  3.         //获取Person class 实例
  4.         Class<Person> c1 = Person.class;
  5.         //反射获取 类上的注解
  6.         MyAnnotation classAnnotation = c1.getAnnotation(MyAnnotation.class);
  7.         System.out.println(classAnnotation.msg());
  8.         //反射获取 private属性上的注解
  9.         Field we = c1.getDeclaredField("weight");
  10.         MyAnnotation fieldAnnotation = we.getAnnotation(MyAnnotation.class);
  11.         System.out.println(fieldAnnotation.msg());
  12.         //反射获取 public属性上的注解
  13.         Field he = c1.getDeclaredField("height");
  14.         MyAnnotation field2Annotation = he.getAnnotation(MyAnnotation.class);
  15.         System.out.println(field2Annotation.msg());
  16.         //反射获取 方法上的注解
  17.         Method me = c1.getMethod("dance",null);
  18.         MyAnnotation methodAnnotation = me.getAnnotation(MyAnnotation.class);
  19.         System.out.println(methodAnnotation.msg());
  20.         
  21.     }
  22. }
复制代码
结果:
this person class
this person field private
this person field public
this person method
我们通过反射读取api时,一般会先去校验这个注解存不存在:
  1. if(c1.isAnnotationPresent(MyAnnotation.class)) {
  2.     //存在 MyAnnotation 注解
  3. }else {
  4.     //不存在 MyAnnotation 注解
  5. }
复制代码
我们发现反射真的很强大,不仅可以读取类的属性、方法、构造器等信息,还可以读取类的注解相关信息。
那反射是如何实现工作的?
我们来看下源码:
从 c1.getAnnotation(MyAnnotation.class);通过idea点进去查看源码,把重点的给贴出来,其他的就省略了
[code]Map

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

拉不拉稀肚拉稀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表