Java核心知识体系5:反射机制详解

Java核心知识体系1:泛型机制详解
Java核心知识体系2:注解机制详解
Java核心知识体系3:异常机制详解
Java核心知识体系4:AOP原理和切面应用

1 介绍

无论是那种语言体系,反射都是必不可少的一个技术特征。从Java体系来说,很多常用的技术框架或多或少都使用到了反射技术,比如Spring、MyBatis、RocketMQ、FastJson 等等。反射技术强大而必要,在大多数框架中起到举足轻重的作用。所以,反射也是Java必不可少的核心技术之一。

接下来我们来看看反射的一些技术要点:

  1. 反射的概念(即什么是反射)?
  2. 反射的作用(它帮我们解决了哪些问题)?
  3. 反射的实现原理?
  4. 如何使用反射?
    下面我就针对以上的疑问,一一来讲解。

1.1 反射是什么?

Java反射(Reflection)是Java语言的一个核心特性,它允许运行中的Java代码对自身进行自我检查,甚至修改自身的组件。具体来说,反射机制提供了在运行状态中,对于任意一个类,都能够了解这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法在Java中就叫做反射。
一句话总结:反射就是在运行时才具体知晓要操作的类是什么结构,并在运行时获取类的完整构造,并调用对应的方法、属性等。

Java的反射主要包括以下三个部分:

  • 类的加载:Java的类在需要使用时才会被加载到JVM中。这个过程是由类加载器(ClassLoader)完成的。类加载器首先检查这个类是否已经被加载过,如果还没有加载,那么就会从磁盘上加载类的字节码并创建一个Class对象。
  • 获取类的信息:当一个对象被创建后,我们可以使用反射来获取这个对象的Class对象。通过这个Class对象,我们可以获取到这个类的所有属性和方法。
  • 方法的调用:通过反射,我们可以动态的调用一个对象的方法。即使这个方法是一个私有的方法,也能够通过反射来调用。

1.2 为什么要用反射?

Java Reflection功能非常强大,并且非常有用,比如:

  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
  • 获取任意对象的属性,并且能改变对象的属性
  • 调用任意对象的方法
  • 判断任意一个对象所属的类
  • 实例化任意一个类的对象
  • 通过反射我们可以实现动态装配,降低代码的耦合度,实现动态代理等。

具体的应用场景:

  • 框架设计:许多框架,如Spring,Hibernate等,都大量使用了反射来实现对象的自动装配,动态代理等功能。
  • 单元测试:单元测试框架(如JUnit)会使用反射来调用被注解的方法。
  • 插件化:为了实现插件化,可以通过反射加载不同的插件。
  • 对象序列化与反序列化:在对象进行序列化和反序列化的时候,会使用反射获取到对象的所有属性和方法。

2 反射的使用

在Java中,Class类与java.lang.reflect类库配合对反射技术进行了完整的支持。在反射的Package中,我们经常使用功能类如下:

  • Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象
  • Field类表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)
  • Method类表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)

下面将对这几个类进行详细介绍。

2.1 反射创建类对象

一般情况下我们通过反射创建类对象主要有两种方式:

  • 通过 Class 对象的 newInstance() 方法

  • 通过 Constructor 对象的 newInstance() 方法

  • 通过 Class 对象的 newInstance() 方法实现

Class clz = Class.forName("com.ad.reflection.TestRefle");
TestRefle tr= (TestRefle)clz.newInstance();
  • 通过 Constructor 对象的 newInstance() 方法实现
Class clz = Class.forName("com.ad.reflection.TestRefle");
Constructor constructor = clz.getConstructor();
TestRefle tr= (TestRefle)constructor.newInstance();

这边需要注意,通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。
下面的代码演示的是通过 Constructor 调用有参构造方法进行了类对象初始化:

Class clz = Class.forName("com.ad.reflection.TestRefle");
Constructor constructor = clz.getConstructor(String.class);
TestRefle tr= (TestRefle)constructor.newInstance("提供一个String参数");

接下来我们继续,通过具体的API获取详细的类信息:类信息、方法信息、属性信息等。

2.2 获取Class类对象

 // 获取Class对象的三种方式
 根据类名: Class mailClass = MailInfo.class;
 根据对象: Class mailClass = new MailInfo().getClass();
 根据全限定类名: Class mailClass = Class.forName("com.ad.MailInfo");
 
 // 根据对象获取信息和实例对象
 获取全限定类名: mailClass.getName();
 获取类名: mailClass.getSimpleName();
 实例化: userClass.getDeclaredConstructor().newInstance();

更加详细Class类获取参考如下:

方法 用途
forName() (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 (2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName() 取全限定的类名(包括包名),即类的完整名字。 getSimpleName() 获取类名(不包括包名)
getCanonicalName() 获取全限定的类名(包括包名)
isInterface() 判断Class对象是否是表示一个接口
getInterfaces() 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss() 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance() 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。

2.3 获取类的成员变量的信息

Field[] fields = _class.getDeclaredFields();

更加详细成员变量获取参考如下:

方法 用途
getField(String name) 获得某个公有的属性对象
getFields() 获取所有的公有的属性对象
getDeclaredField(String name) 获得某个属性对象(public和非public)
getDeclaredFields() 获得所有属性对象(public和非public)

2.4 获得类方法

Method[] methods = _class.getDeclaredMethods();

更加详细方法获取参考如下:

方法 用途
getMethod(String name, Class...<?> paramerterTypes) 获得某个公有的方法对象
getMethods() 获取所有的公有的方法对象
getDeclaredMethod(String name, Class...<?> paramerterTypes) 获得对应类下某个方法(public和非public)
getDeclaredMethods() 获得对应类下所有方法(public和非public)

2.5 获得构造函数

Constructor[] constructors = _class.getDeclaredConstructors();

更加详细构造函数获取参考如下:

方法 用途
getConstructor(Class...<?> paramerterTypes) 获得该类中与参数类型匹配的公有构造方法
getConstructors() 获取该类的所有公有构造方法
getDeclaredConstructor(Class...<?> paramerterTypes) 获得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 获取该类的所有构造方法

这样通过反射就可以做在运行时获取类的完整构造,并获得类信息了。

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(即类的属性)
Method类 代表类的方法
Constructor类 代表类的构造函数

通过上面的几个示例我们基本了解了反射的使用,但这仅仅是使用,我们还需深入理解反射背后的底层实现原理。

3 反射原理分析

3.1 反射的调用流程

1、编写完Java项目之后,java文件都会被编译成一个.class文件
2、这些class文件在程序运行时会被ClassLoader加载到JVM中,当一个类被加载以后,JVM就会在内存中自动产生一个Class对象。
3、通过Class对象获取 Field(属性)、Method(方法)、Construcor(构造函数)
我们平时通过new的形式创建对象,本质上就是通过Class来创建个新对象
image
通过上面的流程我们可以看出反射的优势:

  • 动态装配

我们的程序在运行时,可能不一定会用到所有我们编写和构建的类,这样避免启动时间太长并且浪费大量无用的机器资源。
取而代之的是动态的加载一些类,这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。

  • 降低耦合
    如果你在使用new时明确的指定类名,那这就是典型的硬编码实现,而在使用反射的时候,可以只传入类名参数,就可以生成对象,降低了耦合度,使得程序更具灵性。

完整的调用流程,图片来自网上,比较模糊,后续再补一个
image

3.2 反射的应用场景

image

  • 框架设计:许多框架,如Spring,Hibernate,mybatis,dubbo,rocketmq等,都大量使用了反射来实现对象的自动装配,动态代理等功能。
  • 单元测试:单元测试框架(如JUnit)会使用反射来调用被注解的方法。
  • 插件化:为了实现插件化,可以通过反射加载不同的插件。
  • 对象序列化与反序列化:在对象进行序列化和反序列化的时候,会使用反射获取到对象的所有属性和方法。
  • 动态配置、动态代理:通过反射去读取配置,以及代理请求

4 反射经典案例解析

以下案例来自百度文心一言大模型自动生成,已调试通过。

import java.lang.reflect.Method;  
  
public class ReflectionExample {  
    public static void main(String[] args) {  
        try {  
            // 获取目标类的Class对象  
            Class<?> targetClass = Class.forName("java.util.ArrayList");  
  
            // 获取目标类的所有公共方法  
            Method[] methods = targetClass.getMethods();  
  
            // 遍历所有方法并打印方法名  
            for (Method method : methods) {  
                System.out.println(method.getName());  
            }  
  
            // 获取特定方法,比如添加元素的add方法  
            Method addMethod = targetClass.getMethod("add", Object.class);  
  
            // 创建目标类的实例对象  
            Object targetObject = targetClass.newInstance();  
  
            // 调用add方法添加元素  
            addMethod.invoke(targetObject, "Hello, World!");  
  
            // 获取目标类的所有属性(字段)并打印属性名  
            Field[] fields = targetClass.getDeclaredFields();  
            for (Field field : fields) {  
                System.out.println(field.getName());  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

这个案例展示了如何使用反射来获取目标类的Class对象,获取并打印目标类的所有公共方法,获取特定方法,创建目标类的实例对象,调用目标类的方法,以及获取并打印目标类的所有属性(字段)。

总结

无论是那种语言体系(C#、Java等等),反射都是必不可少的一个技术特征。而从Java体系来说,很多常用的技术框架或多或少都使用到了反射技术,比如Spring、MyBatis、RocketMQ、FastJson 等等。
学习好Java 反射技术能帮助你更好的理解底层调用的原理,也有助于设计更加 轻巧、高内聚、低耦合 的业务框架。

posted @ 2023-10-24 13:02  Hello-Brand  阅读(794)  评论(0编辑  收藏  举报