10

对于java的注解, 自己已经使用了相当长的时间, spring中对注解的使用无处不在,但对它的了解并不深入, 本周对注解进行了较为深入的学习。

什么是注解

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

上面的是java注解的官方解释,相信看了以后本来明白的仍然明白,但是不明白的却还是不明白。

我感觉注解可以简单理解为是一个标签, 若是在类或属性等上面有什么标签,我们就需要对其进行某些处理,如何处理呢?别急,后面会说。

注解的本质

public @interface TestAnnotion {
}

一个最简单的注解定义方式就像上面一样, 和接口很像对不对,只是多了一个@符号,注解实际上是一种继承自接口java.lang.annotation.Annotation的特殊接口,jdk文档有如下说法

An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (@)
……
The direct superinterface of every annotation type is java.lang.annotation.Annotation.

注释类型声明指定一种新的注释类型,一种特殊的接口类型。要将注释类型声明与普通接口声明区分开来,关键字接口前面有at符号(@)
……
每个注释类型的直接上接口是java.lang.annotation.annotation。

所以上面定义的TestAnnotion,可以理解成这样

public interface TestAnnotion extends Annotation {
}

这篇文章这篇文章
通过反编译给我们了更直观的展示这个结论。

自定义注解

想要自定义注解就肯定要了解元注解

元注解

  • 元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。

@Retention

  • Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
  • @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
  • @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
  • @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到
  • 如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)

@Target

  • Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
  • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
  • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
  • @Target(ElementType.METHOD) 作用方法
  • @Target(ElementType.PARAMETER) 作用方法参数
  • @Target(ElementType.CONSTRUCTOR) 作用构造函数
  • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
  • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) 作用于包
  • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
  • 一般比较常用的是ElementType.TYPE类型

@Documented

  • Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Inherited

  • Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。

@Repeatable

  • Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。

实现spring data jpa的@Entity(伪)

我们知道 spring data jpa 中 如果给某个类加上@Entity的注解,spring就会为我们创建相应的数据表,

接下来我们就实现一个注解:他会生成创建一个数据表的sql,然后打印出来(执行也是的原理,咱就不执行了), 要实现这个功能,需要和反射相结合,若是还未学习过反射可以先通过这篇文章学习一下。

Entity.java

@Target(ElementType.TYPE) // 作用于类
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {

}

Member.java

@Entity
public class Member {
    String name;
    Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

TableCreator.java


public class TableCreator {
    public static void
    main(String[] args) throws Exception {
        // 获取Member的实例变量
        Class<?> cl = Member.class;
        // 查找该类上是否有相应的注解
        Entity entity = cl.getAnnotation(Entity.class);
        if (entity == null) {
            System.out.println(
                    "该类没有Entity注解");
            return;
        }
        // 获取数据库的名
        String tableName = cl.getName().toLowerCase();

        // 定义像对象的属性
        List<String> columnDefs = new ArrayList<>();
        for (Field field : cl.getDeclaredFields()) {
            String columnName = field.getName().toLowerCase();

            String columnDef = columnName + " varchar(50)";
            columnDefs.add(columnDef);
        }

        // 构造sql语句
        StringBuilder createCommand = new StringBuilder(
                "create table " + tableName + "(");
        for (String columnDef : columnDefs)
            createCommand.append("\n    ").append(columnDef).append(",");
        String tableCreate = createCommand.substring(
                0, createCommand.length() - 1) + ");";
        System.out.println("生成的sql语句为:\n" + tableCreate);
    }

}

image.png
像上面这种处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)
#### 注解成员变量

我们可以在注解中设置成员变量,形如

@Target(ElementType.TYPE) // 作用于类
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {

  // name 值必须赋值, 若不设置默认值则必须在使用处赋值
  String name() default "";
}

赋值方式
image.png

同时,还有一点需要注意的是,如果你在注解中定义了名为 value 的元素,并且在使用该注解时,value 为唯一一个需要赋值的元素,你就不需要使用键—值对的语法,你只需要在括号中给出 value元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 value,不过在上面的例子中,这样的注解语句也更易于理解:

@Target(ElementType.TYPE) // 作用于类
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {

    //value 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法
    String value();
    String test() default "";
}

image.png
但若不止一个需要赋值的变量,则value也需要键值对的形式
image.png

获取成员变量的值

直接通过获取即可

  // 获取Member的实例变量
      Class<?> cl = Member.class;
      // 查找该类上是否有相应的注解
      Entity entity = cl.getAnnotation(Entity.class);
      // 获取注解中的成员变量值
      String name = entity.value();

java内置的注解

除了上面几种用于定义注解的注解外,java还为我们提供了另外五个注解,Java 5 引入了前三种定义在 java.lang 包中的注解:

  • @Override:表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。
  • @Deprecated:如果使用该注解的元素被调用,编译器就会发出警告信息。
  • @SuppressWarnings:关闭不当的编译器警告信息。
  • @SafeVarargs:在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。
  • @FunctionalInterface:Java 8 中加入用于表示类型声明为函数式接口

总结

对于注解我们可以理解为一个标签,本质上是一个特殊的接口,他的功能主要有如下几点:

  • 提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
  • 运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。
  • 正如官方文档的那句话所说,注解能够提供元数据, 处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)

参考文章

java注解是怎么实现的?
Java 注解完全解析
第二十三章 注解

版权声明

本文保留所有权利,版权归河北工业大学梦云智软件开发团队所有。未经团队及作者事先书面同意,您不得以任何方式将本文内容进行商业性使用或通过信息网络传播本文内容


笙歌会停
1k 声望45 粉丝

代码成就万世基积沙镇海 梦想永在凌云意意气风发