Skip to content

注解和反射是所有框架的底层【框架:Mybatis、Spring系列】

一. 注解(开胃菜)

Java.Annotation

  • 注解入门
  • 内置注解
  • 自定义注解、元注解

1. 什么是注解?

  • 注释(comment) —— 给人看的
  • 注解 (annotation)—— 注:给人看的 解:给程序看

  • 有检查和约束的作用,需要按照规范去写

  • Annotation 是从 JDK 5.0 开始引用的新技术

  • Annotation 的作用:

    • 不是程序本身,可以对程序作出解释(这一点和注释 comment 没什么区别)
    • ==可以被其他程序(比如:编辑器等)读取== (有动态性,通过反射去读取)
  • Annotation 的格式:

    • 注解是以 "@注释名" 在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked") 【抑制警告】
  • Annotation 在那里使用?

    • 可以附加在 package class method field … 上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

2. 内置注解


  • @Override:定义在 java.lang.Override 中,此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明

  • @Deprecated:定义在 java.lang.Deprecates 中,此注释可以用于修辞方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择

  • @SuppressWarnings:定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息。【镇压警告】

    与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了

    • @SuppressWarnings("all")
    • @SuppressWarnings("unchecked")
    • @SuppressWarnings(value ={"unchecked", "deprecation"})
    • 等等……
java
/**
 * 什么是注解?
 *
 * Object 老宗主类,所有类默认继承
 */
// @SuppressWarnings 镇压警告   参数:all【镇压全部警告】
@SuppressWarnings("all")
public class _01_Annotation extends Object{

    // @Override 重写的注解
    @Override
    public String toString() {
        return super.toString();
    }

    // @FunctionalInterface 函数式接口 【Runnable 接口 中的注解】

    // @Deprecated 废弃的,不推荐使用的 【Thread 类 中的注释】
    // 不鼓励程序员使用的程序元素,但是可以使用,存在更好的方式
    @Deprecated
    public void testDeprecated(){
        System.out.println("此方法已过时,不推荐使用!");
    }

    public void testList(){
        List list = new ArrayList<>();
    }

    public static void main(String[] args) {

        _01_Annotation annotation = new _01_Annotation();
        annotation.testDeprecated();
    }
}

3. 元注解


  • 元注解的作用就是负责注解其他注释,Java 定义了 4 个标准的 meta-annotation 类型,他们被用来提供对其他 annotaion 类型作说明
  • 这些类型和它们所支持的类在 java.lang.annotation 包中可以找到(@Target,@Retention,@Document,@Inherited
    • @Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方,如:方法,字段,类……)
    • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
      • SOURCE(源码级别)< CLASS < RUNTIME
    • @Document:说明该注解将包含在 javadoc 中
    • @Inherited:说明子类可以 继承 父类中的该注解
java
/**
 * 元注解
 */
@MyAnnotation
public class _02_MetaAnnotation {

    @MyAnnotation
    public void testMyAnnotation() {

    }

    public static void main(String[] args) {

    }
}

//@Target(目标) 表示我们的注解可以用在哪些地方
//TYPE:类    METHOD:方法
//value 可以省略
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//@Retention 表示注解在什么地方有效 (源码sources < class < 运行时Runtime)
@Retention(RetentionPolicy.RUNTIME)
//@Documented 表示是否将这个注解生成在 JavaDoc 中
@Documented
//@Inherited 表示子类可以继承父类的注解
@Inherited
@interface MyAnnotation{

}

4. 自定义注解

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

二. 反射机制(主餐)

Java.Reflection

非常强大,让 Java 有了动态性。Java 是静态语言,因为有了反射机制,Java 变成了准动态语言!(但同时也增加了不安全性,有利有弊)

  • Java 反射机制概述
  • 理解 Class 类并获取 Class 实例 【JVM】
  • 类的加载 与 ClassLoader 【双亲委派机制】
  • 创建运行时类的对象 【Hook】
  • 获取运行时类的完整结构 【可以获取类的注解和类的泛型】
  • 调用运行时类的指定结构

1. Java 反射机制概述

1.1 静态语言 VS 动态语言

  • 动态语言
    • 是一类在运行时可以改变其结构的语言。如:新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
    • 主要动态语言:Object-C 、C# 、JavaScript 、PHP 、Python 等。
  • 静态语言
    • 与动态语言相对应,运行时结构不可变的语言就是静态语言。
    • 主要静态语言:Java、C、C++
    • Java 不是动态语言,但 Java 可以称之为 "准动态语言" 。即 Java 有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java 的动态性让编程的时候更灵活(同时,增加了不安全性,有利有弊)!

1.2 Java Reflection

  • Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期间借助与 Reflection API 取得任何类的内部消息(类名,类接口,类方法,类字段,类属性),并能直接操作任意对象的内部属性及方法。-- private 不能直接访问,需要构造方法,需要 get/set,但是使用反射可以直接读取
  • 加载完类之后(Class c = Class.forName("java.lang.String");),在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
java
/**
 * 什么叫反射?
 */
public class _04_Reflect {
    public static void main(String[] args) {
        try {
            //通过反射来获取 类的class 对象
            Class<?> c1 = Class.forName("com.xin._02_reflect.User");
            Class<?> c2 = Class.forName("com.xin._02_reflect.User");
            Class<?> c3 = Class.forName("com.xin._02_reflect.User");
            Class<?> c4 = Class.forName("com.xin._02_reflect.User");
            System.out.println(c1);

            //一个类在内存中只有一个class对象,无论创建多少个class,都是同一个
            //一个类被加载后,类的整个结构都会被封装在 class 对象中,通过这个对象,我们可以获取到类的全部信息
            System.out.println(c1.hashCode());
            System.out.println(c2.hashCode());
            System.out.println(c3.hashCode());
            System.out.println(c4.hashCode());

        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

1.3 正常方式 VS 反射方式

正常方式:引入需要的“包类”名称 --> 通过 new 实例化 --> 取得实例化对象

反射方式:实例化对象 --> getClass() 方法 --> 得到完整的 "包类" 名称

1.4 Java 反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理(AOP 面向切片编程)
  • ……

1.5 Java 反射优点和缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM(虚拟机),我们希望做什么并且它满足我们的要求。这类操作总是 慢于 直接执行相同的操作。(new 比 反射 快了好几十倍,除了特殊情况,不要全部都用反射)

1.6 反射相关的主要 API

  • java.lang.Class : 代表一个类
  • java.lang.reflect.Method : 代表类的方法
  • java.lang.reflect.Field : 代表类的成员变量
  • java.lang.reflect.Constructor : 代表类的构造器
  • ……

2. 理解 Class 类并获取 Class 实例 (JVM -- java 虚拟机)

  • 类如何加载?
  • 如何获取 class 实例?

2.1 理解 Class 类

  1. 在 Object 类中定义了以下的方法,此方法将被所有子类继承

    java
    public final Class getClass()

    以上的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

    image-20250808170123957
  2. 对象照镜子后可以得到的信息:某个类的属性、方法、构造器、父类、实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。

    • Class 本身也是一个类
    • Class 对象只能由系统建立对象(我们可以通过反射获得对象)
    • 一个加载的类在 JVM 中只会有一个 Class 实例
    • 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件(编译后生成的.class 文件)
    • 每个类的实例都会记得自己是由哪个 Class 实例所生成
    • 通过 Class 可以完整地得到一个类中的所有被加载的结构
    • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象
  3. Class 类的常用方式

    方法名功能说明
    static ClassforName(String name)返回指定类名 name 的 Class 对象
    Object newInstance()调用缺省构造函数,返回 Class 对象的一个实例 【相当于 new 一个对象】
    getName()返回此 Class 对象所表示的实体(类、接口、数组类或 void)的名称
    Class getSuperClass()返回此 Class 对象的父类的 Class 对象
    Class [] getInterfaces()获取当前 Class 对象的接口
    ClassLoader getClassLoader()返回该类的类加载器
    Constructor [] getConstructors()返回一个包含某些 Constructor 对象的数组
    Method getMethod(String name, Class.. T)返回一个 Method 对象,此对象的形参类型为 paramType
    Field [] getDeclaredFields()返回 Field 对象的一个数组 【字段】

2.2 获取 Class 类的实例

  1. 若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高

    java
    Class clazz = Person.class;
  2. 已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象

    java
    Class clazz = person.getClass();
  3. 已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException

    java
    Class clazz = Class.forName("com.xin.reflect.User");
  4. 内置基本数据类型可以直接用 类名.Type

  5. 还可以利用 ClassLoader

java
/**
 * 测试 Class 类的创建方式有哪些
 */
public class _05_CreateClass {

    public static void main(String[] args) throws ClassNotFoundException {

        _05_CreateClass cc = new _05_CreateClass();

        //获得 User class 的三种方法
        //获得 基本内置类型的包装类
        cc.getUserClass();

        System.out.println("-------------------------分割线-----------------------------");

        //获得 Student class 的三种方法
        cc.getStudentClass();
    }

    /**
     * 获得 Student class 的三种方法
     * @throws ClassNotFoundException
     */
    public void getStudentClass() throws ClassNotFoundException {
        Person student = new Student();
        System.out.println("这个人是:" + student.name);

        //1. 通过 对象 获得
        Class<?> c1 = student.getClass();

        //2. 通过 forName 获得
        Class<?> c2 = Class.forName("com.xin._02_reflect.pojo.Student");

        //3. 通过 类名.class 获得【更高效】
        Class<Student> c3 = Student.class;

        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());

        //获得父类类型
        Class<?> superclass = c3.getSuperclass();//Person
        System.out.println(superclass);
        Class<?> superclass1 = superclass.getSuperclass();//Object
        System.out.println(superclass1);

    }

    /**
     * 获得 User class 的三种方法
     * @throws ClassNotFoundException
     */
    public void getUserClass() throws ClassNotFoundException {
        //1. 创建方式一:通过 类名.class 获得【更高效】
        Class<User> c1 = User.class;

        //2. 创建方式二:通过对象获得
        User user = new User("小李",1,18);
        Class<? extends User> c2 = user.getClass();

        //3. 创建方式三:通过 forName 获得
        Class<?> c3 = Class.forName("com.xin._02_reflect.pojo.User");

        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());

        //4. 创建方式四:基本内置类型的包装类都有一个 TYPE 属性 【作为了解】
        Class<Integer> c4 = Integer.TYPE;
        System.out.println(c4);
    }
}

3. 哪些类型可以有 Class对象 ?

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void
java
/**
 * 哪些类型可以有 class对象?
 *      1.所有类型的 class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
 *      2.接口
 *      3.数组(一维数组、二维数组)
 *      4.注解
 *      5.枚举
 *      6.基本数据类型
 *      7. void
 *      8. Class
 */
public class _06_ClassObject {
    public static void main(String[] args) {
        
        //1.所有类型的 class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
        Class<Object> c1 = Object.class;

        //2.接口
        Class<Runnable> c2 = Runnable.class;

        //3.数组(一维数组、二维数组)
        Class<String[]> c3 = String[].class;
        Class<int[][]> c4 = int[][].class;

        //4.注解
        Class<Override> c5 = Override.class;
        
        //5.枚举
        Class<ElementType> c6 = ElementType.class;

        //6.基本数据类型
        Class<Integer> c7 = Integer.class;

        //7. void
        Class<Void> c8 = void.class;

        //8. Class
        Class<Class> c9 = Class.class;

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

        System.out.println("-------------------------分割线-----------------------------");

        //数组长度不一样,返回的是同一个类吗?
        //是同一个类
        //只要元素类型与维度一样,就是同一个 Class
        int[] a = new int[10];
        int[] b = new int[1000];
        System.out.println(a.getClass());
        System.out.println(a.getClass());
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
    }
}

4. 类的加载与 ClassLoader

4.1 Java内存分析(理解class是什么)

image-20241226220318100方法区是一个特殊的

4.2 类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

  1. 类的加载(Load):将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
  2. 类的链接(Link):将类的二进制数据合并到JRE(java的运行时环境)中。
  3. 类的初始化(Initialize):JVM负责对类进行初始化。

4.3 类的加载与 ClassLoader 的理解

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class对象。(不能主动创建,只能获取--通过反射是动态的)
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配(在初始化之前的链接阶段就已经有内存和初始值了,所以可以直接调用)
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:(JVM做的,不是我们程序员做的,我们控制不了)
    • 执行类构造器 < clinit >() 方法的过程。类构造器 < clinit >() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
    • 虚拟机会保证一个类的 < clinit >() 方法在多线程环境中被正确加锁和同步。

image-20250810133253877

java
/**
 * Java 内存分析
 *
 * 1.加载到内存,会产生一个类对应的 class 对象
 * 2.链接:链接结束后 m = 0  初始默认值
 * 3.初始化
 *      <clinit>(){
 *          System.out.println("A静态方法块初始化");
 *          m = 300;
 *          m = 100;
 *      }
 *
 *      结果:m = 100;
 */
public class _07_JavaMemoryAnalysis {
    public static void main(String[] args) {
//        A a = new A();
        System.out.println(A.m);
    }
}

class A{
    static{
        System.out.println("A静态方法块初始化");
        m = 300;
    }

    /*
        m = 300;
        m = 100;
        覆盖
    */

    static int m = 100;

    public A(){
        System.out.println("A类构造方法初始化");
    }
}

4.4 什么时候会发生类初始化?

  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化 mian 方法所在的类
    • new 一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用 java.lang.reflect 包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
java
/**
 * 测试
 *   1.类什么时候会初始化
 *   2.类什么时候不会初始化
 */
public class _08_ClassInit {

    static {
        //虚拟机 JVM 启动的时候就会加载
        System.out.println("main 类被加载");
    }
    public static void main(String[] args) {
        //1.类什么时候会初始化
        //(1)主动引用
//        Son son = new Son();

        //(2)反射也会产生主动引用
//        try {
//            Class<?> sonClass = Class.forName("com.xin._02_reflect.Son");
//        } catch (ClassNotFoundException e) {
//            throw new RuntimeException(e);
//        }

        System.out.println("------------------------------------------");

        //2.不会产生类的引用的方法
        //(1)当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
//        System.out.println(Son.b);

        //(2)通过数组定义类引用,不会触发此类的初始化
        Son[] sons = new Son[5];

        //(3)引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
        System.out.println(Son.M);
    }
}

class Father{
    static int b = 2;

    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");
        m = 300;
    }

    static int m = 100;
    static final int M = 1;
}

4.5 类加载器的作用

  • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。

  • ==类缓存==:标准的 Java SE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM 垃圾回收机制(gc)可以回收这些 Class 对象。(缓存的作用:提高效率)

    image-20241229220600325
  • 类加载器的作用:把类(class)装载进内存。JVM 规范定义了如下类型的类的加载器:

    • 引导类加载器(Bootstap Classloader):用C++编写的,是JVM自带的类加载器,负责==Java平台核心库(rt.jar)==,用来装载核心类库。该加载器无法直接获取

      image-20250812170534341
    • 扩展类加载器(Extension Classloader):负责 jre/lib/ext 目录下的 jar 包或 -D java.exit.dirs 指定目录下的 jar 包装入工作库

      image-20250812170839351
    • 系统类加载器(System Classloader):负责 java -classpath 或 -D java.class.path 所指的目录下的类与 jar 包装入工作库,最常用的加载器

    image-20241229221833824
  • 双亲委派机制

    txt
    多重检测,保证安全性
    如果你也写了一个 java.lang.String 会发现不会用你自己写的类,会向上找扩展类加载器,根加载器,如果有,会用上层的
java
/**
 * 获取 类加载器
 */
public class _09_ClassLoader {
    public static void main(String[] args) throws ClassNotFoundException {
        //获得 系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@22d8cfe0

        //获得 系统类的加载器 的 父类  ==> 扩展类加载器
        ClassLoader extensionLoader = systemClassLoader.getParent();
        System.out.println(extensionLoader);//sun.misc.Launcher$ExtClassLoader@1eb44e46

        //获得 扩展类加载器 的 父类 ==>  引导类加载器(根加载器)【C/c++ 写的, Java读取不到】
        ClassLoader bootstapLoader = extensionLoader.getParent();
        System.out.println(bootstapLoader);//读取不到,返回 null


        System.out.println("------------------------------------------");


        //测试当前类是哪个加载器加载的
        Class<?> aClass = Class.forName("com.xin._02_reflect._08_ClassInit");
        ClassLoader classLoader = aClass.getClassLoader();
        System.out.println(classLoader);//【系统类的加载器】  sun.misc.Launcher$AppClassLoader@22d8cfe0
        //测试 JDK 内置的类是哪个加载器加载的
        Class<?> aClass1 = Class.forName("java.lang.String");
        ClassLoader classLoader1 = aClass1.getClassLoader();
        System.out.println(classLoader1);//【引导类加载器】 读取不到,返回 null

        //如何获取系统类加载器可以加载的路径
        String property = System.getProperty("java.class.path");
        System.out.println(property);
        /*
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\charsets.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\deploy.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\access-bridge-64.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\cldrdata.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\dnsns.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\jaccess.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\jfxrt.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\localedata.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\nashorn.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\sunec.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\sunjce_provider.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\sunmscapi.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\sunpkcs11.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\ext\zipfs.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\javaws.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\jce.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\jfr.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\jfxswt.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\jsse.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\management-agent.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\plugin.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\resources.jar;
            D:\02_work\softWare_work\Java\jdk-1.8\jre\lib\rt.jar;
            D:\02_work\softWare_work\JetBrains\IntelliJ IDEA 2024.2.0.2\workspace\studyFollowKuang\01-JavaBase\05_annotation_reflect\target\classes;
            D:\02_work\softWare_work\JetBrains\IntelliJ IDEA 2024.2.0.2\lib\idea_rt.jar
         */

        /**
         * 双亲委派机制
         *         多重检测,保证安全性
         *         如果你也写了一个 java.lang.String 会发现不会用你自己写的类,会向上找扩展类加载器,根加载器,如果有,会用上层的
         */

    }
}

5. 创建运行时类的对象


5.1 获取运行时类的完整结构

通过反射获取运行时类的完整结构(Field Method Constructor SuperClass Interface Annotation)

  • 全部的 Field
  • 全部的方法
  • 全部的构造器
  • 所继承的父类
  • 实现的全部接口
  • 注解
  • ……
java
/**
 * 通过反射获取运行时类的完整结构
 */
public class _10_ObtainCLassInfo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
//        Class<?> userClass = Class.forName("com.xin._02_reflect.pojo.User");

        User user = new User();
        Class<? extends User> userClass = user.getClass();

        /**
         * 获取 包名/类名
         */
        System.out.println("----------- 获取 包名/类名 -----------");
        String name = userClass.getName();//获取 包名 + 类名
        String simpleName = userClass.getSimpleName();//获取类名
        System.out.println(name);
        System.out.println(simpleName);

        /**
         * 获取 属性、字段
         */
        System.out.println("----------- 获取 属性、字段 -----------");
        //只能找到 public 属性
        Field[] fields = userClass.getFields();
        for (Field field : fields) {
            System.out.println("公有字段:" + field.getName());
        }
        //找到全部的属性
        fields = userClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("全部字段:" + field);
            System.out.println("全部字段:" + field.getName());
        }
        //获得指定属性
        Field id = userClass.getDeclaredField("id");
        System.out.println(id);

        /**
         * 获得 类的方法
         */
        System.out.println("----------- 获取 类的方法 -----------");
        Method[] methods = userClass.getMethods();
        //获得 本类及其父类的全部 public 方法
        for (Method method : methods) {
            System.out.println("公共方法:" + method);
        }
        //获得 本类的所有方法(公有/私有)
        methods = userClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("全部方法:" + method);
        }
        //获得指定方法
        //因为方法可以重载,所以需要输入参数
        Method getName = userClass.getMethod("getName", null);//无参
        System.out.println(getName);
        Method setName = userClass.getMethod("setName", String.class);//有参
        System.out.println(setName);

        /**
         * 获得 类的构造器
         */
        System.out.println("----------- 获取 类的构造器 -----------");
        //获得全部构造器
        Constructor<?>[] constructors = userClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("遍历1:" + constructor);
        }
        constructors = userClass.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("遍历2:" + constructor);
        }
        //获得指定构造器
        Constructor<? extends User> constructor = userClass.getConstructor();
        System.out.println(constructor);
        Constructor<? extends User> constructor2 = userClass.getConstructor(String.class,Integer.TYPE,Integer.TYPE);
        System.out.println(constructor2);
    }
}

小结:

  1. 在实际的操作中,取得类的信息的操作代码,并不会经常开发
  2. 一定要熟悉 java.lang.reflect 包的作用,反射机制
  3. 如何取得属性、方法、构造器的名称、修饰符……

5.2 有了 Class 对象,能做什么?

  • 创建类的对象:调用 Class 对象的 newInstance() 方法
    1. 类必须有一个无参数的构造器
    2. 类的构造器的访问权限需要足够

思考?难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。

  • 步骤如下:
    1. 通过 Class 类的 getDeclaredConstructor(Class< ? >... parameterTypes) 取得本类的指定形参类型的构造器
    2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
    3. 通过 Constructor 实例化对象
java
/**
 * 通过反射动态的创建对象
 *      1.用 class 对象 创建对象【本质:类的无参构造器 创建对象】
 *      2.用构造器 创建对象
 *
 * 不能直接操作私有属性和方法,我们需要关闭程序的安全检测  xx.setAccessible(true);【会降低程序的效率】
 */
public class _11_ReflectCreateObject {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得 class 对象
        Class<?> userClass = Class.forName("com.xin._02_reflect.pojo.User");

        //构造一个对象
        User user = (User) userClass.newInstance();//本质上是调用了类的无参构造器;没有无参构造器会报错
        System.out.println(user);
        user.run();

        //难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
        //通过构造器创建对象
        Constructor<?> constructor = userClass.getDeclaredConstructor(String.class, Integer.TYPE, Integer.TYPE);
        User user2 = (User) constructor.newInstance("小李",2,18);
        user2.run();
    }
}

5.3 调用指定的方法

通过反射,调用类中的方法,通过Method类完成

  1. 通过 Class 类的 getMethod(String name,Class...parameterTypes) 方法取得一个 Method 对象,并设置此方法操作时所需要的参数类型。

  2. 之后使用 Object invoke(Object obj,Object[] args) 进行调用,并向方法中传递要设置的 obj 对象的参数信息。

    image-20241230214248513
    • Object 对应原方法的返回值,若原方法无返回值,此时返回 null
    • 若原方法若为静态方法,此时形参Object obj 可为 null
    • 若原方法形参列表为空,则 Object[] args 为 null
    • 若原方法声明为 private,则需要在调用 invoke() 方法前,显式调用方法对象的 setAccessible(true) 方法,将可访问 private 的方法。(先关闭程序的安全检测)
      • Method 和 Field 、Constructor 对象都有 setAccessible() 方法
      • setAccessible 作用是启动和禁用访问安全检查的开关【false:安全检查(默认);true:关闭安全检查】
      • 参数值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查
        • 提高反射效率。如果代码中必须使用反射,而该句代码频繁的被调用,那么请设置为 true 。
        • 使得原本无法访问的私有成员也可以访问
      • 参数值为 false 则指示反射的对象应该实施 Java 语言访问检查
java
/**
 * 不能直接操作私有属性和方法,我们需要关闭程序的安全检测  xx.setAccessible(true);【会降低程序的效率】
 */
public class _11_ReflectCreateObject {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得 class 对象
        Class<?> userClass = Class.forName("com.xin._02_reflect.pojo.User");
        //通过反射调用普通方法
        User user3 = (User) userClass.newInstance();
        Method setName = userClass.getDeclaredMethod("setName", String.class);
        Method add = userClass.getDeclaredMethod("add");
        setName.invoke(user3,"user3 --> 小李李");
        System.out.println(user3.getName());
        add.setAccessible(true);//检查关掉,默认为false开启
        add.invoke(user3);//invoke 激活

        //通过反射操作属性
        User user4 = (User) userClass.newInstance();
        Field name = userClass.getDeclaredField("name");
        name.setAccessible(true);//检查关掉,默认为false开启
        name.set(user4,"user4 --> 小李李李");
        System.out.println(user4.getName());
    }
}

5.4 分析性能问题

java
/**
 * 分析反射性能
 */
public class _12_AnalyzeReflectPerformance {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        _12_AnalyzeReflectPerformance arp = new _12_AnalyzeReflectPerformance();
        //普通方式调用
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            arp.ordinaryWay();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("ordinaryWay -->" + (endTime - startTime));//4ms

        //反射方式调用
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            arp.reflectWay();
        }
        endTime = System.currentTimeMillis();
        System.out.println("reflectWay -->" + (endTime - startTime));//621ms

        //反射方式调用 关闭安全检查
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            arp.reflectWayAcc();
        }
        endTime = System.currentTimeMillis();
        System.out.println("reflectWayAcc -->" + (endTime - startTime));//558ms
    }

    //普通方式调用
    public void ordinaryWay(){
        User user = new User();
        user.executePub();
    }

    //反射方式调用
    public void reflectWay() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Class<?> userClass = Class.forName("com.xin._02_reflect.pojo.User");

        User user = (User) userClass.newInstance();
        Method executePub = userClass.getMethod("executePub");
        executePub.invoke(user);
    }

    //反射方式调用 关闭安全检查
    public void reflectWayAcc() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Class<?> userClass = Class.forName("com.xin._02_reflect.pojo.User");

        User user = (User) userClass.newInstance();
        Method executePub = userClass.getMethod("executePub");
        executePub.setAccessible(true);
        executePub.invoke(user);
    }
}

6. 反射操作泛型

泛型:约束机制,保证代码安全性,免去强制类型转换问题


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

  • 为了通过反射操作这些类型,Java新增了 ParameterizedType , GenericArrayType , TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中 的类型但是又和原始类型齐名的类型

  • ParameterizedType:表示一种参数化类型,比如 Collection< String >

  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型

  • TypeVariable:是各种类型变量的公共父接口

  • WildcardType:代表一种通配符类型表达式

java
/**
 * 通过反射获取泛型
 */
public class _13_ReflectObtainGeneric {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<_13_ReflectObtainGeneric> c1 = _13_ReflectObtainGeneric.class;

        //带有泛型参数的方法
        Method test01 = c1.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = test01.getGenericParameterTypes();//返回值类型
        for (Type type : genericParameterTypes) {
            System.out.println("test01 --> getGenericParameterTypes --> "+ type);
            if (type instanceof ParameterizedType) {
                //判断当前类型 是否是 参数化类型
                ParameterizedType parameterizedType = (ParameterizedType) type;//强转为:参数化类型
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//获得真实的参数信息
                for (Type argument : actualTypeArguments) {
                    System.out.println(argument);
                }
            }
        }

        //带有泛型返回值的方法
        Method test02 = c1.getMethod("test02");
        Type genericReturnType = test02.getGenericReturnType();//返回值类型
        System.out.println("test02 --> getGenericParameterTypes --> "+ genericReturnType);
        if(genericReturnType instanceof ParameterizedType) {
            ParameterizedType type = ((ParameterizedType) genericReturnType);
            Type[] actualTypeArguments = type.getActualTypeArguments();
            for (Type argument : actualTypeArguments) {
                System.out.println(argument);
            }
        }
    }

    //带有泛型参数的方法
    public void test01(Map<String , User> map, List<String> list){
        System.out.println("test01");
    }

    //带有泛型返回值的方法
    public Map<String , User> test02(){
        System.out.println("test02");
        return null;
    }
}

7. 反射操作注解

  • getAnnotations
  • getAnnotation

练习:ORM

  • 了解什么是ORM?【数据库】

    • Object relationship Mapping --> 对象关系映射

      image-20250104175002091
    • 类和表结构对应

    • 属性和字段对应

    • 对象和记录对应

  • 要求:利用注解和反射完成类和表结构的映射关系

java
/**
 * 反射操作注解
 */
public class _14_ReflectOperateAnnotation {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> c1 = Class.forName("com.xin._02_reflect.Student_14");
        //通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获得注解的 value 值
        Table_14 annotation = c1.getAnnotation(Table_14.class);
        String value = annotation.value();
        System.out.println(value);

        //获得类指定的注解
        Field name = c1.getDeclaredField("name");
        Field_14 annotation1 = name.getAnnotation(Field_14.class);
        System.out.println(annotation1.columnName());
        System.out.println(annotation1.type());
        System.out.println(annotation1.length());
    }
}

@Table_14("db_Student")
class Student_14{
    @Field_14(columnName = "db_id",length = 10)
    private int id;
    @Field_14(columnName = "db_name",type = "varchar",length = 10)
    private String name;
    @Field_14(columnName = "db_age",length = 3)
    private int age;

    public Student_14() {
    }

    public Student_14(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;
    }

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

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

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field_14{
    String columnName() default "id";
    String type() default "int";
    long length() default 0;
}