注解,一项在框架中与反射相辅相成的技术

90 阅读3分钟

注解还是很重要的,在这里先列举一些注解概述与基本的使用

注解一般在框架中是结合反射来使用的,可通过反射机制来读取注解,实现对元数据的访问

自定义注解的方式

使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口

public @interface MyAnnotation {
}

@interface 用来声明一个注解:格式: public @interface 注解名 { 定义内容 }

其中的每一个方法实际上是声明了一个配置参数

方法的名称就是参数的名称

返回值类型就是参数的类型(返回值只能是基本类型、Class 、String 、enum )

可以通过 default 来声明参数的默认值

如果只有一个参数成员,一般参数名为 value

注解元素必须要有值,我们定义注解元素时,常使用空字符串,0 作为默认值

@MyAnnotation1
public class AnnotationTest {
​
    @MyAnnotation1
    public void test1() {
​
    }
​
    @MyAnnotation2("aaa") // 可以把原先的 @MyAnnotation2(value = "aaa") 省略掉 value。默认规定
    public void test2() {
​
    }
}
​
@Inherited
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation1 { // 使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口
​
    String name() default ""; // 可以通过 default 来声明参数的默认值
​
    int id() default -1; // 使用注解时,只要有一个属性没有默认赋值,则使用时就要去手动赋值,不然会报错
​
    String[] schools() default {"清华大学", "北京大学"};
}
​
​
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
    /**
     * 只有一个注解的情况下,建议把参数名定义成 value,这样子使用时可以省略掉这个参数
     * 如果参数不用 value 作为参数名
     * 如用 name,则使用起来则需要:@MyAnnotation2(name = "aaa")
     */
    String value();
}

元注解

Java 定义了 4 个标准的 meta-annotation 类型,被用来提供对其他 annotation 类型做说明

这些类型和他们所支持的类,在 java.lang.annotation 包中可以找到:@Target@Retention@Documented@Inherited

  • @Target :用于描述注解的使用范围

  • @Retention :表示需要在什么级别保存该注释信息,用于描述注解的生命周期

    SOURCE < CLASS < RUNTIME(框架都是 runtime,意思是注解要保证到了运行时还是可用的)

  • @Documented :说明该注解将被包含在 javadoc 中

  • @Inherited :说明子类可以继承父类的注解



通过反射来获取注解信息

这下面的代码看得不清楚,直接上截图可能就清晰一些了

反射_自定义ORM注解.png

然后在表对应的实体类使用这个注解,是不是就很有感觉,有一种在写 Mybatis 的感觉

反射_自定义实体类ORM.png

然后框架底层通过反射,调用到我们实体类的注解上的信息,就取出信息对应到数据库自动连接执行操作

在这里我还没去研究过 Mybatis 源码,估计过阵子会去看看 Mybatis 的源码


在下面贴一个完整的测试代码,有需要的可以自行 copy 测试

public class Reflect05 {
    public static void main(String[] args) throws NoSuchFieldException {

        Class<Teacher> clazz = Teacher.class;
        Class<? extends Teacher> zhangsan = new Teacher(1, "zhangsan", 30).getClass();

        // 获取类上的注解(此时还没能获取 Field 上的注解)
        TableChen annotation = clazz.getDeclaredAnnotation(TableChen.class);
        System.out.println(annotation.value());

        // 获取某个 Field 上的注解
        Field field = zhangsan.getDeclaredField("name");
        FieldChen fieldAnnotation = field.getDeclaredAnnotation(FieldChen.class);
        System.out.println(fieldAnnotation.columnName());
    }
}

// 假如有个 Teacher 表,那就要对应一个 Teacher 类
@TableChen("db_teacher")
class Teacher {
    @FieldChen(columnName = "teacher_id", type = "bigint", length = 255)
    private int id;
    @FieldChen(columnName = "teacher_name", type = "varchar", length = 255)
    private String name;
    @FieldChen(columnName = "teacher_name", type = "int", length = 3)
    private int age;
    public Teacher() {}
    public Teacher(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId() {return id;}
    public void setId(int id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
}

/**
 * 对应 ORM 框架的表
 * Object Relationship Mapping(对象关系映射)
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableChen {
    String value();    // 表名
}


/**
 * 对应 ORM 框架的字段
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldChen {
    String columnName(); // 字段名
    String type();       // 字段类型
    int length();        // 字段长度
}