反射、动态代理

Java Reflection-Java 反射机制

一、概述

  1. 可以做什么?

    • Reflection 反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息(成员变量、构造器、方法),并能直接操作任意对象的内部属性和方法。
    • 类在加载完毕后,会在堆内存的方法区中产生一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了完整的类的结构信息。这个对象就像是一面镜子,可以通过这个镜子看到类的结构,这种行为被称之为反射
  2. 使用单例模式设计的类,被反射后还是可以构造多个实例,但是没有必要这样做,因为一般单例模式的类需要的功能都可以用唯一实例去调用。

  3. 类的封装性和反射机制并不矛盾,一个类在设计时,对一些方法进行private修饰是因为这个方法只在本类的其他public类里面使用,不对外可见是因为外面用不到这些private方法。反射是如果真的有需要调用private方法时使用的。

  4. 反射的源头是Class类。

  5. 反射相关的主要 API

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

        // 反射之后能对 Person 类所做的操作
        @Test
        public void test2() throws Exception{
            Class clazz = Person.class; // 获取 Person 运行时类的对象
            // 1.通过反射创建 Person 类的对象
            Constructor constructor = clazz.getConstructor(String.class, int.class);
            Object o = constructor.newInstance("xiangyu", 12);
            Person person = (Person) o;
            System.out.println(person);
    
            // 2.通过反射调用对象的 public 属性、方法
            // 调用属性
            Field age = clazz.getDeclaredField("age"); // 获取 clazz 对象中名为 name 的属性
            age.set(person,21);
            System.out.println(person);
    
            // 调用方法
            Method show = clazz.getDeclaredMethod("show");
            show.invoke(person);
    
            System.out.println("************************");
            // 3.通过反射调用 private 构造器、属性和方法
            Constructor cons1 = clazz.getDeclaredConstructor(String.class); // 获取私有构造器
            cons1.setAccessible(true);
            Person person2 = (Person) cons1.newInstance("fangxu");
            System.out.println(person2);
    
            Field name = clazz.getDeclaredField("name"); // 获取私有属性
            name.setAccessible(true); // 设置为可访问的
            name.set(person2,"芳旭");
            System.out.println(person2);
    
            Method showNation = clazz.getDeclaredMethod("showNation", String.class); // 获取私有方法
            showNation.setAccessible(true); // 设置访问权限
            String nation = (String) showNation.invoke(person, "中国");
            System.out.println(nation);
        }
    

二、理解 java.lang.Class 类并获取 Class 的实例

  1. 类的加载过程:

    程序经过 javac.exe 命令编译后,会生成一个或多个字节码文件(.class 文件)。接着使用 java.exe 命令解释运行某一个字节码文件,就相当于将某个字节码文件加载到内存中,此过程就称为类的加载。加载到内存中的类就称为运行时类,此运行时类,就作为Class的一个实例。

  2. 也就是说:Class的一个实例就对应着一个运行时类

  3. 加载到内存中的运行时类,会缓存一定时间。在此时间内,我们可以通过不同的方式来获取此运行时类。

  4. 获取Class的实例的方式:(四种)

        @Test
        public void test3() throws Exception{
            // 方式一:调用运行时类的属性 .class
            Class clazz1 = Person.class;
            System.out.println(clazz1);
            // 方式二:通过运行时类的对象
            Person person = new Person("香芋",1);
            Class clazz2 = person.getClass();
            System.out.println(clazz2);
            // 方式三:调用 Class 的静态方法 forName(String classPath),这种方式是比较常用的
            Class clazz3 = Class.forName("com.xiangyu.reflectionTest.Person");
            System.out.println(clazz3);
    
            // 运行时类一定时间内不会重复加载
            System.out.println(clazz1 == clazz2);
            System.out.println(clazz1 == clazz3);
            
            // 方式四:ClassLoader 类加载器
            ClassLoader loader = ReflectionTest.class.getClassLoader();
            Class clazz4 = loader.loadClass("com.xiangyu.reflectionTest.Person");
            System.out.println(clazz4);
        }
    
  5. 哪些类型可以有Class对象?

    1. class: 外部类、成员(成员内部类,静态内部类)、局部内部类、匿名内部类

    2. interface: 接口

    3. []: 数组

    4. enum: 枚举

    5. annotation: 注解@interface

    6. primitive type: 基本数据类型

    7. void

      @Test
      public void test4() {
          Class c1 = Object.class;
          Class c2 = Comparable.class;
          Class c3 = String[].class;
          Class c4 = int[][].class;
          Class c5 = ElementType.class;
          Class c6 = Override.class;
          Class c7 = int.class;
          Class c8 = void.class;
          Class c9 = Class.class;
      
          int[] arr1 = new int[10];
          int[] arr2 = new int[100];
          Class c10 = arr1.getClass();
          Class c11 = arr2.getClass();
          // 只要元素类型与维度一样,就是同一个Class实例
          System.out.println(c10 == c11);
      }
      

三、类加载机制与 ClassLoader 的理解

1. 类加载机制有三个步骤:加载->链接->初始化
  1. 加载阶段,Java 虚拟机要完成三件事情

    1. 通过一个类的全限定名来获取定义此类的二进制字节流。(获取)

    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。(加载到内存)

    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

      ·《Java虚拟机规范》中并未对这三点做具体的要求,比如在第一点获取二进制字节流的时候,并未要求从哪里获取,以什么方式获取。从这一点开发人员就可以玩出非常多的花样,比如:

      1. 从 jar 包等压缩包中获取
      2. 从网络中获取,典型的应用是 Web Applet
      3. 由其他文件生成,典型的应用是 JSP 应用,由 JSP 文件生成对应的 Class 文件
      4. 从数据库中读取(这种方式比较少,有些中间件服务器可以选择把应用程序安装到数据库中完成代码在集群间分发)
      5. 运行时计算生成(最常见就是动态代理,在 java.lang.reflect.Proxy 中)
      6. 从加密文件中获取(典型的)
  2. 连接阶段分为三个步骤:验证、准备、解析

    1. 验证是连接阶段的第一步,这以阶段的目的是确保 Class 文件的字节流中包含的信息符合《 Java 虚拟机规范》的全部约束要求,保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证的信息包括:

      1.文件格式验证:是否以魔数 0xCAFEBABE 开头,主、次版本号是否被当前虚拟机接受等

      2.元数据验证:这个类是否有父类,这个类的父类是否继承了不允许被继承的类等

      3.字节码验证

      4.符号引用验证

    2. 准备阶段是正式为类中定义的变量(即静态变量,也就是被 static 修饰过的变量,不包括实例变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配。但必须注意到方法区本身是一个逻辑上的区域,在 JDK 7 及之前, HotSpot 使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在 JDK 8 之后,类变量则会随着 Class 对象一起存放在 Java 堆中,这时候”类变量在方法区“就完全是一种对逻辑概念的表述了。

    3. 解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程。

      • 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
      • 直接饮用:可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
  3. 初始化是类加载过程的最后一个步骤,前面的动作中,除了加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由 Java 虚拟机来主导控制。直到初始化阶段, Java 虚拟机才真正开始执行类中编写的 Java 代码,将主导权移交给应用程序。

  4. 关于虚拟机在何时开始类的加载,《Java 虚拟机规范》中并没有做强制约束,需要虚拟机的具体实现来自由把握,但是对于初始化阶段,有且只有六种情况必须立即对类进行初始化

    1. 使用 new 关键字实例化对象的时候、读取或设置一个类型的静态字段、调用一个类型的静态方法的时候。
    2. 使用 java.lang.reflact 包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
    3. 如果初始化类的时候,发现其父类还没有进行初始化,那么要先触发其父类的初始化。
    4. 当虚拟机启动时,用户需要指定一个主类(main()方法所在的那个类),虚拟机会先初始化这个类。
    5. JDK7 新加入的动态语言支持,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
    6. 当一个接口中定义了 JDK8 新加入的默认方法 (被 default 关键字修饰的方法),如果有这个接口的实现类被初始化,那么此接口要在器之前被初始化(类似于子类初始化前先初始化父类)。
2. 类加载器

​ 类加载阶段中”通过一个类的全限定名来获取描述该类的二进制字节流“实现这个动作的代码称为“类加载器“(ClassLoader)。

​ 类加载器虽然只用于实现类的加载动作,但其在 Java 应用程序中起不同的作用:对于任意一个类,都必须由加载它的类加载器和这个类本身共同确立其在 Java 虚拟机中的唯一性,每一个类加载器都有一个独立的类名称空间。也就是说:比较两个类是否“相等”,只有在这两个类是被同一个类加载器加载的前提下才有意义,否则,既使这两个类来源于同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载他们的类加载器不同,那这两个类就一定不相等。

在 Java 虚拟机角度来看,只存在两种不同的类加载器:

  1. 启动类加载器(Bootstap ClassLoader),其 HotSpot 虚拟机实现是由 C++ 语言编写的,是虚拟机本身的一部分。
  2. 其他所有类加载器,这些类加载器都由 Java 语言实现,独立存在于虚拟机之外,并且全部继承自 java.lang.ClassLoader 。
    但是在开发人员的角度来看,类加载器的划分要细致一些:
    img
  • 启动类加载器(Bootstrap Class Loader):负责加载存放在 <JAVA_HOME>\bin 目录,或者被 -Xbootclasspath 参数所指定的路径中存放的,而且是 Java 虚拟机能够识别的(按照文件名识别,如 rt.jar、tools.jar,名字不符合的类库既使放在lib目录也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被 Java 程序直接引用,如果需要把加载请求委派给引导类加载器去处理,那么直接使用 null 代替即可(也就是用 null 值代表启动类加载器)。
  • 扩展类加载器(Extension Class Loader):这个类加载器是在类 sun.misc.Launcher$ExtClassLoader 中以 Java 代码的形式实现的。负责加载 <JAVA_HOME>\bin\ext 目录中,或者被 java.ext.dirs 系统变量所制定的路径中的所有类库。
  • 应用程序类加载器(Application Class Loader):这个类加载器是由 sun.misc.Launcher$AppClassLoader 来实现。此类加载器是 ClassLoader 类中的 getSystemClassLoader() 方法的返回值,所以也称其为系统类加载器。负责加载用户类路径(ClassPath)上所有的类库。
3. 双亲委派模型

​ 上图中展示的各种类加载器之间的关系被称为类加载器的“双亲委派模型”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都要有自己的父类加载器。不过这里类加载器之间的父子关系一般不是继承(Inheritance)的关系,而是通常使用组合(Composition)关系来复用父加载器的代码。

​ 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有类加载器最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

​ 使用双亲委派模型来组织类加载器之间的关系,可以防止类重复加载,防止用户自己编写与系统类(例如 java.lang.Object)同名的类。双亲委派模型对于保证 Java 程序的稳定极为重要,但其实现代码却极其简单,全部集中在 java.lang.ClassLoader 的 loadClass() 方法中。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查请求的类是否已经被加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出 ClassNotFoundException
                    // 说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    // 在父类加载器无法加载时
                    // 再调用本身的 findClass 方法来进行类加载
                    long t1 = System.nanoTime();
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

​ 代码中先检查请求加载的类型是否已经被加载过,若没有则调用父类加载器的 loadClass() 方法,若父加载器为空则默认使用启动类加载器作为父加载器。加入父类加载器加载失败,抛出 ClassNotFoundException 异常的话,才调用自己的 findClass() 方法尝试进行加载。

4. 破坏双亲委派模型

​ 双亲委派模型并不是一个具有强制约束性的模型,而是 Java 设计者推荐的类加载器的实现方式。如果有明确充分的理由,是可以对这个模型进行创新,比如代码热替换(Hot Swap)、模块热部署(Hot Deployment)等。

四、创建运行时类的对象


import org.junit.jupiter.api.Test;
import java.util.Random;

/**
 * @Classname NewInstanceTest
 * @Description 通过反射创建对应的运行时类对象
 */
public class NewInstanceTest {

    @Test
    public void test1() throws Exception{
        Class<Person> clazz = Person.class; // 类的泛型决定了下面 .newInstance() 方法的返回值
        Person p = clazz.newInstance();
        /*
            newInstance() 此方法会创建对应的运行时类的对象,此方法实际上是调用了运行时类的空参构造器。
          要想此方法正常创建运行时类的对象,要求:
            1.运行时类必须提供空参构造器
            2.空参构造器的访问修饰符一般设置为public

            在 javabean 中要求提供一个 public 的空参构造器。原因:
            1.便于通过反射,创建运行时类的对象
            2.便于子类调用super()继承此运行时类时,父类有空参构造器
         */
        System.out.println(p);
    }

    @Test
    public void test2() throws Exception {
        for (int i = 0; i < 100; i++) {
            int num = new Random().nextInt(3);
            String classPath = "";
            switch (num) {
                case 0:
                    classPath = "java.util.Date";
                    break;
                case 1:
                    classPath = "java.lang.Object";
                    break;
                case 2:
                    classPath = "com.xiangyu.classLoaderTest.ClassLoaderTest";
                    break;
            }
            Object instance = getInstance(classPath);
            System.out.println(instance.toString());
        }
    }

    /**
     * 此方法创建一个指定类的对象。
     * @param classPath 指定类的全类名
     * @return 指定类的对象
     * @throws Exception
     */
    public Object getInstance(String classPath) throws Exception{
        Class clazz = Class.forName(classPath);
        return clazz.newInstance();
    }
}

五、获取运行时类的完整结构

  1. 属性

    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    
    /**
     * @Classname FieldTest
     * @Description 获取运行时类的属性结构
     */
    public class FieldTest {
        @Test
        public void test1() {
            Class clazz = Person.class;
            // 获取属性结构
            // getFields() 获取当前运行时类及其父类中声明为 public 访问权限的属性。
            Field[] fields = clazz.getFields();
            for (Field f : fields) {
                System.out.println(f + "\t" + f.getName());
            }
    
            // getDeclaredFields() 获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
            System.out.println();
            Field[] fields1 = clazz.getDeclaredFields();
            for (Field f: fields1) {
                System.out.println(f);
            }
        }
    
        // 权限修饰符 数据类型 变量名
        @Test
        public void test2() {
            Class clazz = Person.class;
            Field[] fields = clazz.getDeclaredFields();
            for (Field f: fields) {
                // 1.权限修饰符
                int modifiers = f.getModifiers();
                System.out.println("权限修饰符:" + Modifier.toString(modifiers));
                // 2.数据类型
                Class<?> type = f.getType();
                System.out.println("数据类型:" + type);
                // 3.变量名
                String name = f.getName();
                System.out.println("变量名;" + name);
                System.out.println();
            }
        }
    }
    
  2. 方法

    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.lang.reflect.Parameter;
    
    /**
     * @Classname MethodTest
     * @Description 获取运行时类的方法结构
     */
    public class MethodTest {
        @Test
        public void test1() {
            Class clazz = Person.class;
            // getMethods() 获取当前运行时类机器所有父类中声明为 public 权限的方法
            Method[] methods = clazz.getMethods();
            for (Method m : methods) {
                System.out.println(m);
            }
    
            System.out.println("------------------------");
            // getDeclaredMethods() 获取当前运行时类中声明的所有方法(不包含父类中声明的方法)。
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method m : declaredMethods) {
                System.out.println(m);
            }
        }
    
        /**
         * 获取方法中的内部结构,包括:
         * <p>1.注解  getAnnotations()</p>
         * <p>2.权限修饰符  getModifiers()</p>
         * <p>3.返回值类型  getReturnType</p>
         * <p>4.方法名(参数类型1 形参名1 ...)  m.getName()  getParameterTypes()</p>
         * <p>5.throw XxxException  getExceptionTypes()</p>
         */
        @Test
        public void test2() {
    
            Class clazz = Person.class;
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method m : declaredMethods) {
                // 1.注解
                Annotation[] annotations = m.getAnnotations();
                for (Annotation a : annotations) {
                    System.out.println( a);
                }
                // 2.权限修饰符
                System.out.print(Modifier.toString(m.getModifiers()) + " ");
                // 3.返回值类型
                Class<?> returnType = m.getReturnType();
                System.out.print(returnType + " ");
                // 4.方法名
                System.out.print(m.getName() + " ");
                // 形参列表
                Class<?>[] parameterTypes = m.getParameterTypes(); // 所有的形参的类型
                Parameter[] parameters = m.getParameters(); // 所有的参数名
                System.out.print("(");
                if (parameterTypes.length != 0) {
                    for (int i = 0; i < parameterTypes.length; i++) {
                        System.out.print(parameterTypes[i].getName() + " " + parameters[i]);
                        if (i != parameterTypes.length-1){
                            System.out.println(", "); // 不是最后一个参数就补个逗号
                        }
                    }
                }
                System.out.print(") ");
                // 5.throw XxxException
                Class<?>[] exceptionTypes = m.getExceptionTypes();
                if (exceptionTypes.length != 0) {
                    System.out.print("throws ");
                    for (int i = 0; i < exceptionTypes.length; i++) {
                        System.out.print(exceptionTypes[i]);
                        if (i != exceptionTypes.length-1) {
                            System.out.print(", "); // 不是最后一个异常就补个逗号
                        }
                    }
                }
                System.out.println();
                System.out.println("-----------------------");
            }
        }
    }
    
  3. 其他

    import java.lang.annotation.Annotation;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    
    /**
     * @Classname OtherTerst
     * @Description TODO
     */
    public class OtherTest {
        /**
         * 获取构造器
         */
        @Test
        public void test1() {
            Class clazz = Person.class;
            // getConstructors() 获取当前运行时类中声明为 public 的构造器
            Constructor[] constructors = clazz.getConstructors();
            for (Constructor c : constructors) {
                System.out.println(c);
            }
    
            // getDeclaredConstructors() 获取当前运行时类中声明的所有构造器(不包括父类构造器)。
            Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
            for (Constructor c : declaredConstructors) {
                System.out.println(c);
            }
        }
        /**
         * 获取运行时类的父类
         */
        @Test
        public void test2() {
            Class clazz = Person.class;
            Class superclass = clazz.getSuperclass();
            System.out.println(superclass);
        }
        /**
         * 获取运行时类的父类(带泛型)
         */
        @Test
        public void test3() {
            Class clazz = Person.class;
            Type genericSuperclass = clazz.getGenericSuperclass();
            System.out.println(genericSuperclass);
        }
        /**
         * 获取运行时类的父类(带泛型)的泛型
         */
        @Test
        public void test4() {
            Class clazz = Person.class;
            Type genericSuperclass = clazz.getGenericSuperclass();
            ParameterizedType paramType = (ParameterizedType) genericSuperclass;
            Type[] actualTypeArguments = paramType.getActualTypeArguments(); // 获取泛型的类型
            System.out.println(actualTypeArguments[0].getTypeName());
        }
        /**
         * 获取运行时类实现的接口
         */
        @Test
        public void test5() {
            Class clazz = Person.class;
            Class[] interfaces = clazz.getInterfaces();
            for (Class c : interfaces) {
                System.out.println(c.getName());
            }
            System.out.println();
            // 继承的父类中的接口
            Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
            for (Class c : interfaces1) {
                System.out.println(c.getName());
            }
        }
        /**
         * 获取当前运行时类所在的包
         */
        @Test
        public void test6() {
            Class clazz = Person.class;
            Package aPackage = clazz.getPackage();
            System.out.println(aPackage.getName());
        }
        /**
         * 获取运行时类中的注解
         */
        @Test
        public void test7() {
            Class clazz = Person.class;
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation a : annotations) {
                System.out.println(a.toString());
            }
        }
    }
    
  4. 操作运行时类中指定的属性

    import java.lang.reflect.Field;
    
    /**
     * @Classname ReflectionTest
     * @Description 调用运行时类中指定的结构:属性、方法、构造器
     */
    public class ReflectionTest {
        @Test
        public void test1() throws Exception{
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
            // 获取运行时类中指定变量名的属性(只能获取 public 的),通常不使用这种方式
            Field id = clazz.getField("id");
            id.set(person,01); // 第一个参数设置给哪个对象属性,第二个参数设置值
            int pId = (int) id.get(person); // 参数设置获取哪个对象的
        }
    
        @Test
        public void test2() throws Exception{
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
    
            // 获取运行时类中指定变量名的属性(所有权限类型的)
            Field name = clazz.getDeclaredField("name");
            name.setAccessible(true); // 设置可访问
    
            name.set(person,"哈哈");
            Object o = name.get(person);
            System.out.println(o.toString());
        }
    }
    
  5. 操作运行时类中指定的方法

        /**
         * 操作运行时类中的方法
         */
        @Test
        public void testMethod() throws Exception {
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
    
            // 获取某个指定名称的方法,第一个参数指定方法名,第二个可变参数指定方法的参数列表
            Method show = clazz.getDeclaredMethod("show", String.class);
            show.setAccessible(true); // 设置方法为可访问的
    
            // 执行方法:第一个参数指定哪个对象调用方法,第二个可变参数指定参数
            Object returnValue = show.invoke(person,"CHN"); // invoke() 方法的返回值就是调用方法的返回值
            System.out.println("方法的返回值:" + returnValue);
    
            System.out.println("-----调用静态方法-----");
            //     private static void showDesc()
            Method declaredMethod = clazz.getDeclaredMethod("showDesc");
            declaredMethod.setAccessible(true);
            Object returnValue1 = declaredMethod.invoke(null);
            System.out.println(returnValue1);
        }
    
  6. 操作运行时类的构造器并生成对象

    	/**
         * 调用运行时类中的构造器
         */
        @Test
        public void testConstructor() throws Exception {
            Class<Person> clazz = Person.class;
            // private Person(String name, int age)
            // 先获取指定的构造器
            Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
            // 指定构造器是可访问的
            declaredConstructor.setAccessible(true);
            // 调用此构造器创建运行时类的对象
            Person person = declaredConstructor.newInstance("哈哈", 12);
            System.out.println(person);
        }
    

总结:

  1. 获取 Class 实例的三种方式?

    Class clazz1 = Person.class;
    Class clazz2 = Class.forName("类的全限定名");
    Person p1 = new Person();
    Class clazz3 = p1.getClass();
    
  2. 谈谈你对 Class 类的理解。
    一个 Class 实例就对应着一个加载到内存中的运行时类。

  3. 创建类的对象的方式?

    1. 直接 new 一个。
    2. 通过反射获取类的运行时类对象,使用 newInstance() 方法。
    3. 可以在类中提供一个 public 的静态工厂方法提供对象。
  4. 创建 Class 对应运行时类的对象的通用方法,代码实现。这样操作,需要对应的运行时类构造器满足哪些要求?

    Class<Person> clazz = Person.class;
    Person p1 = clazz.newInstance();
    

    要求:

    1. 必须要有空参构造器
    2. 权限要足够
  5. 在工程或 module 下有名为 “jdbc.properties” 的配置文件,文件内容为:name = Tom。如何在程序中通过代码获取 Tom 这个值。

    Properties pro = new Properties();
    ClassLoader loader = this.getClass().getClassLoader(); // 通过当前类对象拿到其对应的运行时类对象,再获取到类加载器
    InputStream is = loader.getResourcesAsStream("jdbc.properties"); // 通过类加载器的对象方法获取文件的输入流
    pro.load(is); // 将文件的输入流加载到 Properties 对象
    
    // 通过键获取文件中的值
    String name = pro.getProperty("name");
    System.out.println(name);
    
  6. 如何调用方法 show() ?

    class User {
    	public void show() {
    		System.out.println("hi...");
    	}
    }
    
    public class Test {
    	public static void main(String[] args) throws Exception{
            Class clazz = Class.forName("User"); // 获取 User 类的运行时类对象
            User user = (User) clazz.newInstance(); // 通过运行时类对象获取 User 对象
    		Method method = clazz.getDeclaredMethod("show"); // 通过运行时类对象获取 show 方法
            method.setAccessiable(ture); // 设置 show 方法的访问权限
            method.invok(user); // 调用方法
    	}
    } 
    
  7. 关于反射的理解?

    • Reflection 反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息(成员变量、构造器、方法),并能直接操作任意对象的内部属性和方法。
    • 类在加载完毕后,会在堆内存的方法区中产生一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了完整的类的结构信息。这个对象就像是一面镜子,可以通过这个镜子看到类的结构,这种行为被称之为反射
    • 框架 = 反射 + 注解 + 设计模式
  8. 体会反射机制的“动态性”

    	@Test
        public void test2() throws Exception {
            for (int i = 0; i < 100; i++) {
                int num = new Random().nextInt(3);
                String classPath = "";
                switch (num) {
                    case 0:
                        classPath = "java.util.Date";
                        break;
                    case 1:
                        classPath = "java.lang.Object";
                        break;
                    case 2:
                        classPath = "com.xiangyu.day28.classLoaderTest.ClassLoaderTest";
                        break;
                }
                Object instance = getInstance(classPath);
                System.out.println(instance.toString());
            }
        }
    	/**
         * 此方法创建一个指定类的对象。
         * @param classPath 指定类的全类名
         * @return 指定类的对象
         * @throws Exception
         */
        public Object getInstance(String classPath) throws Exception{
            Class clazz = Class.forName(classPath);
            return clazz.newInstance();
        }
    
    
  9. 反射机制提供的功能

    • 在程序运行时判断任意一个对象的所属类
    • 在程序运行时构造任意一个类的对象
    • 在程序运行时判断任意一个类所具有的成员变量和方法
    • 在程序运行时获取泛型信息
    • 在程序运行时调用任意一个对象的成员变量和方法
    • 在程序运行时处理注解
    • 生成动态代理
  10. 相关API

  • java.lang.Class 反射的源头
  • java.lang.reflact.Method
  • java.lang.reflact.Field
  • java.lang.reflact.Constructor
  1. 创建类的对象的方式?

    1. new + 构造器
    2. 静态的对象工厂方法(单例模式等)
    3. 通过反射
  2. Class 实例可以是哪些结构?

    1. 类(外部类,成员【成员内部类,静态内部类】,局部内部类,匿名内部类)
    2. 接口
    3. 数组
    4. 枚举类
    5. annotation 注解
    6. primitive 基本数据类型
    7. void
  3. 类的加载过程(了解)
    image-2022061817031584014. 类的加载器的作用 将class文件字节码内容加载到内存中,并将这些静态数据转换成方 法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。

  4. 类的加载器的分类
    · 在 Java 虚拟机角度来看,只存在两种不同的类加载器:

    1. 启动类加载器(Bootstap ClassLoader),其 HotSpot 虚拟机实现是由 C++ 语言编写的,是虚拟机本身的一部分。
    2. 其他所有类加载器,这些类加载器都由 Java 语言实现,独立存在于虚拟机之外,并且全部继承自 java.lang.ClassLoader 。
    3. 但是在开发人员的角度来看,类加载器的划分要细致一些,下图为 HotSpot 虚拟机实现的 ClassLoader 模型
      image-20220618171748364
  5. Java 类编译、运行的执行的流程
    image-20220618171109400

  6. 创建运行时类对象

    Class<Perason> clazz = Person.calss;
    Person p = clazz.newInstance();
    
  7. 获取运行时类的完整结构

    1. 获取属性:getFields(); getDeclaredFields();
    2. 获取方法:getMethods(); getDeclaredMethods();
    3. 获取构造器:getConstructors(); getDeclaredConstructors();
    4. 获取父类:getSuperclass();
    5. 获取父类(带泛型):getGenericSuperclass();
    6. 获取父类(带泛型)的泛型:
    Type genericSuperclass = clazz.getGenericSuperclass();
    ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    Type[] actualTypeArguments = paramType.getActualTypeArguments();
    
    1. 获取运行时类实现的接口:getInterfaces();
    2. 获取继承的父类中实现的接口:getSuperclass().getInterfaces();
    3. 获取当前运行时类所在的包:getPackage();
    4. 获取运行时类中的注解:getAnnotations();
  8. 调用运行时类的指定结构:

    1. 获取指定属性:getField(String); getDeclaredField(String);
    2. 调用指定属性需要使用 newInstance(); 方法获取一个对象,使用对象调用指定属性。
    3. 获取指定方法:getMethod(String,Class< ? >…); getDeclaredMethod(String,Class< ? >…);
    4. 调用指定方法使用 invoke() 方法(没有被 static 关键字修饰的方法也需要通过对象调用)
    5. 获取指定构造器:getConstructor(Class< ? >…); getDeclaredConstructor(Class< ? >…);
    6. 调用指定构造器使用 newInstance() 方法。

反射的应用:动态代理

一、静态代理示例

/**
 * @Classname StaticProxyTest
 * @Description 静态代理的示例,其特点是:代理类和被代理类在编译期间,就确定下来了
 */
interface ClothFactory {
    void produceCloth();
}
// 代理类
class ProxyClothFactory implements ClothFactory {

    private ClothFactory factory; // 使用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");
        factory.produceCloth();
        System.out.println("代理工厂做一些善后工作");
    }
}

// 被代理类
class LiNingClothFactory implements ClothFactory {

    @Override
    public void produceCloth() {
        System.out.println("李宁工厂生产一批运动服");
    }
}

public class StaticProxyTest {
    public static void main(String[] args) {
        ClothFactory lining = new LiNingClothFactory(); // 被代理类对象
        ClothFactory pcf = new ProxyClothFactory(lining); // 代理类对象
        pcf.produceCloth();
    }

}

二、动态代理示例


import com.sun.corba.se.spi.ior.ObjectKey;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Classname ProxyTest
 * @Description 动态代理的示例
 */
interface Human {
    String getBelief();
    void eat(String food);
}
// 被代理类
class SuperMan implements Human {

    @Override
    public String getBelief() {
        return "I believe i can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }
}

/*
    要想生成动态代理要解决的问题:
    1.如何根据加载到内存的被代理类,动态的创建一个代理类及其对象
    2.当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法
 */
class ProxyFactory {
    // 调用此方法返回一个代理类的对象
    public static Object getProxyInstance(Object obj) { // obj:被代理类对象
        /*
            第一个参数获取类加载器(被代理类是哪个类加载器加载的,我们就跟他一样)
            第二个参数获取接口(被代理类实现了哪些接口)
        */

        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object obj; //赋值时需要使用被代理类对象进行赋值
    public void bind(Object obj) {
        this.obj = obj;
    }

    // 当我们通过地阿里类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    // 将被代理类要执行的方法 a 的功能声明在 invoke() 中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        HumanUtil humanUtil1 = new HumanUtil();
        humanUtil1.method1();

        // method 即为代理类对象调用的方法,此方法也就作为被代理类对象要调用的方法
        // obj本身就是被代理类的对象
        Object invokeValue = method.invoke(obj, args);
        // 上述方法的返回值就作为当前类中 invoke() 方法的返回值

        humanUtil1.method2();
        return invokeValue;
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        // 当通过代理类调用方法时,会自动调用被代理类中同名的方法。
        System.out.println(proxyInstance.getBelief());
        proxyInstance.eat("火锅");
        System.out.println("**************************");
        LiNingClothFactory lining = new LiNingClothFactory();
        ClothFactory clothFactory = (ClothFactory) ProxyFactory.getProxyInstance(lining);
        clothFactory.produceCloth();
    }
}

class HumanUtil {
    public void method1 () {
        System.out.println("==========通用方法1===========");
    }
    public void method2 () {
        System.out.println("==========通用方法2===========");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值