Java注解和反射机制

Java注解和反射机制

1.注解

1.1 注解介绍

注解(Annotation)是JDK 5.0 开始引入的新技术,其主要作用是:

  • 对程序作出解释,并且本身不是程序(这个作用与注释相同);
  • 可以被其他程序(如编译器等)读取。

Annotation的格式:注解是以@注解名格式再代码中存在的,而且还可以添加一些参数值,例如@SuppressWarnings(value = "unchecked")

Annotation的应用场景:注解可以附加在package、class、method和field等上面,相当于给这些元素添加了额外的辅助信息和说明信息,然后我们可以再通过 反射机制 编程实现对注解提供的数据源的访问和使用。

1.2 内置注解

内置注解为Java预先为我们准备的一些最常用的注解:

  • @Override:该注解定义在java.lang.Override中,只适用于修饰方法,表示声明的这个方法是重写的父类中的方法。
  • @Deprecated:该注解定义在java.lang.Deprecated中,可以修饰方法、属性、类,表示不鼓励程序员再去适用这样的元素,但并不代表不能使用;通常是因为该元素很危险或者已经有比该元素更好的替代品,即过期。
  • @SuppressWarnings:该注解定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息,并且该注解需要添加参数才能正确使用,这些参数是事先定义好了。如:
      1. @SuppressWarnings(value = "all")
      2. @SuppressWarnings(value = "unchecked")
      3.@SuppressWarnings(value = {"unchecked","deprecation"})等等…

1.3 元注解

元注解(meta-annotation)的作用是负责注解其他的注解,对自定义的注解进行说明和规定,即帮助完成其他注解的定义。元注解在java.lang.annotation包中,Java中定义了4个标准的元注解:

  • @Target:用于描述注解的使用范围,即定义的该注解可以在什地方使用。
  • @Retention:表示需要在什么级别保存该注解信息,用于描述注解的声明周期(源代码<编译文件<运行时)。
  • @Document:说明该注解将被包含在javadoc中。
  • @Inherited:说明子类可以继承父类中的该注解。

如下就是内置注解@Deprecated的源码,使用了多个元注解:

/** * A program element annotated &#64;Deprecated is one that programmers * are discouraged from using, typically because it is dangerous, * or because a better alternative exists. Compilers warn when a * deprecated program element is used or overridden in non-deprecated code. * * @author Neal Gafter * @since 1.5 * @jls 9.6.3.6 @Deprecated */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={
   CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
   
}

1.4 自定义注解

注解的定义与我们对接口的定义非常相似,只需要在interface关键字前面加上@符号即可,即@interface。完整格式为:public @interface 注解名 { 定义注解内容 }。并且注解同样与接口一样,可以声明方法,但作用缺与接口大相径庭:

  • 方法名称实际上就是注解的参数名称,方法即参数;
  • 方法返回值的数据类型则为参数所需的数据类型,并且只能是基本数据类型;
  • 可以通过default关键字来为参数设置默认值,一般经常使用空字符串或者0作为默认值。。如String value() default "Korin";表示默认值为korbin的字符串;
  • 如果只有一个参数成员,一般参数名为value,这样可以在使用注解时省略参数名,直接给值;
  • 注解参数必须要有值,如果有默认值可以不用显式指定,否则必须指明值。

如下即为简单声明的一个自定义的注解:

@Target({
   ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
   
    String value() default "";
    String[] names();
    int id() default 0;
}

2.反射机制

上文的注解本质上用来存放说明性信息和辅助信息的,除了Java自己的一些注解可以被编译器识别,那我们自定义的注解该如何使用这些信息呢?只声明却不用不是就没意义了吗?答案是结合Java的反射机制就可以很好的通过使用注解来完成很多工作。

2.1 Java反射机制概述

动态语言和静态的语言的概念:

  • 动态语言是一类在代码运行时还可以去改变其代码结构的语言,如在程序运行过程中,新的函数、对象和甚至代码都可以被引进,并且已有的函数还可以被删除或者修改。简单的说就是我们写好的代码可以根据我们的需求,动态的去改变其结构和逻辑。现在一些主要的动态语言有:Object-C、C#、JavaScript、PHP和Python等。
  • 静态语言则与动态语言相反,为了保证程序的安全性,在程序运行时结构不允许被改变,常见的静态语言有:Java、C和C++等。

Java不是动态语言,但是Java又可以称之为“准动态语言”。正是因为Java中 反射机制 的存在,使得Java有一定的动态性,使得Java编程变得更加灵活:

反射(Reflection)是Java在一定程度上可以被视为动态语言的关键,Java的反射机制允许程序在运行期间借助于反射API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。
 
在Java的JVM虚拟机中,加载完程序中的每一个类后,会在堆内存的方法区中生成一个Class类型的对象,Class本身就是Java中的一个类(java.lang.Class),并且每一个自定义的类只有一个Class对象;而且实际上每个Class对象对应其所对应的类的.class编译后的文件。这个Class对象包含了完整这个类在JVM中的完整数据结构,并提供了访问这些数据结构的接口。所以我们就可以通过这个Class对象对该类进行相关的操作。
 
下图为正常方式通过全类名创建类的实例化对象;而反射方式则是反过来的,通过实例化的对象名,得到该对象的类对象。这也正是反射机制的命名由来。
在这里插入图片描述

Java反射机制的优点和缺点:

  • 优点:可以实现动态创建对象和编译,体现出很大的灵活性。
  • 缺点:反射对性能有影响。因为使用反射机制相当于一种解释操作,我们可以告诉JVM希望它做什么并且它会满足我们的要求,这样的操作肯定总是慢于程序直接执行的。

反射机制的关键是:Class类;并且实际上,反射机制正是后续我们学习和使用的各种Java框架的设计灵魂~

2.2 获取Class类对象

Java中有提供了多种方式可以获取一个类的Class类对象:

  1. 若已知具体的类名,则可以通过该类的class属性获取Class类对象。该种获取Class类对象的方法最为安全可靠,且程序性能最高。
  2. 若已知某个类的实例对象,则可以通过调用该实例对象的getClass()方法获取其自己类的Class类对象。
  3. 若已知的是一个类的全类名,且该类在类路径下,则可以通过Class类的静态方法forName()来获取该类的Class类对象。
  4. 内置基本数据类型还可以直接通过静态成员变量TYPE获取Class类对象,如Class clazz = Integer.TYPE;

那么Java中有哪些类型有Class类对象呢?

  • class:外部类、成员内部类、静态内部类、局部内部类和匿名内部类。
  • interface:接口
  • []:数组
  • enum:枚举
  • @interface:注解(annotation)
  • 基本数据类型(primitive type)
  • void

2.2.1 类的class属性

Java中默认每个类都会一个class属性,该属性存储了该类的所有结构信息。调用该属性返回的是该类的Class对象。

示例自定义User类,并通过User类的class属性获取Class对象,打印输出全包类名:

public class Main {
   
    public static void main(String[] args) {
   
        Class<User> clazz = User.class;
        System.out.println(clazz);
        /* 运行结果:class top.korbin.reflection.User */
    }
}

class User {
   
    private Long id;
    private String name;

    public Long getId() {
   return id; }
    public void setId(Long id) {
   this.id = id; }
    public String getName() {
   return name; }
    public void setName(String name) {
   this.name = name; }
}

2.2.2 Object.getClass()方法

Java中的Object类中提供了public final native Class<?> getClass()方法实现通过对象实例获取其类对象;并且此方法会被所有子类继承。事实上Object类正是Java中所有类的父类,即使没有显式声明编译器也会默认加上,所以相当于类每个都有这个方法。

直接通过实例对象的getClass()方法获取其Class对象,打印输出全包类名:

public class Main {
   
    public static void main(String[] args) {
   
        User user = new User();
        Class<? extends User> clazz = user.getClass();
        System.out.println(clazz);
        /* 运行结果:class top.korbin.reflection.User */
    }
}

2.2.3 Class.forName()方法

Java反射机制的关键类:Class,该类提供了public static Class<?> forName(String className)静态方法,通过全类名作为该方法的参数,获得类对象。如果所传字符串参数不合法,则需要捕获异常:

public class Main {
   
    public static void main(String[] args) {
   
        try {
   
            Class<?> clazz = Class.forName("top.korbin.reflection.User");
            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
   
            e.printStackTrace();
        }
        /* 运行结果:class top.korbin.reflection.User */
    }
}

2.3 ClassLoader类加载器

类加载器即负责类的加载,并为之生成对应的Class类对象。虽然我们不需要关心类加载机制,但是了解这个类加载机制我们就能更好的理解程序的运行~

2.3.1 类加载过程

在了解类加载过程之前,我们还得先基本了解 Java虚拟机中的内存结构
在这里插入图片描述
什么时候进行类加载?

一般来说,只有在第一次 主动调用 某个类时才会去进行类加载,即只初始化一次。如果一个类有父类,会先去加载其父类,然后再加载其自身。JVM 规定了以下六种情况为 主动调用:

  1. 一个类的实例被创建(new操作、反射、cloning,反序列化);
  2. 调用类的static方法;
  3. 使用或对类/接口的static属性进行赋值时(这不包括final常量与在编译期确定的常量表达式);
  4. 当调用 API 中的某些反射方法时;
  5. 子类被初始化;
  6. 被设定为 JVM 启动时的启动类(具有main方法的类)。

其余的情况皆为 被动调用,即JVM会自动去加载,如final常量。

Java类在JVM虚拟机的加载过程:

在了解了上述的基础知识之后,对于类的加载过程,当程序主动使用一个类时,如果该类还未被加载到Java内存中,也就是满足上述的主动调用和被动调用情况下时,JVM则会通过如下三个步骤来对该类进行初始化:
在这里插入图片描述

  1. 加载(Loading ):该过程主要由Classloader完成,将.class文件的字节码内容加载到JVM内存方法区中,并将类的数据转换成方法区对应的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
  2. 链接(Linking):将Java类的二进制代码合并到JVM的运行环境状态之中的过程。
    1. 验证(Verification):确保加载的类信息符合JVM规范,没有安全方面的问题,即代码语法校验。
    2. 准备(Preparation):正式为类成员变量(不包括实例变量)分配内存并设置默认初始值的阶段,这些内存都将在方法区中进行分配。这个阶段不会执行任何代码,而只是简单的根据变量类型决定初始值。
    3. 解析(Resolution):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程,即初始化final常量的过程。
  3. 初始化(Initialization):这个阶段JVM会去真正执行代码,具体包括:代码块(static与非static)、构造函数、变量显式赋值。这些代码执行的顺序遵循以下两个原则:
    1. 有static修饰的先初始化static,然后是非static的;
    2. 先成员变量显式初始化,再构造代码块初始化,最后才调用构造函数进行初始化。

在这里插入图片描述
上图为测试运行在不同地方输出静态成员变量的值,结合上述的类加载过程观察成员变量值初始化的过程:

public class Main {
   
    public static void main(String[] args) throws ClassNotFoundException {
   
        System.out.println("在main主函数中直接输出其num静态成员变量的值:"+A.num);
        System.out.println("=============================================");
        new A();
        System.out.println("在main主函数中创建A类对象后,再输出其num静态成员变量的值:"+A.num);
    }
}

class A {
   
    public static int num = 100;
    static {
   
        System.out.println("A类的静态代码块初始化");
        System.out.println("A类的静态代码块中num静态变量值为:"+num);
        System.out.println("A类的静态代码块中给num赋值300");
        num = 300;
    }
    public A(){
   
        System.out.println("A类的无参构造方法初始化");
        System.out.println("A类的无参构造方法中num静态变量值为:"+num);
        System.out.println("A类的无参构造方法中给num赋值500");
        num = 500;
    }
}

2.3.2 类加载器的分类和作用

类加载器的作用是将类(Class)装在进JVM内存。JVM规范定义了如下类型的类加载器:

  • 引导类加载器(Bootstrap Classloader):用C++编写,是JVM自带的类架子啊器,负责Java平台核心库,用来装载核心类库(如String、System等),jre/lib/rt.jar下的类都是核心类。该类加载器无法直接获取。
  • 扩展类加载器(Extension Classloader):负责jre/lib/ext目录下的jar包或者-D java.ext.dirs指定目录下的jar包装入工作库。
  • 系统类加载器(System Classloader/AppClassLoader ):负责java -classpath或者-D java.class.path所指目录下的类与jar包装入工作库。是最常用的类加载器。
    在这里插入图片描述
public class Main {
   
    public static void main(String[] args) throws ClassNotFoundException {
   
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //获取系统类加载器的父类:扩展类加载器
        ClassLoader extensionClassLoader = systemClassLoader.getParent();
        System.out.println(extensionClassLoader);
        //获取扩展类加载器的父类:引导类加载器(获取不到,为null)
        ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
        System.out.println(bootstrapClassLoader);
        //获取当前类的类加载器
        ClassLoader currentClassLoader = new Main().getClass().getClassLoader();
        System.out.println(currentClassLoader);
        //获取JDK内置的Object类的类加载器
        ClassLoader objectClassLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(objectClassLoader);
    }
}

2.3 Class类对象的常用方法

序号 方法名 功能描述
1 getName() 返回String形式的该类的名称。
2 newInstance() 根据某个Class对象产生其对应类的实例,它调用的是此类的默认构造方法(没有默认无参构造器会报错)
3 getClassLoader() 返回该Class对象对应的类的类加载器。
4 getSuperClass() 返回某子类所对应的直接父类所对应的Class对象
5 isArray() 判定此Class对象所对应的是否是一个数组对象
6 getComponentType() 如果当前类表示一个数组,则返回表示该数组组件的 Class 对象,否则返回 null。
7 getConstructor(Class[]) 返回当前 Class 对象表示的类的指定的公有构造子对象。
8 getConstructors() 返回当前 Class 对象表示的类的所有公有构造子对象数组。
9 getDeclaredConstructor(Class[]) 返回当前 Class 对象表示的类的指定已说明的一个构造子对象。
10 getDeclaredConstructors() 返回当前 Class 对象表示的类的所有已说明的构造子对象数组。
11 getDeclaredField(String) 返回当前 Class 对象表示的类或接口的指定已说明的一个域对象。
12 getDeclaredFields() 返回当前 Class 对象表示的类或接口的所有已说明的域对象数组。
13 getDeclaredMethod(String, Class[]) 返回当前 Class 对象表示的类或接口的指定已说明的一个方法对象。
14 getDeclaredMethods() 返回 Class 对象表示的类或接口的所有已说明的方法数组。
15 getField(String) 返回当前 Class 对象表示的类或接口的指定的公有成员域对象。
16 getFields() 返回当前 Class 对象表示的类或接口的所有可访问的公有域对象数组。
17 getInterfaces() 返回当前对象表示的类或接口实现的接口。
18 getMethod(String, Class[]) 返回当前 Class 对象表示的类或接口的指定的公有成员方法对象。
19 getMethods() 返回当前 Class 对象表示的类或接口的所有公有成员方法对象数组,包括已声明的和从父类继承的方法。
20 isInstance(Object) 此方法是 Java 语言 instanceof 操作的动态等价方法。
21 isInterface() 判定指定的 Class 对象是否表示一个接口类型
22 isPrimitive() 判定指定的 Class 对象是否表示一个 Java 的基类型。
23 newInstance() 创建类的新实例

2.4 反射动态获取完整的类结构

有了Class类对象,我们就可以通过调用上问中的Class对象的方法,动态的获取一个类的完整类结构(成员变量和方法等):

public class Main {
   
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
   
        //模拟获取到了一个向上转型的Object对象
        Object object = new User(10007L,"Korbin");
        Class clazz = object.getClass();
        //获取类的结构,请自行打印输出查看
        System.out.println(clazz.getName());//获取全类名
        System.out.println(clazz.getSimpleName());//获取单独的类名
        Field[] fields = clazz.getFields();//获取类的所有pulic公有属性
        Field[] declaredFields = clazz.getDeclaredFields();//获取类的所有属性,包括public和private属性
        Field name = clazz.getField("name");//通过指定的属性名获取属性的对象
        Method[] methods = clazz.getMethods();//获取当前类及其父类的全部public方法
        Method[] declaredMethods = clazz.getDeclaredMethods();//获取当前类的所有方法,包括public和private方法
        Method getName = clazz.getMethod("getName", null);//通过指定的方法名和方法参数获取方法的对象
        Constructor[] constructors = clazz.getConstructors();//获取类的全部public构造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();//获取类的全部构造方法,包括private和public构造方法
        Constructor constructor = clazz.getConstructor(Long.class, String.class);//通过构造参数获取指定构造参数
    }
}

2.5 反射动态操作对象实例

2.5.1 创建对象实例

既然有了Class类对象,我们还可以直接通过newInstance()方法动态的创建该类的对象示例,但需要满足如下2个条件:

  • 类必须有一个无参构造器
  • 类的构造器的访问权限需要足够。即不能为private

当然,没有无参构造器的类同样可以创建对象实例。只需要先通过Class类对象调用上文中介绍的构造方法getConstructor(Class ... parameterTypes),传入构造器参数类型,然后通过该构造器Constructor对象调用newInstance方法传入参数值后拿到类的实例化的对象。

public class Main {
   
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
   
        Class clazz = User.class;//也可以根据Class.forName获取Class对象
        Object user = clazz.newInstance();
        System.out.println(user);

        Constructor constructor = clazz.getConstructor(Long.class, String.class);
        User user1 = (User) constructor.newInstance(1007L,"Korbin");
        System.out.println(user1);
        /*运行结果: User{id=null, name='null'} User{id=1007, name='Korbin'} */
    }
}

2.5.2 执行对象实例的方法

上述通过反射机制动态拿到了类的对象实例后,我们就可以进一步调用执行该实例对象的方法了。通过上文介绍的Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);方法拿到Method对象,然后调用其invoke();方法,并传入类方法的参数值(实例对象和实例对象方法的参数值),实现对该方法的调用:

public class Main {
   
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
   
        Class clazz = User.class;
        User user = (User) clazz.newInstance();
        System.out.println(user);
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
        setNameMethod.invoke(user,"Korbin111");
        System.out.println(user);
        /* 运行结果: User{id=null, name='null'} User{id=null, name='Korbin111'} */
    }
}

注意,如果调用的方法是private私有方法,将没有足够的权限执行方法而抛出IllegalAccessException异常,但是我们可以通过在执行方法前先调用MethodsetAccessible(true)方法关闭访问安全检查的开关。

2.5.3 操作对象实例的成员变量

反射拿到类的实例对象后,还可以通过上文介绍的Field name = clazz.getDeclaredField("name");方法对类的属性(成员变量)进行操作。如通过Fieldset()方法传入实例对象和属性值作为参数即可实现对属性的赋值;通过get()方法传入属性名作为参数可以实现获得属性值:

public class Main {
   
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
   
        Class clazz = User.class;
        Object user = clazz.newInstance();
        System.out.println(user);
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);//关闭访问安全检查开关
        name.set(user,"Korbin666");
        System.out.println(name.get(user));
        System.out.println(user);
        /* 运行结果: User{id=null, name='null'} Korbin666 User{id=null, name='Korbin666'} */
    }
}

2.6 反射和普通方式性能对比分析

public class Main {
   
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
   
        test1();
        test2();
        test3();
    }

    //普通方式调用对象的方法
    public static void test1(){
   
        User user = new User();
        long startTime = System.currentTimeMillis();
        //执行10亿次调用对象方法的操作
        for (int i = 0; i < 1000000000; i++) {
   
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行10亿次时间:"+(endTime-startTime)+"ms");
    }

    //反射方式调用
    public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
   
        Object user = new User();
        Class clazz = user.getClass();
        Method getName = clazz.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        //执行10亿次调用对象方法的操作
        for (int i = 0; i < 1000000000; i++) {
   
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行10亿次时间:"+(endTime-startTime)+"ms");
    }

    //反射方式并关闭安全监测情况下调用
    public static void test3() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
   
        Object user = new User();
        Class clazz = user.getClass();
        Method getName = clazz.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        //执行10亿次调用对象方法的操作
        for (int i = 0; i < 1000000000; i++) {
   
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射并关闭安全监测方式执行10亿次时间:"+(endTime-startTime)+"ms");
    }

}

运行结果:
在这里插入图片描述
如果我们反射调用非常多,建议关闭访问安全检查开关,提高程序执行效率~

2.7 反射操作泛型

Java中的泛型采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,以确保数据的安全性和免去强制类型转换所带来的问题。所以,一旦编译完成,所有和泛型有关的类型将全部操作。

为了通过反射来操作泛型,Java新增了ParamterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归为Class类型,但是又和原始类型齐名的类型。

  • ParamterizedType:表示一种参数化类型,比如Collection<String>
  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型;
  • TypeVariable:是各种类型变量的公共父接口;
  • WildcardType:代表一种通配符类型表达式。

示例通过反射机制获取泛型:

public class Main {
   
    public static void main(String[] args) throws NoSuchMethodException {
   
        Method test1 = Main.class.getMethod("test1", Map.class, List.class);
        //获得方法的参数类型
        Type[] genericParameterTypes = test1.getGenericParameterTypes();
        for (Type genericParameterType: genericParameterTypes){
   
            System.out.println(genericParameterType);
            //获取参数类型的结构化参数类型,即<>中的类型
            if (genericParameterType instanceof ParameterizedType){
   
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments){
   
                    System.out.println(actualTypeArgument);
                }
            }
        }
        System.out.println("=======================================================");
        Method test2 = Main.class.getMethod("test2", null);
        //获得返回值类型
        Type genericReturnType = test2.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType){
   
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments){
   
                System.out.println(actualTypeArgument);
            }
        }

    }
    public void test1(Map<String,User> map, List<User> list){
   
        System.out.println("test1");
    }
    public Map<String,User> test2(){
   
        System.out.println("test2");
        return null;
    }

}

在这里插入图片描述

2.8 反射获取注解信息

首先,了解一下什么是 ORM, 即Object Reletionship Mapping,也就是对象关系映射表。如下图:
在这里插入图片描述

  • 类和表结构对应;
  • 属性和表字段对应;
  • 对象和表的一行记录对应。

那么我们如何通过反射机制和注解搭配来完成一个类和表结构的映射关系呢?

public class Main {
   
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
   
        Class clazz = User.class;
        //通过反射获得类的注解
        Annotation[] annotations = clazz.getAnnotations();
        //获得指定的注解
        Table table = (Table)clazz.getAnnotation(Table.class);
        System.out.println(table);
        //获取指定注解的值
        System.out.println("Table注解的value参数值:"+table.value());
        //获取类属性的注解
        Field id = clazz.getDeclaredField("id");
        TableField tableField = id.getAnnotation(TableField.class);
        System.out.println(tableField);
        System.out.println("TableField注解的name参数值:"+tableField.name());
        System.out.println("TableField注解的type参数值:"+tableField.type());
        System.out.println("TableField注解的length参数值:"+tableField.length());
    }
}

@Table("user")
class User {
   
    @TableField(name = "id", type = "Long",length = 10)
    public Long id;
    @TableField(name = "name", type = "String",length = 8)
    public String name;
}

/** * 类名的注解 * @author Korbin */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
   
    String value();
}

/** * 属性注解 * @author Korbin */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableField{
   
    String name();
    String type();
    int length() default 1;
}
本文来源MrKorbin,由架构君转载发布,观点不代表Java架构师必看的立场,转载请标明来源出处:https://javajgs.com/archives/25257

发表评论