注解的实现原理以及使用方式

158 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

什么是注解?

  • 注释给程序员看,注解则是给编译器看的,称之为元数据,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量进行注解。
  • 它相当于代码中的特殊标记,通过注解为代码打上标记后,那么程序就可以在编译类加载运行时读取这些注解,并执行相对应的处理逻辑。
  • 传统方式是通过配置XML文件的方式来告诉类要如何运行,现在我们可以通过在类上加注解告诉类要如何运行。

注解分类

可以根据注解的性质,将注解分为以下三大类: (一)基本注解,在java.lang包下,包括以下五个注解:

  1. @Override:重写注解
  2. @Deprecated:标注某个类或者方法过时
  3. @SuppressWarnings("all"):忽略编译器所有警告
  4. @SafeVarargs:jdk7中堆污染警告
  5. @Functionallnterface:显示指定该接口是函数式接口

(二)元注解,是用于定义注解的注解,在java.lang.annotation包下,包括以下常用注解:

  1. @Retention:标明注解被保留的阶段,SOURCE(编译时)<CLASS(类加载)<RUNTIME(运行时),一般都写RUNTIME,保证在运行的时候,注解还是有效的。
  2. @Target:标明注解在什么类型上(类,方法,属性 ...)
  3. @Inherited:标明注解可继承
  4. @Documented:表明该注解被包含在javadoc中
  5. @Inherited:子类可以继承父类的注解

(三)自定义注解,根据自己的需求定义注解。使用的第三方库里的注解也算是自定义在注解,只不过是别人自定义的,比如我们经常使用@Value进行值注入,使用@Autoware进行依赖注入

应用场景

  1. 生成文档:通过代码里的标识生成javadoc文档。
  2. 编译检查:让编译器在编译期间进行检查验证。
  3. 编译时动态处理:例如编译时动态生成代码。
  4. 运行时动态处理:例如使用反射注入实例

实现原理

        最近做系统搜索时用到了Elasticsearch,在查询的时候,需要给指定查询字段的属性,以及分词器,并且并不是所有字段这些属性都相同,所以就自定义了一个EsField注解,包含typeanalyzer两个值,作用于FieldDTO的每个字段上,用于指定查询字段的属性和分词器。借此机会,也分析一下注解的实现原理。

  • 自定义注解@EsField
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EsField {
    String type();
    String analyzer();
}
  • DTO的字段上使用注解

@Data
public class ContentDTO {

    /**
     * 数据id,将mysql中的数据ID同步到ES中,便于更新
     */
    @EsField(type = "keyword", analyzer = "ik_max_word")
    private Integer id;

    /**
     * 1.type属性:表示在Es中对应的属性
     * 2.analyzer属性,表示需要分词,指定分词器
     */
    @EsField(type = "keyword", analyzer = "")
    private String title;
}
  • 测试类分析注解的运行原理

如下debug过程中,可以看到,最终获取到的esFiled是一个代理类

image.png

进一步反编译esField.class文件之后,看到如下内容:

image.png

  • Java 通过动态代理为这个注解所转化的接口生成了一个代理类,这个代理类实现了注解中成员属性对应的抽象方法,并完成给成员属性赋值的任务,然后在程序运行的时候就可以通过反射获取到注解的成员属性的值了。
  • 其实注解的底层原理就是java的动态代理。

自定义注解

  • 使用@interfae自定义注解,会自动继承Annotation接口,格式:public @interface 注解名{定义内容}

  • 在注解上定义的成员变量只能是String,数组,Class,枚举类,注解

在这里插入图片描述

  • 赋值
@MyAnnotation(username = "zhangsan", age = 20)
public void add(String username, int age) {
}
  • 在注解声明属性的时候,给出默认值,修饰的时候就不需要给出具体的值了。
public @interface MyAnnotation {
    //定义了两个成员变量
    String username() default "zicheng";
    int age() default 23;
}