🦊 Java
1、Java基础
1.1 StringBuffer和StringBuilder的区别
1.2 ConcurrentHashMap和TreeMap实现原理
1.3 ArrayList和LinkedList实现原理
1.4 HashSet和TreeSet实现原理
1.5 深拷贝与浅拷贝
1.6 抽象类与接口
2、Java多线程
2.1 并发编程的三大特性
2.2 指令重排
2.3 Volatile原理
2.4 CAS原理
2.5 Java的4种引用级别
2.6 Java中的锁
2.7 Synchronized实现原理
2.8 线程池实现原理
2.9 AQS
2.10 创建线程的方式
2.11 ThreadLocal原理
3、JVM
3.1 判断对象是否存活的方法
3.2 JVM内存结构
3.3 常见的垃圾收集算法有哪些
3.4 指针碰撞和空闲列表
3.5 常见的垃圾收集器有哪些
3.6 内存溢出与内存泄漏的区别
3.7 常用的JVM启动参数有哪些
3.8 反射机制
4、NIO
4.1 概述
5、Spring
5.1 Spring IOC
5.2 Spring AOP
6、SpringBoot
6.1 SpringBoot、SpringCloud的联系与区别
-
+
tourist
register
Sign in
反射机制
## 1 含义 1. 一般情况下,我们**使用某个类时必定知道他是什么类**,**是用来做什么的**,于是我们**直接对这个类进行实例化**,之后**使用这个类对象进行操作**,**这种进行类对象的初始化的方法**,我们**可以理解为正射**,例如: ```java Apple apple = new Apple(); apple.setPrice(4.0); ``` 2. 而**反射只有在运行时才知道要操作的类是什么**,并且**可以在运行时获取类的完整构造**,并**调用对应的方法**,例如: ```java Class<?> cls = Class.forName("top.grayson.jvm.reflect.Apple"); Method setPriceMethod = cls.getMethod("setPrice", double.class); Constructor<?> constructor = cls.getConstructor(); Object instance = constructor.newInstance(); setPriceMethod.invoke(instance, 5.0); ``` 3. 上面两段代码的**执行结果是完全一样的**,但是其**思路完全不一样**: 1. 第一段代码在**未运行时就已经确定了要运行的类**(`Apple`)。 2. 第二段代码在**运行时通过字符串值才得知要运行的类**(`top.grayson.jvm.reflect.Apple`)。 4. 完整的代码及运行结果如下: ```java public class ReflectTest1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // 直接初始化,正射 Apple apple = new Apple(); apple.setPrice(4.0); System.out.println(apple.getPrice()); // 反射 Class<?> cls = Class.forName("top.grayson.jvm.reflect.Apple"); Method setPriceMethod = cls.getMethod("setPrice", double.class); Constructor<?> constructor = cls.getConstructor(); Object instance = constructor.newInstance(); setPriceMethod.invoke(instance, 5.0); Method getPriceMethod = cls.getMethod("getPrice"); Object price = getPriceMethod.invoke(instance); System.out.println(price); } } ``` ```txt 4.0 5.0 ``` 5. 从上面的例子中我们可以看出,一般情况下我们**使用反射获取一个对象的步骤为**: 1. **获取类的 `Class` 对象实例**: ```java Class<?> cls = Class.forName("top.grayson.jvm.reflect.Apple"); ``` 2. **根据 `Class` 对象实例获取 `Constructor` 对象**: ```java Constructor<?> constructor = cls.getConstructor(); ``` 3. **根据 `Constructor` 对象的 `newInstance()` 方法获取反射类对象**: ```java Object instance = constructor.newInstance(); ``` 6. 而如果要**调用某一个方法**,则需要经过下面的步骤: 1. **获取方法的 `Method` 对象**: ```java Method setPriceMethod = cls.getMethod("setPrice", double.class); ``` 2. **利用 `invoke()` 方法调用方法**: ```java setPriceMethod.invoke(instance, 5.0); ``` ## 2 常用 API ### 2.1 获取反射中的 Class 对象 1. 在反射中,**要获取一个类或调用一个类的方法**,我们首先**需要获取到该类的 `Class` 对象**。 2. 在 Java API 中,获取 `Class` 类对象有三种方法: 1. **使用 `Class.forName()` 静态方法**,当我们知道该类的全路径名时,可以使用该方法获取 `Class` 对象: ```java Class cls = Class.forName("java.lang.String"); ``` 2. **使用 `.class` 方法**,这种方法只适合在编译前就知道操作的 `Class`: ```java Class cls = String.class; ``` 3. **使用类对象的 `getClass()` 方法**: ```java String str = new String("Hello"); Class cls = str.getClass(); ``` ### 2.2 通过反射创建类对象 1. 通过反射创建类对象主要有两种方式: 1. **通过 `Class` 对象的 `newInstance()` 方法**,这种方法**只能使用默认的无参构造方法**: ```java Class cls = Apple.class; Apple apple = (Apple)cls.newInstance(); ``` 2. **通过 `Constructor` 对象的 `newInstance()` 方法**,这种方法相比通过 `Class` 对象的 `newInstance` 方法,**可以选择特定构造方法**: ```java // 无参构造方法 Class cls = Apple.class; Constructor constructor = cls.getConstructor(); Apple apple = (Apple)constructor.newInstance(); // 有参构造方法 Class cls = Apple.class; Constructor constructor = cls.getConstructor(String.class, int.class); Apple apple = (Apple)constructor.newInstance("红富士", 15); ``` ### 2.3 通过反射获取类属性、方法、构造器 1. 我们**通过 `Class` 对象的 `getFields()` 方法可以获取 `Class` 类的属性**,但**无法获取私有属性**: ```java Class<?> cls = Class.forName("top.grayson.jvm.reflect.Apple"); for (Field field: cls.getFields()) { System.out.println(field.getName()); } ``` ```txt price ``` 如果想**获取包括私有属性在内的所有属性**,**可以使用 `Class` 对象的 `getDeclaredFields()` 方法**: ```java Class<?> cls = Class.forName("top.grayson.jvm.reflect.Apple"); for (Field field: cls.getDeclaredFields()) { System.out.println(field.getName()); } ``` ```txt name price ``` 2. 与获取类属性一样,当我们去获取类方法、类构造器时,**如果要获取私有方法或私有构造器**,则**必须使用有 `declared` 关键字的方法**。 ## 3 源码解析 1. 进入 `Method` 的 `invoke()` 方法我们可以看到,一开始是**进行了一些权限的检查**,最后是**调用了 `MethodAccessor` 类的 `invoke()` 方法进行进一步处理,如下图所示:** ![](/media/202108/2021-08-05_162322.png) 2. **`MethodAccessor` 是一个接口**,**定义了方法调用的具体操作**: ![](/media/202108/2021-08-05_162616.png) `MethodAccrssor` 有三个具体的实现类,分别为: ![](/media/202108/2021-08-05_162845.png) 3. 要看 `ma.invoke()` 到底调用的是哪个类的 `invoke()` 方法,则需要看看 `MethodAccessor` 对象返回的到底是哪个类对象,所以我们需要进入 `acquireMethodAccessor()` 方法中看一下: ![](/media/202108/2021-08-05_163222.png) 4. 从 `acquireMethodAccessor()` 方法中我们可以看到,代码**先判断是否存在对应的 `MethodAccessor` 对象**,**如果存在那么就复用之前的 `MethodAccessor` 对象**,**否则调用 `ReflectionFactory` 对象的 `newMethodAccessor()` 方法生成一个 `MethodAccessor` 对象**: ![](/media/202108/2021-08-05_163742.png) 5. 在 `ReflectionFactory` 类的 `newMethodAccessor()` 方法里,我们可以看到**首先生成了一个 `NativeMethodAccessorImpl` 对象**,**然后将这个对象作为参数调用了 `DelegatingMethodAccessorImpl` 类的构造方法**,这里的实现是**使用了[代理模式](https://notebook.ricear.com/project-42/doc-770)**,**将 `NativeMethodAccessorImpl` 对象交给 `DelegatingMethodAccessorImpl` 对象代理**,我们查看 `DelegatingMethodAccessorImpl` 类的构造方法可以知道,其实是**将 `NativeMethodAccessorImpl` 对象赋值给 `DelegatingMethodAccessorImpl` 类的 `delegate` 属性**: ![](/media/202108/2021-08-05_164325.png) 6. 所以说 `ReflectionFactory`**类的 `newMethodAccessor()` 方法最终返回 `DelegatingMethodAccessorImpl` 对象**,所以我们前面的 里,其将**会进入 `DelegatingMethodAccessorImpl` 类的 `invoke()` 方法中**,**进入 `DelegatingMethodAccessorImpl` 类的 `invoke()` 方法后**,这里**调用了 `delegate` 属性的 `invoke()` 方法**: ![](/media/202108/2021-08-05_165239.png) 7. `delegate` 的类型为 `MethodAccessorImpl`,该类**是一个抽象类**,有两个子类,分别是 `DelegatingMethodAccessorImpl` 和 `NativeMethodAccessorImpl`: ![](/media/202108/2021-08-05_165933.png) 按照我们前面说的,**这里的 `delegate` 其实是一个 `NativeMethodAccessorImpl` 对象**,所以这里**会进入 `NativeMethodAccessorImpl` 的 `invoke()` 方法**。 8. 在 `NativeMethodAccessorImpl` 的 `invoke()` 方法中,**会判断调用次数是否超过阈值**(默认为 15 次),**如果超过该阈值**,那么就**会生成另一个 `MethodAccessor` 对象**,并**将原来 `DelegatingMethodAccessorImpl` 对象中的 `delegate` 属性指向最新的 `MethodAccessor` 对象**: ![](/media/202108/2021-08-05_170845.png) 9. 到这里,其实我们可以知道 `MethodAccessor`**对象其实就是具体去生成反射类的入口**,通过查看源码上的注释,我们可以了解到 `MethodAccessor` 对象的一些设计信息: 1. **初次加载字节码实现反射**,**使用 `Method.invoke()` 和 `Constructor.newInstance()` 加载花费的时间是使用原生代码加载花费时间的 3-4 倍**,这**使得那些频繁使用反射的应用需要花费更长的启动时间**。 2. 为了避免这中痛苦的时间,我们在**第一次加载的时候重用了 JVM 的入口**,**之后切换到字节码的实现**,因此**实际的 `MethodAccessor` 实现有两个版本**,**一个是 `Native` 版本**,**一个是 `Java` 版本**: 1. **`Native` 版本一开始启动快**,**但是随着运行时间变长**,**速度变慢**;**`Java` 版本一开始加载慢**,**但是随着运行时间变长**,**速度变快**。 2. 正是因为二者存在这样的差异,所以**第一次加载的时候我们会发现使用的是 `NativeMethodAccessorImpl`**,**当反射调用次数超过 15 次之后**,则**使用 `MethodAccessorGenerator` 生成的 `MethodAccessorImpl` 对象去实现反射**。 10. `Method` 类的 `invoke()` 方法的整个流程的时序图如下所示: ![](/media/202108/2021-08-05_1725350.6596558044182225.png) ## 4 应用场景 反射常见的应用场景有以下几个: 1. **Spring 实例化对象**: 1. 当**程序启动时**,**Spring 会读取配置文件 `application.xml` 并解析出里面的所有标签并实例化到 `IOC` 容器中**。 2. 具体示例如下: 1. 假如有如下的上下文配置文件: ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="smallpineapple" class="top.grayson.jvm.domain.SmallPineapple"> <constructor-arg type="java.lang.String" value="小菠萝"/> <constructor-arg type="int" value="21"/> </bean> </beans> ``` 2. 在定义好上面的文件后,通过 `ClassPathXmlApplicationContext` 加载该配置文件,程序启动时,Spring 会将该配置文件中的所有 `bean` 都实例化,放入 IOC 容器中,IOC 容器本身就是一个工厂,通过该工厂传入 `<bean>` 标签的 `id` 属性获取到对应的实例: ```java public class SpringIOCTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); SmallPineapple smallPineapple = (SmallPineapple)context.getBean("smallpineapple"); smallPineapple.getInfo(); // 小菠萝的年龄是 21 } } ``` 3. Spring 在实例化对象的过程经简化之后,可以理解为反射实例化对象的步骤: 1. **获取 `Class` 对象的构造器**。 2. **通过构造器调用 `newInstance()` 实例化对象**。 2. **用反射实现[动态代理](https://notebook.ricear.com/project-42/doc-770/#3-2-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86)**。 3. **JDBC 连接数据库**: 1. 使用 JDBC 连接数据库时,**指定连接数据库的驱动类时用到反射加载驱动类**。 2. 具体的示例如下: 1. 在**导入第三方库**时,**JVM 不会主动去加载外部导入的类**,**而是等到真正用时**,**才去加载需要的类**,正是如此,**我们可以在获取数据库连接时传入驱动类的全限定名**,**交给 JVM 加载该类**: ```java public class DBConnectionTest { // 指定数据库的驱动类 private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.driver"; public static Connection getConnection() throws ClassNotFoundException, SQLException { Connection conn = null; // 加载驱动类 Class.forName(DRIVER_CLASS_NAME); // 获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root"); return conn; } } ``` 2. 在我们开发 SpringBoot 项目时,常见的 `application.yml` 中的数据库配置,也用到了反射的原理: ```yaml spring: dataSource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://··· ``` 与 1 中的写法相比,这样写的好处是**不需要修改源码**,**仅加载配置文件就可以完成驱动类的替换**。 ## 5 优缺点 ### 5.1 优点 1. **增加程序的灵活性**: 1. 面对需求变更时,可以**灵活地实例化不同的对象**。 2. 例如,在 SpringBoot 中利用反射连接数据库,涉及到数据库的数据源,直接在 `application.yml` 里面配置即可,当涉及到需要更改数据源时,直接更改配置文件即可,无需修改源码。 ### 5.2 缺点 1. **破坏类的封装性**: 1. **反射可以获取类中被 `private` 修饰的变量**、**方法和构造器**,这**违反了面向对象的封装特性**,因为**被 `private` 修饰意味着不想对外暴露**,**只允许本类访问**,**而 `setAccessable(true)` 可以无视访问修饰符的限制**,**外界可以强制访问**。 2. **性能损耗**: 1. 在**直接 `new` 对象并调用对象方法和访问属性**时,**编译器会在编译期提前检查可访问性**,**如果尝试进行不正确的访问**,**IDE 会提前提示错误**,例如**参数传递类型不匹配**、**非法访问 `private` 属性和方法**。 2. 而在**利用反射操作对象**时,**编译器无法提前得知对象的类型**,**访问是否合法**,**参数传递类型是否匹配**,**只有在程序运行时调用反射的代码时才会从头开始检查**、**调用**、**返回结果**,**JVM 也无法对反射的代码进行优化**。 > 虽然反射具有性能损耗的特点,但是我们不能一概而论,产生了使用反射就会性能下降的思想,**反射的慢**,**需要同时调用上 100W 次才可能体现出来**,在**几次**、**几十次的调用**,**并不能体现反射的性能低下**,所以不要一味地戴有色眼镜看反射,**在单次调用反射的过程中**,**性能损耗可以忽略不计**,**如果程序的性能要求很高**,**那么尽量不要使用反射**。 ## 参考文献 1. [大白话说 Java 反射:入门、使用、原理](https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html)。 2. [Java 反射是什么?看这篇绝对会了【macrozheng】!](https://mp.weixin.qq.com/s?src=11×tamp=1628149269&ver=3233&signature=q8iuSXPmXNfFoo4GvULCCyzJM8gJupniDik786W3HK9yA4J1x81iPkMwHUxenmddsMxZNFTDP36zNIwTW4T9v3QuDF-wg*-7ba0PuFQ01iX7tmtvcRpuRU6IcJZ0UOQO&new=1) 3. [Java 反射:这是一份全面 & 详细的 Java 反射机制 学习指南](https://www.jianshu.com/p/356e1d7a9d11)。
ricear
July 11, 2022, 10:05 p.m.
©
BY-NC-ND(4.0)
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
share
link
type
password
Update password