注解与反射详解(含手写ORM底层demo、Javabean定义)

        本文对注解和反射进行了系统的介绍,对ORM框架底层的实现写了个demo,该demo能够反映出注解和反射技术之间的联系

目录

一、注解

1、简介

2、学习注解的重点

3、内置注解

4、元注解与自定义注解

4.1、元注解

4.2、自定义注解

二、反射

1、反射概述

2、类加载器

2.1 使用类加载器加载配置文件

3、Class对象及获取Class对象的方式

4、通过反射获取Constructor(构造器)

4.1 通过class对象获取一个类的构造方法

4.2 Constructor创建对象

4.3 举例

5、通过反射获取Method(方法)

5.1 通过class对象获取一个类的方法

5.2 Method执行方法

5.3 举例

6、通过反射获取Field(属性)

6.1 通过class对象获取一个类的属性

6.2 Field获取属性

6.3 举例

三、反射与注解的联系

3.1 通过反射获取注解的信息

3.2 反射+注解实现ORM框架底层demo

3.2.1 ORM框架介绍

3.2.2 利用注解+反射实现ORM的demo

四、内省机制

4.1 Javabean与内省机制简介

 4.2 内省机制的使用


一、注解

1、简介

  • java注解(Annotation)又称java标注,是JDK5.0引入的一种注释机制
    • 注解可以和注释联系起来,注释是给程序员看的,注解是给机器看的。注解相当于把代码注释到运行时使用,可以简化开发
  • java语言中的类、方法、变量、参数和包都可以被标注
  • java标注可以通过反射获取标注内容,在编译器生成类文件时,标注可以被嵌入到字节码中。java虚拟机可以保留标注内容,在运行时可以获取到标注内容
    • 也支持自定义的java标注,即自定义编写注解
  • 主要用途
    • 编译格式的检查
    • 反射中进行解析
    • 生成帮助文档
    • 跟踪代码依赖情况等

2、学习注解的重点

  • 学好注解的关键是理解其语法和用法
  • 学习步骤
    • 注解的概念(即简介中的内容)
    • 怎么使用内置注解
    • 怎么自定义注解
    • 反射中怎么获取注解(将在后面的反射中讲解)

3、内置注解

  • 下面常用的内置注解会有举例

  • @Override
    • 用于标注重写的方法,在编译时会进行格式的检查
    • 在方法定义时使用
    • 定义在java.lang.Override


  • @Deprecated
    • 用于标记废弃的包、类或方法
      • 废弃的方法还能用,在使用时会加一条删除线,不建议使用废弃方法
    • 定义在java.lang.Deprecated


  •  @SafeVarargs
    • java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告

  • @FunctionInterface
    • java8开始支持,用于标识一个匿名函数或函数式接口
      • 函数式接口指的是接口中只有一个抽象方法的接口
  • 函数式接口定义多个抽象方法时会报错

  •  定义一个抽象方法时不报错


  •  @Repeatable
    • java8开始支持,标识某注解可以在同一个声明上使用多次

  • @SuppressWarnings
    • 抑制编译时的警告信息
    • 定义在java.lang.SuppressWarnings
    • 有三种使用方式
      • @SuppressWarnings("unchecked")
        • 抑制单类型的警告
      • @SuppressWarnings("unchecked","rawtypes")
        • 抑制多类型的警告
      • @SuppressWarnings("all")
        • 抑制所有类型的警告
  • 参数列表

4、元注解与自定义注解

4.1、元注解

  • 指的是作用在其他注解上的注解
    • 这里的其他注解,通常指的是我们自定义的注解
  • 元注解的类型
    • @Retention
      • 标识这个注解怎么保存。是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
    • @Documented
      • 标记这些注解是否包含在用户文档(javadoc)中
    • @Target
      • 标记这个注解应该是哪种java成员
      • 即标明该注解的作用范围,是作用在类上,还是作用于方法或其他位置
      • 参数传入的是ElementType中的常量
    • @Inherited
      • 标记这个注解是自动继承的
        • 子类会继承父类使用的注解中被@Inherited修饰的注解
        • 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰
        • 类实现接口时不会继承任何接口中定义的注解

4.2、自定义注解

4.2.1 注解架构图

  •  Annotation与RententionPolicy与ElementType的关系
    • 每一个Annotation对象,都会有唯一的RententionPolicy属性
    • 每一个Annotation对象,都会有1到n个ElementType属性
  • ElementType(注解的用途类型)
    • 每一个Annotation对象,都会有1到n个ElementType属性
    • 当Annotation与某个ElementType关联时,就意味着Annotation有了某种用途
      • 例如,若一个Annotation对象是METHOD类型,则该Annotation只能用来修饰方法
    • ElementType是枚举类,具体的常量值如下
    package java.lang.annotation;

    public enum ElementType {
        TYPE, /* 类、接口(包括注释类型)或枚举声明 */
        FIELD, /* 字段声明(包括枚举常量) */
        METHOD, /* 方法声明 */
        PARAMETER, /* 参数声明 */
        CONSTRUCTOR, /* 构造方法声明 */
        LOCAL_VARIABLE, /* 局部变量声明 */
        ANNOTATION_TYPE, /* 注释类型声明 */
        PACKAGE /* 包声明 */
    }
  • RetentionPolicy(注解作用域策略)
    • 每一个Annotation对象,都会有唯一的RententionPolicy属性
      • a)、若Annotation的类型为SOURCE,则意味着Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了。例如@Override标志就是一个Annotation,当它修饰一个方法的时候,就意味着该方法覆盖父类的方法,并且在编译期间会进行语法检查。编译器处理完后,@Override就没有任何作用了
      • b)、若Annotation的类型为CLASS,则意味着编译器将Annotation存储于类对应的.class文件中,它是Annotation的默认行为
      • c)、若Annotation的类型为RUNTIME,则意味着编译器将Annotation存储于class文件中,并且可由JVM读入
    • 总结来说就是,RetentionPolicy通常会定义为RUNTIME,能够确保JVM可以获取到注解
      • 范围比较:RUNTIME>CLASS>SOURCE
package java.lang.annotation;

public enum RetentionPolicy {
    SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
    CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
    RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

4.2.2 定义格式

@interface 自定义注解名{
    参数类型 方法名();
        ......
}

或

@interface 自定义注解名{
    参数类型 方法名1()default 默认值;
        ......
}

4.2.3 注意事项

  • 定义的注解,自动继承了java.lang.annotation.Annotation接口
  • 注解中的每一个方法,实际是声明的注解配置参数
    • 方法的名称就是配置参数的名称
    • 方法的返回值类型,就是配置参数的类型,只能是基本数据类型/Class/String/enum/数组
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值

4.2.4 自定义注解举例

  • 在idea中创建注解

  •  代码
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    String value() default "null";
}

上面定义了一个注解MyAnnotation,下面是对部分代码的解析

  • @interface
    • 使用@interface定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解是一个Annotation
    • 定义Annotation必须使用@interface
    • 注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节都由编译器完成。通过@interface定义注解后,该注解不能继承其他的注解或接口
  • @Documented
    • 类和方法的Annotation在缺省的情况下是不会出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中
    • 定义Annotation时,@Documented可有可无;如果没定义,则该Annotation不会出现在javadoc中
  • @Target(ElementType.TYPE)
    • 用于指定Annotation的类型属性
    • 这里指定了Annotation的类型是ElementType.TYPE,意味着该注解是用来修饰类、接口(包括注释类型)或枚举声明
    • 定义Annotation时,@Target可有可无
      • 如果有,则该注解只能用于指定的地方
      • 如果没有,则该注解可以用于任何地方
  • @Retention(RetentionPolicy.RUNTIME)
    • 用于指定Annotation的策略属性
    • 这里指的是该Annotation册策略是RetentionPolicy.RUNTIME。编译器会将该Annotation信息保留在.class文件中,并且能被JVM读取
    • 定义Annotation时,@Retention可有可无
      • 如果没有,默认是RetentionPolicy.CLASS

二、反射

1、反射概述

  • 定义
    • java反射机制是在运行状态中,获取任意一个类的结构,通过该类的结构可以创建对象、得到方法或属性、执行方法。这种在运行状态动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制
  • 个人解析
    • 反射是java的高级特性之一
    • 通过反射可以获取任意一个类的结构,这个类可以是不存在的,可以在未来定义。比如在spring等框架就大量使用了反射,其中IOC容器可以管理对象,管理的这些对象是我们以后在项目中定义的,也就是说反射可以看成是面向未来编程
    • 反射具有动态性,是java动态编程的特点,即能够在程序运行起来之后,再动态加载原本没定义的类或新的代码
      • java的动态机制在移动端用的比较多
    • 反射是一种反封装机制,体现在“任意”二字上。我们可以通过反射技术操作一个私有的属性或方法,甚至调用私有化的构造器创建一个对象
    • 正常情况下是先有类后再有对象,反射与此不同,它是先运行代码,再加载类;或者说是外部传入一个对象,可以通过反射反推出这个对象的类结构

2、类加载器

  • 类加载器(Classloader)是java运行时环境(JRE,即Java Runtime Environment)的一部分,负责动态加载java类到java虚拟机的内存空间中
  • java默认有三种类加载器
类加载器名称 描述
BootstrapClassLoader(引导启动类加载器)
 
嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,引
导启动类加载器无法被应用程序直接使用
 
ExtensionClassLoader(扩展类加载器)
 
是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类
库。它的父加载器是BootstrapClassLoader
 
App ClassLoader(应用类加载器)
 
App ClassLoader是应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文
件。它的父加载器为ExtensionClassLoader
 
  • 程序员自己操作加载配置文件、类等使用的是应用类加载器

面试题:多个类加载器如何避免类加载重复?

  • 类通常是按需加载,即第一次使用该类时才加载。学习类加载器时,需要掌握java的委派概念
  • 多个类加载器避免类加载重复的方法是:使用双亲委派模型
    • 如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试去加载。委派的好处就是避免有些类被重复加载
  • 人话解析双亲委派模型
    • 三个类加载器的关系如下图所示,最底层是我们使用的应用类加载器。从上往下,我们简称a(引导启动类加载器)、b(扩展类加载器)、c(应用类加载器)
    • 当我们加载一个类时,使用的是c。c会委派b去加载,b不是最顶层,所以又会委派a去加载。如果a加载了,b和c就不用加载了,可以共用a加载后的类结构。如果a不想加载该类,就会返回给b,b如果加载了也是大家共用。如果b也不想加载,那就让委派者自己加载,也就是c自己加载。不论谁加载了,类结构都是共用的。这种委派机制能够避免重复加载同一个类。

2.1 使用类加载器加载配置文件

  • 通过类加载器加载资源文件,默认加载的是项目src路径下的文件
    • 读取步骤
      • 通过类名.class获取类的类对象
      • 通过类对象的getClassLoader()方法获取应用类加载器
      • 通过类加载器的getResourceAsStream()方法获取src包下的输入流,入参要读取的文件名
      • 通过缓冲流封装上面得到的输入流,读取文件内容
  • 项目文件截图如下,src包下有一个book.txt文件,里面写了一句诗

  •  代码如下
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //获取类加载器
        ClassLoader c = Demo1.class.getClassLoader();
        //通过类加载器获取输入流
        InputStream is = c.getResourceAsStream("book.txt");
        //使用缓冲流读取文件内容
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        //读取内容
        System.out.println(reader.readLine()); //床前明月光,疑是地上霜
    }
}
  • 如果给项目添加resource root目录(资源根目录),则getResourceAsStream()方法读取的不再是src下的文件,而是资源根目录下的文件
  • 配置资源根目录的步骤
    • 在模块目录下创建一个文件夹,命名为source

 

  •  此时我们在source目录下同样创建一个book.txt,里面的内容为Hello World!

  •  再次运行上面的代码,输出结果是Hello World!

3、Class对象及获取Class对象的方式

Class对象简介

  • 想要了解一个类,必须先获取到该类的字节码文件对象(即类名.class)。在java中,每一个字节码文件被加载到内存后,都存在一个对应的Class类型的对象中,这就是Class对象,也称类对象
    • Class可以理解为是一种表示类的数据类型,即Class是类 类型

 获取Class对象的三种方式

1、在编写代码时,知道类的名称,且类已经存在,则可以通过
    包名.类名.class得到一个类的类对象;
2、如果拥有类的对象,可以通过
    对象.getClass()得到一个类的类对象;
3、如果在编写代码时,知道类的名称,但是类不存在,则可以通过
    Class.forName(包名+类名)得到一个类的类对象

以上三种方式在调用时,如果类在内存中不存在,则会加载到内存;
如果类已经在内存中存在,则不会重复加载该类,而是重复利用。
(一个class文件在内存中不会存在两个类对象)

以上三种方式获取同一个类的类对象时,获取的是同一个类对象。
即使用==判断的结果是true
  • 特殊的类对象
    • 基本数据类型的类对象:
      • 基本数据类型.class
      • 包装类.type
    • 基本数据类型包装类对象:
      • 包装类.class
  • 在获取Class对象时,Class可以指定泛型,也可以不指定;不指定的话后面需要强制类型转换

举例

  • 目录结构如下

  •  代码如下
import com.java.Person;

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        //这里的Person放在src下,没有
        //第一种方式:通过包名.类名.class加载类
        Class<Person> c1 = Person.class;
        System.out.println(c1); //class com.java.Person

        //第二种方式:通过类的对象获取类的信息
        Person p = new Person();
        Class<Person> c2 = (Class<Person>) p.getClass();
        System.out.println(c2); //class com.java.Person

        //第三种方式:使用Class.forName()
        //该类不存在时抛出异常
        Class<Person> c3 = (Class<Person>) Class.forName("com.java.Person");
        System.out.println(c3); //class com.java.Person
        System.out.println(c1==c2 && c2==c3); //true
    }
}

4、通过反射获取Constructor(构造器)

4.1 通过class对象获取一个类的构造方法

1. 通过指定的参数类型, 获取指定的单个构造方法 (public修饰的)

        getConstructor(参数类型的class对象数组)
        例如:
                构造方法如下: Person(String name,int age)
                得到这个构造方法的代码如下:
                Constructor c = p.getClass().getConstructor(String.class,int.class);

            或者:Constructor c = p.getClass().getConstructor(new Class[]{String.class,int.class});

2. 获取构造方法数组 (public修饰的)
        getConstructors();

3. 获取所有权限的单个构造方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)
        getDeclaredConstructor(参数类型的class对象数组)

4. 获取所有权限的构造方法数组 (除继承以外所有的:包含私有, 共有, 保护, 默认)
        getDeclaredConstructors();

4.2 Constructor创建对象

常用方法:
       1、 newInstance(Object... para)

                调用这个构造方法,把对应的对象创建出来

                参数:是一个Object类型的可变参数,传递的参数顺序,必须匹配构造方法中形式参数列表的顺序

       2、  setAccessible(boolean flag)
                如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)

4.3 举例

  • 定义一个Person类
    • 含全参构造器、无参构造器、私有化的单属性构造器
    • get、set方法
    • toString方法
package com.java;

import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    private Person(String name) {
        this.name = name;
    }

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }
}
  • 代码
package com;

import com.java.Person;

import java.lang.reflect.Constructor;

public class demo3 {
    public static void main(String[] args) throws Exception {
        //获取Class对象
        Class<Person> c = (Class<Person>) Class.forName("com.java.Person");
        //获取无参构造器
        Constructor<Person> constructor = c.getConstructor();
        //通过无参构造器创建对象
        Person p1 = constructor.newInstance();
        System.out.println(p1); //Person{name='null', age=0}

        //获取全参构造器
        Constructor<Person> constructor1 = c.getConstructor(String.class, int.class);
        //通过全参构造器创建对象
        Person p2 = constructor1.newInstance("张三", 18);
        System.out.println(p2); //Person{name='张三', age=18}

        //获取私有构造器,必须通过getDeclaredConstructor方法获取
        Constructor<Person> constructor2 = c.getDeclaredConstructor(String.class);
        //设置权限为true,否则无法创建对象
        constructor2.setAccessible(true);
        Person p3 = constructor2.newInstance("李四");
        System.out.println(p3); //Person{name='李四', age=0}
    }
}

5、通过反射获取Method(方法)

5.1 通过class对象获取一个类的方法

1. getMethod(String methodName , class.. clss)
        根据参数列表的类型和方法名, 得到一个方法(public修饰的)
2. getMethods();
        得到一个类的所有方法 (public修饰的)
3. getDeclaredMethod(String methodName , class.. clss)
        根据参数列表的类型和方法名, 得到一个方法(除继承以外所有的:包含私有, 共有, 保护, 默认)
4. getDeclaredMethods();
        得到一个类的所有方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)

5.2 Method执行方法

invoke(Object o,Object... para) :
        调用方法 ,
                参数1. 要调用方法的对象
                参数2. 要传递的参数列表
getName()
        获取方法的方法名称
setAccessible(boolean flag)
        如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)

5.3 举例

  • 在上面Person类的基础上,加一个私有方法setName2

package com.java;

import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    private Person(String name) {
        this.name = name;
    }

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    //添加私有方法
    private void setName2(String name){
        this.name = name;
        this.age = 666;
    }
}
  • 代码
import com.java.Person;

import java.lang.reflect.Method;

public class Demo3 {
    public static void main(String[] args) throws Exception {
        //获取类对象
        Class<Person> c = (Class<Person>) Class.forName("com.java.Person");
        //获取类的构造器并创建一个对象
        Person p1 = c.getConstructor().newInstance();
        //获取c的setName()方法
        Method setName = c.getMethod("setName", String.class);
        //p1对象调用setName方法
        setName.invoke(p1,"张三");
        System.out.println(p1); //Person{name='张三', age=0}

        //获取c的私有方法setName2()
        Method setName2 = c.getDeclaredMethod("setName2", String.class);
        //设置权限
        setName2.setAccessible(true);
        //p1调用setName2方法
        setName2.invoke(p1,"李四");
        System.out.println(p1); //Person{name='李四', age=666}
    }
}

6、通过反射获取Field(属性)

6.1 通过class对象获取一个类的属性

1. getDeclaredField(String filedName)
        根据属性的名称, 获取一个属性对象 (所有属性)
2. getDeclaredFields()
        获取所有属性
3. getField(String filedName)
        根据属性的名称, 获取一个属性对象 (public属性)
4. getFields()
        获取所有属性 (public)

6.2 Field获取属性

常用方法:

1. get(Object o);

        参数:要获取属性的对象

        获取指定对象的此属性值

获取指定对象的此属性值
        2. set(Object o , Object value);

        参数1. 要设置属性值的 对象
        参数2. 要设置的值
        设置指定对象的属性的值
3. getName()
        获取属性的名称
4. setAccessible(boolean flag)
        如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的属性)

6.3 举例

  • 在前面Person的基础上加一个公有的属性hobby
package com.java;

import java.io.Serializable;
import java.util.Objects;

public class Person implements Serializable {
    private String name;
    private int age;
    public String hobby;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby='" + hobby + '\'' +
                '}';
    }

    private Person(String name) {
        this.name = name;
    }

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    //添加私有方法
    private void setName2(String name){
        this.name = name;
        this.age = 666;
    }
}
  • 代码
import com.java.Person;

import java.lang.reflect.Field;

public class Demo4 {
    public static void main(String[] args) throws Exception {
        //获取类对象
        Class<Person> c = (Class<Person>) Class.forName("com.java.Person");
        //通过构造器创建一个对象
        Person p = c.getConstructor().newInstance();
        //获取类的公有属性
        Field hobby = c.getField("hobby");
        //设置p对象的hobby属性
        hobby.set(p,"敲代码");
        System.out.println(p); //Person{name='null', age=0, hobby='敲代码'}
        //获取设置的hobby
        System.out.println(hobby.get(p)); //敲代码

        //获取私有属性
        Field name = c.getDeclaredField("name");
        //设置权限为true
        name.setAccessible(true);
        //设置p的name属性
        name.set(p,"小明");
        System.out.println(p); //Person{name='小明', age=0, hobby='敲代码'}
    }
}

三、反射与注解的联系

3.1 通过反射获取注解的信息

  • 获取类/属性/方法的全部注解对象
Annotation[] annotations01 = Class/Field/Method.getAnnotations();
for (Annotation annotation : annotations01) {
    System.out.println(annotation);
}
  • 根据类型获取类/属性/方法的注解对象
注解类型 对象名 = (注解类型) c.getAnnotation(注解类型.class);

3.2 反射+注解实现ORM框架底层demo

3.2.1 ORM框架介绍

  • 下面图片的内容摘自百度百科

  •  人话
    • ORM框架就是对象关系映射,指的是把一个java的对象通过此模型映射到数据库表中,即根据对象的类名、字段名等,自动在数据库表中生成一个与之对应的数据库表,并填上相应字段的值,能够帮助程序员免去编写创建数据库表等基本的操作

3.2.2 利用注解+反射实现ORM的demo

思路:

1、定义一个注解TableAnnotation,用于修饰一个类,关联类名和数据库表名;

2、定义一个注解ColumnAnnotation,用于修饰类中的属性,关联属性名和数据库字段名;

3、定义一个类,使用@TableAnnotation修饰类,并指定对应数据库表的表名;使用@ColumnAnnotation修饰类的属性,并指定对应数据库字段的名及相应的值;

4、通过反射获取类的Class对象,通过Class对象获取修饰类的注解,进而获取注解中的内容,该内容表示的是类对应的数据库表名,可以通过该值去使用sql语句创建数据库表;

5、通过反射获取类的Class对象,再通过Class对象获取类的所有属性Field。使用Field获取对应的注解,并获取注解中的内容。根据注解中的内容去创建数据库的字段并赋值。

代码:

  • 定义@TableAnnotation注解
import java.lang.annotation.*;

//用于将类名映射到数据库表名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TableAnnotation {
    /**
     * 描述数据库表的名称
     * @return
     */
    String value();
}
  • 定义@ColumnAnnotation注解
import java.lang.annotation.*;

//用于标记类的属性与数据库表字段的映射
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ColumnAnnotation {
    /**
     * 描述字段的名称
     * @return
     */
    String columnName();

    /**
     * 描述字段的数据类型
     * @return
     */
    String type();

    /**
     * 描述字段的长度
     * @return
     */
    int length();
}
  • 定义一个Book类,使用自定义的两个注解修饰
import java.io.Serializable;
import java.util.Objects;
@TableAnnotation(value = "book_table")
public class Book implements Serializable {
    @ColumnAnnotation(columnName = "bookId",type = "int",length = 10)
    private Integer bookId;
    @ColumnAnnotation(columnName = "name",type = "varchar",length = 20)
    private String name;
    @ColumnAnnotation(columnName = "info",type = "varchar",length = 1000)
    private String info;

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", name='" + name + '\'' +
                ", info='" + info + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(bookId, book.bookId) &&
                Objects.equals(name, book.name) &&
                Objects.equals(info, book.info);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bookId, name, info);
    }

    public Book(Integer bookId, String name, String info) {
        this.bookId = bookId;
        this.name = name;
        this.info = info;
    }

    public Book() {
    }

    public Integer getBookId() {
        return bookId;
    }

    public void setBookId(Integer bookId) {
        this.bookId = bookId;
    }

    public String getName() {
        return name;
    }

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

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}
  • 定义一个demo类,获取类和属性的注解内容。后续可以根据这些获取的内容去创建数据库表
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

public class demo {
    public static void main(String[] args) {
        //获取Book类的Class对象
        Class<Book> b = Book.class;

        //获取修饰Book类的所有注解(这里只有一个TableAnnotation)
        Annotation[] as = b.getAnnotations();
        for (Annotation a : as) {
            System.out.println(a); //@TableAnnotation(value="book_table")
        }

        //获取@TableAnnotation注解(获取到的是注解接口)
        TableAnnotation annotation = b.getAnnotation(TableAnnotation.class);
        String value = annotation.value();
        //book_table,根据此注解的内容去数据库创建表名
        System.out.println(value);

        //获取所有属性
        Field[] fs = b.getDeclaredFields();
        for (Field f : fs) {
            //获取修饰属性的注解
            ColumnAnnotation ca = f.getAnnotation(ColumnAnnotation.class);
            //获取注解中的内容,后面可以根据这些内容创建数据库表
            String columnName = ca.columnName();
            String type = ca.type();
            int length = ca.length();
            System.out.println("Book的"+f+"属性,对应数据库表中的"+columnName+"字段,类型为:"+type+",长度为:"+length);
        }

    }
}
  • 测试结果

四、内省机制

4.1 Javabean与内省机制简介

Javabean

  • 一个定义在包中的类,至少满足以下条件,且没有相关的业务逻辑处理的类,称为bean类
    • 拥有无参构造器
    • 所有属性私有化
    • 所有属性提供set/get方法
    • 实现了序列化接口
  • 以后对bean类的定义一定要规范,否则在框架的内省机制中可能会出问题,比如某些属性不能赋值......

内省机制

  • java提供了一套java.beans包的api,对于反射的操作进行了封装,可以更快地通过反射技术获取Javabean的相关信息
  • 内省机制在后续的开发用的并不多,但是在自己定义框架的时候用的很多
  • 内省机制的流程

 4.2 内省机制的使用

  • 内省类Introspector

获取Bean类信息

方法:

        getBeanInfo(Class cls)

返回值类型:

        BeanInfo

通过传入的类信息,得到这个Bean类的封装对象

  • BeanInfo

常用的方法:
        MethodDescriptor[] getPropertyDescriptors():
        获取bean类的 get/set方法 数组

  • MethodDescriptor

常用方法:
        1. Method getReadMethod();
                获取一个get方法
        2. Method getWriteMethod();
                获取一个set方法

        
有可能返回null 注意加判断

  • 举例
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class demo1 {
    public static void main(String[] args) throws IntrospectionException {
        //获取Class对象
        Class<Book> b = Book.class;
        //通过内省类的getBeanInfo方法获取bean类信息
        BeanInfo beanInfo = Introspector.getBeanInfo(b);
        //获取bean类的get/set方法数组
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            //获取get方法
            Method get = pd.getReadMethod();
            //获取set方法
            Method set = pd.getWriteMethod();
            System.out.println(get);
            System.out.println(set);
            //获取属性名
            System.out.println(pd.getName());
            //获取属性的数据类型
            System.out.println(pd.getPropertyType());
            System.out.println("-----------------");
        }

    }
}
  • 运行效果

Guess you like

Origin blog.csdn.net/future_god_qr/article/details/121289386