Java中的反射与JDK动态代理

目录

  • 1. 前言
  • 2. 反射的概述
    • 2.1. 反射的概念
    • 2.2. 反射的前提
  • 3. 反射的使用
    • 3.1. `Class` 类
      • 3.1.1. 获取 `Class` 对象
    • 3.2. `Constructor` 类
    • 3.3. `Field` 类
    • 3.4. `Method` 类
  • 4. 反射与 `JDK` 动态代理
    • 4.1. `Proxy` 类
    • 4.2. `InvocationHandler` 接口

1. 前言

一直以来反射技术都是 Java 中的闪亮点,这也是目前大部分框架(如 Spring/Mybatis 等)得以实现的支柱。在 Java 中,Class 类与 java.lang.reflect 类库一起对反射技术进行了全力的支持

2. 反射的概述

2.1. 反射的概念

Java 反射机制是在 运行状态 中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为「正」。而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用

Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类 Apple,而第二段代码则是在运行时通过字符串值才得知要运行的类 com.chenshuyi.reflect.Apple

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法

2.2. 反射的前提

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件对应的 Class 类型的对象

3. 反射的使用

JavaClass 类与 java.lang.reflect 类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有

  • Class 类:运行在内存中的所有类都是该 Class 类的实例对象,每个 Class 类对象内部都包含了本来的所有信息。记着一句话,通过反射干任何事,先找 Class 准没错!
  • Constructor 类:描述一个类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符等
  • Field 类:描述一个类的属性,内部包含了该属性的所有信息,例如数据类型,属性名,访问修饰符等
  • Method 类:描述一个类的所有方法(包括抽象方法),内部包含了该方法的所有信息,与 Constructor 类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的

3.1. Class

如图是类的正常加载过程:反射的原理在与 Class 对象。熟悉一下加载的时候:Class 对象的由来是将 Class 文件读入内存,并为之创建一个 Class 对象

Java中的反射与JDK动态代理_第1张图片

  • Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是 JVM 中有 N 多的实例每个类都有其 Class 对象(包括基本数据类型)
  • Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM 已经帮我们创建好了

没有公共的构造方法,方法共有 64 个太多了。下面用到哪个就详解哪个吧

Java中的反射与JDK动态代理_第2张图片

3.1.1. 获取 Class 对象

  • Object ——> getClass();
  • 任何数据类型(包括基本数据类型)都有一个静态的 Class 属性
  • 通过 Class 类的静态方法:forName(String className)

第一个是因为 Object 类中的 getClass 方法,因为所有类都继承 Object 类。从而调用 Object 类来获取

Java中的反射与JDK动态代理_第3张图片

public class ReflexTest {

    public static void main(String[] args) {
        /*第一种方式获取Class对象*/
        Student student = new Student();/*这一new产个生一Student对象,一个Class对象*/
        Class<? extends Student> studentClass1 = student.getClass();
        System.out.println(studentClass1.getName());

        /*第二种方式获取Class对象*/
        Class<Student> studentClass2 = Student.class;
        System.out.println(studentClass1 == studentClass2);

        /*第三种方式获取Class对象*/
        try {
            /*注意此字符串必须是真实路径,就是带包名的类路径,包名.类名*/
            Class<?> studentClass3 = Class.forName("org.dabao.reflex.Student");
            System.out.println(studentClass2 == studentClass3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


结果:
org.dabao.reflex.Student
true
true

注意:在运行期间,一个类,只有一个 Class 对象产生。三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法

3.2. Constructor

Constructor 类存在于反射包 java.lang.reflect 中,反映的是 Class 对象所表示的类的构造方法。获取 Constructor 对象是通过 Class 类中的方法获取的Class 类与 Constructor 相关的主要方法如下

方法返回值 方法名称 方法说明
static Class forName(String className)<、code> 返回与带有给定字符串名的类或接口相关联的 Class 对象
Constructor getConstructor(Class... parameterTypes) 返回指定参数类型、具有public访问权限的构造函数对象
Constructor[] getConstructors() 返回所有具有public访问权限的构造函数的Constructor对象数组
Constructor getDeclaredConstructor(Class... parameterTypes) 返回指定参数类型、所有声明的(包括private)构造函数对象
Constructor[] getDeclaredConstructor() 返回所有声明的(包括private)构造函数对象
T newInstance() 创建此 Class 对象所表示的类的一个新实例

其他方法请查看 API

3.3. Field

Field 类存在于反射包 java.lang.reflect 中,它提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。同样的道理,我们可以通过 Class 类的提供的方法来获取代表字段信息的 Field 对象Class 类与 Field 对象相关方法如下

方法返回值 方法名称 方法说明
Field getDeclaredField(String name) 获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field[] getDeclaredField() 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
Field getField(String name) 获取指定name名称、具有public修饰的字段,包含继承字段
Field[] getField() 获取修饰符为public的字段,包含继承字段

其他方法请查看 API

3.4. Method

Method 类存在于反射包 java.lang.reflect 中,它提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是 Class 类获取 Method 对象相关的方法

方法返回值 方法名称 方法说明
Method getDeclaredMethod(String name, Class... parameterTypes) 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
Method[] getDeclaredMethod() 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
Method getMethod(String name, Class... parameterTypes) 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法
Method[] getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法

4. 反射与 JDK 动态代理

我们知道 Spring 主要有两大思想,一个是 IOC,一个就是 AOP。对于 IOC 容器中 bean 的创建就是使用了 Java 的反射技术;而对于 Spring 的核心 AOP 来说,我们知道 AOP 的实现原理之一就是 JDK 的动态代理机制,而 JDK 的动态代理本质就是利用了反射技术来实现的

JDK 的动态代理机制中,有两个重要的类和接口,一个是 InvocationHandler 接口、一个是 Proxy 类,这一个类和接口是实现我们动态代理所必须用到的

4.1. Proxy

Proxy 类来自于 java.lang.reflect 包下,它就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance() 这个方法

Object proxy = Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • loader:一个 ClassLoader 类加载器对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载
  • interfaces:一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我代理对象就能调用这组接口中的方法了
  • h:一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上

通过 Proxy.newProxyInstance() 创建的代理对象是在 JVM 运行时 动态生成的一个对象 ,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象

4.2. InvocationHandler 接口

InvocationHandler 接口也来自于 java.lang.reflect 包下。每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 handler当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke() 方法来进行调用

package java.lang.reflect;

public interface InvocationHandler {

	public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
  • proxy:指代我们所代理的那个真实对象
  • method:指代的是我们所要调用真实对象的某个方法的 Method 对象
  • args:指代的是调用真实对象某个方法时接受的参数

有关 JDK 动态代理的原理 1:https://blog.csdn.net/weixin_38192427/article/details/113093728
有关 JDK 动态代理的原理 2:https://blog.csdn.net/Dream_Weave/article/details/84183247

你可能感兴趣的:(java,#,基础,java)