Java 学习—反射机制:解锁代码的无限可能
一、关于反射
1.1 简介
Java 反射(Reflection)是 Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。
反射机制能够使程序具备更大的灵活性和扩展性
Oracle 官方对反射的解释:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
1.2 发展
Java 反射机制的发展历程可以分为几个重要的阶段,随着 Java 语言和平台的演进,反射的功能和使用场景也不断丰富。
1. Java 1.0 版本(1996 年)
初步引入:Java 在最初的版本中就引入了反射机制的基础概念,但功能较为有限。主要是为了支持动态加载类和基础的类信息查询。
2. Java 1.1 版本(1997 年)
增强功能:Java 1.1 对反射机制进行了增强,增加了对接口的支持,使得开发者可以更灵活地操作对象。
引入
java.lang.reflect
包:这个包提供了获取类的信息、调用方法、访问属性的功能。
3. Java 2(JDK 1.2,1998 年)
引入集合框架:反射与集合框架的结合使用得到了广泛关注,开发者能够通过反射创建和操作集合中的对象。
安全性增强:反射机制也开始关注安全性,引入了安全管理器,限制某些反射操作。
4. Java 5(JDK 1.5,2004 年)
泛型支持:Java 5 引入了泛型,反射也随之支持泛型类型的查询和操作,提升了类型安全性。
注解机制:引入了注解(Annotations),反射机制开始被广泛应用于框架中,通过反射读取和处理注解信息。
5. Java 6(2006 年)及后续版本
性能优化:随着反射在大型框架(如 Spring、Hibernate)中的广泛应用,Java 的开发团队逐步对反射的性能进行了优化,尽量减少反射操作的开销。
动态代理:Java 6 中的
java.lang.reflect.Proxy
类使得动态代理的实现成为可能,进一步增强了反射的应用场景。
6. Java 8(2014 年)
Lambda 表达式:与反射结合使用,提升了函数式编程的灵活性。
增强的类型推断:使得反射在处理复杂数据类型时变得更加高效和安全。
7. Java 9 及后续版本
模块化系统:Java 9 引入了模块化(Project Jigsaw),反射的使用在模块间的访问控制上得到了新的关注。
API 和性能持续改进:持续优化反射的 API 和性能,以适应现代应用程序的需求。
1.3 特点
优点:
灵活性:能够在运行时决定使用哪个类或方法。
动态性:支持在程序运行过程中动态生成类和对象。
缺点:
性能开销:反射操作相对较慢,因为涉及动态解析。
安全性问题:使用反射可能会破坏封装性,访问私有成员。
代码可读性:反射使代码难以理解和维护。
1.4 应用场景
很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架。 很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
举一个例子,在运用 Struts 2 框架的开发中我们一般会在 struts.xml
里去配置 Action
,比如:
配置文件与 Action
建立了一种映射关系,当 View 层发出请求时,请求会被 StrutsPrepareAndExecuteFilter
拦截,然后 StrutsPrepareAndExecuteFilter
会去动态地创建 Action 实例。比如我们请求 login.action
,那么 StrutsPrepareAndExecuteFilter
就会去解析 struts.xml 文件,检索 action 中 name 为 login 的 Action,并根据 class 属性创建 SimpleLoginAction 实例,并用 invoke 方法来调用 execute 方法,这个过程离不开反射。
对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。而对于一般的开发者来说,不深入框架开发则用反射用的就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。
以下是一些常见的应用场景:
1. 框架开发
依赖注入(Dependency Injection, DI) :框架如 Spring 使用反射来创建和管理 Bean 对象,实现依赖注入。通过反射,框架可以在运行时动态地将依赖对象注入到目标对象中。
面向切面编程(Aspect-Oriented Programming, AOP) :框架如 Spring AOP 使用反射来织入切面,实现横切关注点的分离。
对象关系映射(Object-Relational Mapping, ORM) :框架如 Hibernate 使用反射来映射数据库表和 Java 对象,实现持久化操作。
2. 单元测试
单元测试框架:框架如 JUnit 使用反射来发现和执行测试方法。通过反射,测试框架可以自动扫描类中的测试方法并执行它们。
Mock 对象:在单元测试中,经常需要模拟(mock)一些对象的行为。框架如 Mockito 使用反射来创建和管理这些 Mock 对象。
3. 动态代理
Java 动态代理:通过反射生成实现了特定接口的代理对象,用于实现 AOP、远程调用(RPC)等功能。
CGLIB 动态代理:CGLIB 是一个强大的高性能代码生成库,它可以在运行时生成一个类的子类对象,并使用反射来实现方法拦截。
4. 配置文件解析
XML/JSON/YAML 配置文件:许多框架和库使用反射来解析配置文件,并根据配置文件中的信息动态创建和配置对象。例如,Spring 可以通过 XML 或注解配置文件来管理 Bean。
5. 序列化和反序列化
JSON 库:库如 Gson 和 Jackson 使用反射来将 Java 对象转换为 JSON 字符串,或将 JSON 字符串转换为 Java 对象。
XML 库:库如 JAXB 使用反射来将 Java 对象转换为 XML 文档,或将 XML 文档转换为 Java 对象。
6. 插件系统
动态加载插件:通过反射,可以在运行时动态加载和卸载插件。这种机制常用于开发可扩展的应用程序,如 IDE、游戏引擎等。
7. 数据绑定
数据绑定框架:框架如 JavaFX 和 Vaadin 使用反射来将用户界面组件的数据绑定到模型对象上,实现双向数据绑定。
8. 脚本语言集成
嵌入脚本语言:Java 可以通过反射来调用嵌入的脚本语言(如 JavaScript、Python)中的函数和方法,实现混合编程。
9. 安全性和权限管理
安全管理:通过反射可以动态地检查和设置对象的访问权限,实现细粒度的安全控制。
10. 日志记录
日志框架:框架如 Log4j 和 SLF4J 使用反射来获取类的信息,以便在日志记录中包含详细的上下文信息。
1.5 主要 API
在 Java 中,反射主要涉及以下几个重要的类和接口:
Class 类:
是进行反射的核心类。每个类在 Java 中都有一个与之对应的 Class 对象。
常用方法:
forName(String className)
:通过类的完全限定名获取 Class 对象。getDeclaredMethods()
:获取所有声明的方法,包括私有、保护和公共方法。getDeclaredFields()
:获取所有声明的字段。getDeclaredConstructors()
:获取所有声明的构造函数。newInstance()
:创建一个类的新实例。
Method 类:
表示一个类的方法。
常用方法:
invoke(Object obj, Object... args)
:调用此 Method 对象所表示的方法。getName()
:获取方法的名称。getParameterTypes()
:获取方法的参数类型。getReturnType()
:获取方法的返回类型。
Field 类:
表示一个类的字段(属性)。
常用方法:
get(Object obj)
:获取指定对象的字段值。set(Object obj, Object value)
:设置指定对象的字段值。getType()
:获取字段的类型。
Constructor 类:
表示类的构造函数。
常用方法:
newInstance(Object... initargs)
:创建一个新对象的实例。getParameterTypes()
:获取构造函数的参数类型。
AccessibleObject 类:
Method、Field 和 Constructor 类都继承自 AccessibleObject,提供了控制访问权限的能力。
常用方法:
setAccessible(boolean flag)
:设置对象是否可以进行访问(即使是私有)。
二、反射工作原理
调用反射的总体流程如下:
1、当编写完一个 Java 项目之后,每个 java 文件都会被编译成一个.class 文件。
2、这些 class 文件在程序运行时会被 ClassLoader 加载到 JVM 中,当一个类被加载以后,JVM 就会在内存中自动产生一个 Class 对象。
3、通过 Class 对象获取 Field/Method/Construcor
我们一般平时是通过 new 的形式创建对象实际上就是通过这些 Class 来创建的,只不过这个 class 文件是编译的时候就生成的,程序相当于写死了给 jvm 去跑。
反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到 jvm,而是在运行时根据需要才加载。
原来使用 new 的时候,需要明确的指定类名,这个时候属于硬编码实现,而在使用反射的时候,可以只传入类名参数,就可以生成对象,降低了耦合性,使得程序更具灵活性。
二、反射的基本运用
反射可以用于判断任意对象所属的类,获得 Class 对象,构造任意一个对象以及调用一个对象。
这里介绍基本反射功能的使用和实现(反射相关的类一般都在 java.lang.relfect 包里)。
2.1 获取 Class 对象
在 Java 中,获取一个类的Class
对象是使用反射的第一步,Class
对象用于操作类的信息。
方式
有以下集中方式可以获取 Class 类的实例:
1、若已知具体的类,可以通过类的 class 属性获取,该方式最为安全可靠,且程序性能最高。
2、已知某个类的实例,通过调用该实例的 getClass 方法获取 Class 对象。
3、已知一个类的全类名,且该类在类路径下,可以通过静态方法 forName()获取。
4、内置基本数据类型可以直接使用类名.Type 获取。
5、利用 ClassLoader(类加载器)获取。
使用类加载器可以加载类并获取 Class
对象,特别是在自定义类加载的场景中:
示例
2.2 获取类的字段
使用反射获取类的成员变量(字段)的方法主要依赖于Class
类和Field
类。下面是详细的步骤,展示如何利用反射获取类的成员变量。
步骤
1、获取 Class 对象:
使用
Class.forName()
或者MyClass.class
方式获取目标类的Class
对象。
2、获取字段:
可以使用
getDeclaredFields()
方法获取所有声明的字段,包括私有、保护和公共字段。如果只想获取公共字段,可以使用
getFields()
方法。
3、访问字段:
通过
Field
对象,可以获取字段的类型、名称和访问字段的值或者设置字段的值。
示例
以下示例中定义了一个简单的类Person
,然后通过反射获取其成员变量。
输出结果
运行该代码后,输出如下所示:
注意事项
访问控制:对于私有字段,需要调用
setAccessible(true)
来允许访问。异常处理:在使用反射时,可能会遇到多种异常,例如
ClassNotFoundException
、NoSuchFieldException
、IllegalAccessException
等,所以需要进行适当的异常处理。性能:过度使用反射可能会导致性能下降,通常应在必要时使用。
通过上述步骤和示例,您可以轻松获取类的成员变量。使用反射时要谨慎,确保理解其影响和使用场景
2.3 获取类的方法
使用反射机制可以动态地获取类的信息,包括类的方法。下面是如何使用反射获取类的方法的步骤和示例代码。
步骤
1、获取 Class
对象:首先,需要获取目标类的 Class
对象。
2、使用反射获取方法:
使用 getMethods()
方法获取所有公有的方法(包括继承的方法)。
使用 getDeclaredMethods()
方法获取所有声明的方法(包括私有方法)。
3、遍历方法:遍历获取的方法,并打印出相关的信息,例如方法名、返回类型和参数类型。
示例
以下是一个示例代码,展示了如何获取某个类的方法信息:
2.4 获得类的构造函数
使用反射机制可以获取类的构造函数。下面是获取构造函数的步骤和示例代码。
步骤
1、获取 Class
对象:获取目标类的 Class
对象。
2、使用反射获取构造函数:
使用 getConstructors()
方法获取所有公有构造函数。
使用 getDeclaredConstructors()
方法获取所有声明的构造函数(包括私有构造函数)。
3、遍历构造函数:遍历获取的构造函数,打印出构造函数的信息,例如名称和参数类型。
示例
2.5 调用类的方法
使用反射机制可以动态地调用一个类的方法。下面是实现这一功能的步骤和示例代码。
步骤
1、获取 Class
对象:获取目标类的 Class
对象。
2、获取方法:
使用 getMethod()
方法获取公有方法。
使用 getDeclaredMethod()
方法获取所有声明的方法(包括私有方法)。
3、调用方法:通过 Method
对象调用目标方法。
示例
以下是一个示例代码,展示如何使用反射调用类的方法:
2.6 利用反射创建数组
可以使用反射来动态创建数组。通过 java.lang.reflect.Array
类,可以创建和操作数组。以下是创建数组的步骤和示例代码。
步骤
获取数组的
Class
对象:使用Class.forName()
或类型名.class
获取数组元素的Class
对象。使用
Array.newInstance()
创建数组:调用该方法并传入数组的Class
对象和数组的维度(长度)。
示例
以下示例展示如何使用反射创建一维和二维数组:
文章转载自:ccm03
评论