南昌网站建设公司服务,百度一下做网站,企业管理培训课程,有什么网站可以发布个人信息✅作者简介#xff1a;大家好#xff0c;我是Leo#xff0c;热爱Java后端开发者#xff0c;一个想要与大家共同进步的男人#x1f609;#x1f609; #x1f34e;个人主页#xff1a;Leo的博客 #x1f49e;当前专栏#xff1a;Java从入门到精通 ✨特色专栏#xff…✅作者简介大家好我是Leo热爱Java后端开发者一个想要与大家共同进步的男人 个人主页Leo的博客 当前专栏Java从入门到精通 ✨特色专栏MySQL学习 本文内容反射机制详解 个人知识库 Leo知识库欢迎大家访问 目录 前言什么是反射反射的场景1. 动态对象创建示例代码 2. 动态方法调用示例代码 3. 访问和修改私有字段示例代码 4. 框架和库的实现Spring 示例 5. 动态代理示例代码 反射的基础认识Class类认识类加载类加载机制类的加载流程 学会使用反射获取Class对象的几种方式1. 通过类名静态方式2. 通过对象的 getClass() 方法动态方式3. 通过 Class.forName() 方法动态加载4. 通过类加载器ClassLoader说明 API基本使用 反射的基本原理 前言
大家好我是Leo哥今天一起来回顾一下反射机制。
什么是反射
反射Reflection 是编程语言中的一种能力它允许程序在运行时动态地检查和操作程序元素比如类、方法、字段等。通俗地说反射就是程序能够在运行时查看自己并做出相应调整的能力。
如果了解过框架底册的话那么对反射一定不陌生。
反射之所以被称为框架的灵魂主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
假设你有一辆汽车通常你需要知道汽车的型号、颜色等信息来驾驶它。但如果汽车具备 反射 能力它自己就能告诉你它的型号、颜色甚至你可以通过特定的方法直接改变它的某些属性。编程中的反射就是这样一种机制让程序能够动态地查看和修改自身。
反射的场景
平时大部分时候都是在写业务代码很少会接触到直接使用反射机制的场景。
但是这并不代表反射没有用。相反正是因为反射你才能这么轻松地使用各种框架。像 Spring、Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
其实反射有许多具体的应用场景的。
反射是一种强大的工具它允许程序在运行时检查和操作类的结构包括类的属性、方法和构造函数。反射有许多具体的应用场景以下是一些常见的应用场景和示例
1. 动态对象创建
反射可以根据类名在运行时创建对象实例。这对于工厂模式或需要根据配置文件创建实例的场景非常有用。
示例代码
Class? clazz Class.forName(com.example.MyClass);
Object instance clazz.getDeclaredConstructor().newInstance();2. 动态方法调用
反射允许在运行时调用对象的方法这在需要根据配置或用户输入决定调用哪个方法时特别有用。
示例代码
Method method clazz.getMethod(myMethod, String.class);
method.invoke(instance, Hello);3. 访问和修改私有字段
反射可以绕过访问控制机制访问和修改私有字段。这对于调试、框架开发和需要操作内部状态的场景非常有用。
示例代码
Field field clazz.getDeclaredField(privateField);
field.setAccessible(true);
field.set(instance, newValue);4. 框架和库的实现
许多流行的框架和库如 Spring、Hibernate使用反射来实现依赖注入、对象关系映射和自动化测试等功能。
Spring 示例
Spring 使用反射来自动注入依赖项例如
Autowired
private MyService myService;Spring 在运行时使用反射来查找和注入 MyService 的实例。
5. 动态代理
Java 动态代理机制基于反射允许在运行时创建接口的代理实例。动态代理常用于 AOP面向切面编程和拦截器模式。
示例代码
InvocationHandler handler new MyInvocationHandler(targetObject);
MyInterface proxy (MyInterface) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),handler
);其实注解中也大量使用到了反射机制例如我们比较熟悉的Spring框架我们通过使用一个Component注解就声明了一个类为 Spring Bean 呢为什么你通过一个 Value注解就读取到配置文件中的值呢
这些都是因为反射机制获取到类/属性/方法/方法的参数上的注解。你获取到注解之后就可以做进一步处理了。
反射的基础
简单来说反射就是为把Java类中的各种成分映射成一个个的Java对象。
例如一个类有成员变量、方法、构造方法、包等等信息利用反射技术可以对一个类进行解剖把个个组成部分映射成一个个对象。
这里我们首先需要理解 Class类以及类的加载机制。 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。
认识Class类
在 Java 反射中Class 类是核心部分。它表示正在运行的 Java 应用程序中的类或接口。每个类或接口都有一个与之对应的 Class 对象。当 JVM 加载某个类时它会创建一个 Class 对象来表示这个类。
Class类Class类也是一个实实在在的类存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)每个Java类运行时都在JVM里表现为一个class对象可通过类名.class、类型.getClass()、Class.forName(“类名”)等方法获取Class对象。 通过Class的源码我们可以得出以下结论
Class类也是类的一种与class关键字是不一样的。ClassT类实现了多个接口包括Serializable、GenericDeclaration、Type和AnnotatedElement并且不能被继承使用了final关键字。定义了三个私有静态常量ANNOTATION、ENUM和SYNTHETIC分别用于标识注解、枚举和合成类。Class类有一个私有构造函数只有JVM能够调用用于初始化Class对象防止默认构造函数的生成。私有构造函数通过参数ClassLoader初始化classLoader字段确保其非空以避免JIT优化问题。
认识类加载
类加载机制
Java类的加载过程包括加载、验证、准备、解析、初始化、使用和卸载七个步骤。加载阶段将类字节码加载到内存中验证阶段确保类的字节码符合规范准备阶段为静态变量分配内存解析阶段将符号引用转换为直接引用初始化阶段执行类的初始化代码使用阶段允许类实例化和使用最后在卸载阶段回收类的内存。 类的加载流程
首先就是编译过程。Java源代码文件如Person.java和Car.java被编译成字节码文件如Person.class和Car.class。
然后就是进入了类加载类加载器Class Loader 负责加载字节码文件。字节码文件可以来自本地文件系统、网络等不同来源。
在方法区中类加载器将加载的类字节码文件存储到方法区中并为每个类创建一个对应的数据结构如Person类数据结构和Car类数据结构。
在堆区中JVM在堆区中为每个类创建一个唯一的Class对象如Person Class对象和Car Class对象用于表示该类的元数据。
最后当程序创建类的实例时如Person类的实例P1、P2等和Car类的实例C1、C2等这些实例对象在堆区中被分配内存。每个实例对象通过其Class对象获取类的元数据从而实现反射机制即可以获取类的内部信息如方法、属性、构造函数等以及反向控制实例对象的能力。
通过下面这张图你能更清晰的了解到类加载机制。 学会使用反射
获取Class对象的几种方式
要通过反射获取具体的信息那么首先需要获取到Class对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。
1. 通过类名静态方式
这是获取 Class 对象最常用和最简单的一种方式适用于在编译时已知类的情况下。
public class MyClass {// 类的定义
}public class Main {public static void main(String[] args) {Class? clazz MyClass.class;System.out.println(Class name: clazz.getName());}
}使用 MyClass.class 可以直接获取 MyClass 的 Class 对象。这种方式在编译时检查类的存在和合法性如果类不存在或拼写错误编译时就会报错。
2. 通过对象的 getClass() 方法动态方式
当你已经有一个类的实例对象时可以通过该对象的 getClass() 方法获取其 Class 对象。
public class MyClass {// 类的定义
}public class Main {public static void main(String[] args) {MyClass obj new MyClass();Class? clazz obj.getClass();System.out.println(Class name: clazz.getName());}
}使用实例对象的 getClass() 方法可以获取该对象的 Class 对象。这种方式适用于在运行时已经有类实例的情况下。
3. 通过 Class.forName() 方法动态加载
这种方式适用于需要在运行时根据类名字符串动态加载类的场景。
public class Main {public static void main(String[] args) {try {Class? clazz Class.forName(com.example.MyClass);System.out.println(Class name: clazz.getName());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}使用 Class.forName(com.example.MyClass) 可以动态加载 MyClass 类。这种方式适用于在运行时需要根据类名字符串加载类的场景例如配置文件中指定类名。
4. 通过类加载器ClassLoader
这种方式用于需要自定义类加载器的场景例如在开发插件系统或模块化系统时。
public class Main {public static void main(String[] args) {try {ClassLoader classLoader Main.class.getClassLoader();Class? clazz classLoader.loadClass(com.example.MyClass);System.out.println(Class name: clazz.getName());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}说明
使用类加载器的 loadClass() 方法可以加载指定类。这种方式适用于需要自定义类加载逻辑的场景。
API基本使用 import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;public class ReflectionExample {public static void main(String[] args) {try {// 获取类的 Class 对象Class? clazz Class.forName(com.example.MyClass);// 获取类名System.out.println(Class Name: clazz.getName());// 获取修饰符int modifiers clazz.getModifiers();System.out.println(Is Public: Modifier.isPublic(modifiers));System.out.println(Is Abstract: Modifier.isAbstract(modifiers));// 获取父类Class? superClass clazz.getSuperclass();System.out.println(Superclass: superClass.getName());// 获取实现的接口Class?[] interfaces clazz.getInterfaces();System.out.println(Interfaces:);for (Class? iface : interfaces) {System.out.println(iface.getName());}// 获取字段Field[] fields clazz.getDeclaredFields();System.out.println(Fields:);for (Field field : fields) {System.out.println(field.getName() - field.getType().getName());}// 获取方法Method[] methods clazz.getDeclaredMethods();System.out.println(Methods:);for (Method method : methods) {System.out.println(method.getName() - method.getReturnType().getName());}// 获取构造函数Constructor?[] constructors clazz.getDeclaredConstructors();System.out.println(Constructors:);for (Constructor? constructor : constructors) {System.out.println(constructor.getName());}// 创建实例Constructor? constructor clazz.getConstructor();Object instance constructor.newInstance();System.out.println(Instance: instance);// 调用方法Method method clazz.getMethod(methodName, String.class);method.invoke(instance, Hello);// 访问和修改字段Field field clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(instance, newValue);System.out.println(Field Value: field.get(instance));} catch (Exception e) {e.printStackTrace();}}
}反射的基本原理
首先我们编写的 Java 源文件.java被保存在磁盘上。然后这些源文件通过 Java 编译器javac编译生成字节码文件.class。在编译过程中编译器会检查源文件中的语法错误如果存在语法错误将无法生成字节码文件。接着JVM 将这些字节码文件加载到内存中。
Java 是面向对象的编程语言所有事物都可以被看作对象。当字节码文件被加载到内存中时JVM 会将其表示为一个 Class 对象。这个 Class 对象包含了与类有关的所有信息。
一旦获得了 Class 对象就可以通过反射机制操作类中的所有内容。Java 将类的成员变量、构造器、成员方法都看作对象并将其封装到相应的类中。通过 Class 对象可以调用相应的方法获取类的成员变量对象、构造器对象、成员方法对象。然后通过这些对象调用方法就相当于该类在操作自身的属性、构造器和成员方法。这就是反射的基本原理。
每个类在编译后都会生成一个同名的 .class 文件该文件保存着 Class 对象的内容。类加载实际上是 Class 对象的加载类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName(com.mysql.jdbc.Driver) 这种方式来控制类的加载该方法会返回一个 Class 对象。反射提供了运行时获取类信息的能力允许在运行时加载类即使在编译时期该类的 .class 文件不存在也可以在运行时加载并使用反射机制操作。