Java反射机制详解
Java 反射机制详解
本章位置:第二阶段 Java 核心框架
前置知识:面向对象、继承、多态、JavaBean、异常机制
本章内容:Java Reflection 反射机制
下一篇:Java 内省机制详解
学习目标:理解 Class 对象、类元信息、反射创建对象、访问字段、调用方法、构造方法、注解读取、泛型信息,以及反射在 MyBatis、Spring、SpringBoot 等框架中的作用。
一、为什么要学习反射
普通 Java 代码中,我们通常:
Student student =
new Student();
然后:
student.setName(
"张三"
);
调用方法:
student.study();
这些代码有一个共同特点:
在编译代码时
我们已经知道类名、方法名、字段名
例如:
Student
setName
study
都已经写死在代码里。
二、框架为什么不能全写死
后面学习:
MyBatis
Spring
SpringBoot
Servlet
JUnit
时会发现框架面对的是:
各种各样的用户类
例如:
User
Student
Order
Product
Teacher
框架不可能提前写:
new User();
new Student();
new Product();
因为框架开发者:
根本不知道你以后会创建什么类
所以框架需要一种能力:
在程序运行时动态获取类的信息,并根据这些信息创建对象、访问字段、调用方法。
这就是:
反射
Reflection
三、什么是反射
Java 反射可以理解为:
在程序运行期间,动态获取一个类的结构信息,并动态操作这个类或对象。
反射可以获取:
类名
包名
父类
接口
字段
方法
构造方法
注解
泛型信息
还可以:
创建对象
读取字段
修改字段
调用方法
四、普通调用和反射调用的区别
普通方式:
Student student =
new Student();
student.setName(
"张三"
);
编译时已经知道:
Student
setName
反射方式:
Class<?> clazz =
Class.forName(
"com.example.Student"
);
然后根据:
字符串
找到:
类
再动态调用。
五、反射的核心入口:Class
Java 反射最重要的类:
java.lang.Class
注意:
Class
不是普通意义上的“类关键字”
它本身也是:
Java 类
六、什么是 Class 对象
每一个被 JVM 加载的类,
在 JVM 中都会有:
一个对应的 Class 对象
例如:
Student
被加载后,
JVM 中会存在:
Class<Student>
对象。
它保存了 Student 的:
结构信息
七、可以把 Class 理解成“类的说明书”
例如:
Student.java
定义:
字段
方法
构造方法
注解
Class 对象就像:
Student 类在运行时的说明书
反射就是:
通过这份说明书
动态操作类
八、获取 Class 对象的三种常见方式
最常见:
类名.class
对象.getClass()
Class.forName()
九、方式一:类名.class
Class<Student> clazz =
Student.class;
特点:
最简单
编译时已经知道类名
十、方式二:对象.getClass()
Student student =
new Student();
Class<?> clazz =
student.getClass();
getClass() 来自:
Object
所以任何普通对象都可以调用。
十一、方式三:Class.forName()
Class<?> clazz =
Class.forName(
"com.example.Student"
);
这里传入:
类的全限定名
例如:
com.example.Student
十二、什么是全限定类名
例如:
package com.example.entity;
public class Student {
}
全限定类名:
com.example.entity.Student
也叫:
Fully Qualified Class Name
十三、Class.forName() 为什么重要
因为类名可以来自:
配置文件
数据库
XML
注解
用户配置
例如配置:
class.name=com.example.Student
代码:
String className =
properties.getProperty(
"class.name"
);
Class<?> clazz =
Class.forName(
className
);
这就是:
运行时动态加载
十四、三种 Class 获取方式比较
| 方式 | 示例 | 特点 |
|---|---|---|
| 类名.class | Student.class |
编译时已知类 |
| 对象.getClass() | student.getClass() |
已有对象 |
| Class.forName() | Class.forName("...") |
动态加载最常见 |
十五、同一个类只有一个主要 Class 对象
例如:
Class<Student> c1 =
Student.class;
Student student =
new Student();
Class<?> c2 =
student.getClass();
Class<?> c3 =
Class.forName(
"com.example.Student"
);
通常:
System.out.println(
c1 == c2
);
System.out.println(
c2 == c3
);
结果:
true
true
因为:
同一个 ClassLoader 中
同一个类对应同一个 Class 对象
十六、Class 常用方法
常见:
getName()
getSimpleName()
getPackageName()
getSuperclass()
getInterfaces()
getModifiers()
isInterface()
isEnum()
isAnnotation()
十七、getName()
clazz.getName();
结果例如:
com.example.Student
十八、getSimpleName()
clazz.getSimpleName();
结果:
Student
十九、getPackageName()
clazz.getPackageName();
结果:
com.example
二十、getSuperclass()
Class<?> parent =
clazz.getSuperclass();
可以获取:
直接父类
二十一、getInterfaces()
Class<?>[] interfaces =
clazz.getInterfaces();
获取:
直接实现的接口
二十二、isInterface()
clazz.isInterface();
判断:
是不是接口
二十三、isEnum()
clazz.isEnum();
判断:
是不是枚举
二十四、isAnnotation()
clazz.isAnnotation();
判断:
是不是注解类型
二十五、反射中的构造方法
构造方法对应:
java.lang.reflect.Constructor
例如:
Constructor<?> constructor;
二十六、getConstructor()
获取:
public 构造方法
例如:
Constructor<Student> constructor =
Student.class
.getConstructor();
表示:
获取 public 无参构造
二十七、getDeclaredConstructor()
获取当前类中声明的构造方法:
Constructor<Student> constructor =
Student.class
.getDeclaredConstructor();
包括:
private
protected
默认
public
二十八、获取有参构造
假设:
public Student(
String name,
Integer age
) {
}
获取:
Constructor<Student> constructor =
Student.class
.getConstructor(
String.class,
Integer.class
);
二十九、通过反射创建对象
JDK 推荐使用:
getDeclaredConstructor()
.newInstance()
例如:
Student student =
Student.class
.getDeclaredConstructor()
.newInstance();
三十、为什么不推荐 Class.newInstance()
老代码可能:
clazz.newInstance();
这个 API 已经:
不推荐使用
现代代码更推荐:
clazz
.getDeclaredConstructor()
.newInstance();
异常信息和构造控制更清晰。
三十一、通过有参构造创建对象
Constructor<Student> constructor =
Student.class
.getConstructor(
String.class,
Integer.class
);
Student student =
constructor.newInstance(
"张三",
20
);
三十二、反射创建对象完整过程
获取 Class
↓
找到 Constructor
↓
newInstance()
↓
得到对象
三十三、反射中的字段 Field
字段对应:
java.lang.reflect.Field
例如:
Field field;
三十四、getField()
clazz.getField(
"name"
);
主要获取:
public 字段
包括:
继承得到的 public 字段
三十五、getDeclaredField()
clazz.getDeclaredField(
"name"
);
获取:
当前类自己声明的字段
不管:
private
protected
public
三十六、getFields()
Field[] fields =
clazz.getFields();
获取:
public 字段
三十七、getDeclaredFields()
Field[] fields =
clazz
.getDeclaredFields();
获取:
当前类声明的所有字段
实际框架中很常用。
三十八、字段基本信息
Field field =
clazz.getDeclaredField(
"name"
);
可以:
field.getName();
field.getType();
field.getModifiers();
三十九、读取 public 字段
假设:
public String name;
反射:
Field field =
clazz.getField(
"name"
);
Object value =
field.get(
student
);
四十、修改 public 字段
field.set(
student,
"李四"
);
四十一、private 字段怎么办
假设:
private String name;
可以:
Field field =
clazz.getDeclaredField(
"name"
);
然后:
field.setAccessible(
true
);
再:
field.set(
student,
"张三"
);
四十二、setAccessible(true)
表示:
尝试绕过 Java 语言层面的访问检查
例如:
private
成员也可以访问。
这就是很多人说:
反射可以“破坏封装”
的原因。
四十三、JDK 9+ 强封装注意
现代 JDK 模块系统中:
并不是所有 JDK 内部成员
都可以随意 setAccessible(true)
有时会出现:
InaccessibleObjectException
框架可能需要 JVM 参数:
--add-opens
开放模块。
四十四、为什么你以前运行 Tomcat 会看到 --add-opens
很多框架或容器需要:
反射访问 JDK 内部结构
所以启动参数可能出现:
--add-opens=java.base/java.lang=ALL-UNNAMED
这就和:
反射
模块强封装
有关。
四十五、反射中的方法 Method
方法对应:
java.lang.reflect.Method
四十六、getMethod()
获取:
public 方法
例如:
Method method =
clazz.getMethod(
"study"
);
四十七、getDeclaredMethod()
Method method =
clazz.getDeclaredMethod(
"study"
);
获取:
当前类声明的方法
可以是:
private
protected
public
四十八、获取有参数方法
假设:
public void setName(
String name
) {
}
获取:
Method method =
clazz.getMethod(
"setName",
String.class
);
四十九、调用方法 invoke()
method.invoke(
student,
"张三"
);
等价于:
student.setName(
"张三"
);
五十、调用无参方法
Method method =
clazz.getMethod(
"study"
);
method.invoke(
student
);
五十一、调用有返回值方法
假设:
public String getName() {
return name;
}
反射:
Method method =
clazz.getMethod(
"getName"
);
Object result =
method.invoke(
student
);
转换:
String name =
(String) result;
五十二、调用 private 方法
Method method =
clazz
.getDeclaredMethod(
"secret"
);
method.setAccessible(
true
);
method.invoke(
student
);
五十三、Method 常用信息
method.getName();
method.getReturnType();
method.getParameterTypes();
method.getModifiers();
五十四、getMethods()
Method[] methods =
clazz.getMethods();
会获取:
public 方法
包括继承的。
五十五、getDeclaredMethods()
Method[] methods =
clazz
.getDeclaredMethods();
获取:
当前类自己声明的所有方法
五十六、getXXX 和 getDeclaredXXX 的区别
这是必须掌握的规律。
getField
getMethod
getConstructor
主要看:
public
并且部分会考虑继承。
而:
getDeclaredField
getDeclaredMethod
getDeclaredConstructor
主要看:
当前类自己声明
不管访问修饰符
五十七、反射获取所有类结构示例
Class<Student> clazz =
Student.class;
System.out.println(
"类名:"
+ clazz
.getName()
);
for (
Field field :
clazz.getDeclaredFields()
) {
System.out.println(
"字段:"
+ field
.getName()
);
}
for (
Method method :
clazz.getDeclaredMethods()
) {
System.out.println(
"方法:"
+ method
.getName()
);
}
五十八、反射异常
常见:
ClassNotFoundException
NoSuchFieldException
NoSuchMethodException
InstantiationException
IllegalAccessException
InvocationTargetException
五十九、ClassNotFoundException
Class.forName(
"com.example.NotExist"
);
如果类不存在:
ClassNotFoundException
六十、NoSuchMethodException
clazz.getMethod(
"notExist"
);
找不到:
NoSuchMethodException
六十一、NoSuchFieldException
clazz.getDeclaredField(
"notExist"
);
不存在:
NoSuchFieldException
六十二、InvocationTargetException
反射调用:
method.invoke(...)
如果:
真正被调用的方法内部抛异常
invoke 可能包装成:
InvocationTargetException
六十三、获取真正原始异常
catch (
InvocationTargetException e
) {
Throwable cause =
e.getCause();
}
排错时应该:
看 cause
六十四、反射调用的性能
反射通常比直接调用:
慢
因为需要:
动态查找
访问检查
参数包装
方法调度
但:
现代 JVM 已经对反射做了很多优化
实际框架是否使用反射:
更多看灵活性需求
而不是只看几纳秒性能
六十五、不要滥用反射
如果正常代码已经知道:
Student student =
new Student();
就不要为了“高级”改成:
Class.forName(...)
反射适合:
框架
工具
插件系统
动态配置
序列化
依赖注入
六十六、反射最大的优点
核心:
灵活
动态
解耦
框架不需要提前知道:
具体业务类
六十七、反射最大的缺点
代码复杂
可读性下降
编译期检查减少
运行时才发现错误
性能有额外开销
可能破坏封装
六十八、反射和配置文件结合
配置:
class.name=com.example.Student
代码:
String className =
ConfigUtil.get(
"class.name"
);
Class<?> clazz =
Class.forName(
className
);
Object object =
clazz
.getDeclaredConstructor()
.newInstance();
程序完全没有写:
new Student()
却创建了 Student 对象。
六十九、这就是 IOC 的基础思想之一
后面 Spring:
读取配置 / 扫描注解
↓
找到类
↓
反射创建对象
↓
保存到容器
这就是:
IOC 容器
底层思路的一部分。
七十、反射和 JavaBean
JavaBean:
private 属性
Getter / Setter
无参构造
反射可以:
发现 Setter
调用 Setter
自动赋值
例如:
name
可以推导:
setName
七十一、简单 Bean 属性注入
假设:
Student student =
new Student();
属性:
name
值:
张三
动态生成方法名:
String property =
"name";
String methodName =
"set"
+ Character
.toUpperCase(
property
.charAt(
0
)
)
+ property
.substring(
1
);
得到:
setName
七十二、反射调用 Setter
Method method =
Student.class
.getMethod(
"setName",
String.class
);
method.invoke(
student,
"张三"
);
这就是:
自动属性绑定
的最基础原理。
七十三、反射和 MyBatis
MyBatis 查询:
SELECT
id,
name,
age
FROM student;
然后返回:
Student
框架会做类似:
读取 ResultSet
↓
创建 Student
↓
找到属性
↓
反射赋值
真正实现更复杂,
但基本思想就是:
反射 + 映射
七十四、反射和 Spring
Spring 常见:
@Service
@Controller
@Component
框架启动时:
扫描类
↓
读取注解
↓
获取 Class
↓
反射创建对象
↓
保存 Bean
七十五、反射和 Servlet
Tomcat 加载 Servlet:
读取 Servlet 类
↓
通过反射创建 Servlet 对象
↓
调用生命周期方法
所以:
Web 容器
也离不开动态类加载和反射。
七十六、反射和 JUnit
JUnit 发现:
@Test
方法后,
会:
反射找到测试方法
↓
反射调用
所以单元测试框架同样大量使用反射。
七十七、反射和注解
后面学习注解时:
clazz.getAnnotation(...)
method.getAnnotation(...)
就是:
反射读取注解
所以:
注解如果没有反射或框架去读取,本身通常不会自动产生业务效果。
七十八、反射中的 Modifier
字段、方法、类:
public
private
static
final
abstract
修饰符可以通过:
Modifier
判断。
例如:
int modifiers =
field
.getModifiers();
boolean isPrivate =
Modifier
.isPrivate(
modifiers
);
七十九、判断 static
Modifier.isStatic(
field.getModifiers()
);
八十、判断 final
Modifier.isFinal(
field.getModifiers()
);
八十一、获取泛型信息
普通:
field.getType();
只能获取:
List
如果字段:
private List<String> names;
想知道:
String
需要:
泛型反射
八十二、getGenericType()
Type type =
field.getGenericType();
如果是:
List<String>
可能得到:
ParameterizedType
八十三、ParameterizedType
if (
type
instanceof ParameterizedType
parameterizedType
) {
Type[] arguments =
parameterizedType
.getActualTypeArguments();
for (
Type argument :
arguments
) {
System.out.println(
argument
);
}
}
结果可能:
class java.lang.String
八十四、为什么框架关心泛型
例如:
List<Student>
框架需要知道:
集合元素到底是 Student 还是 User
JSON 框架、依赖注入框架都会使用:
泛型反射
八十五、数组的 Class
例如:
String[].class
也是:
Class 对象
判断:
clazz.isArray();
八十六、基本类型也有 Class
例如:
int.class
boolean.class
double.class
都对应:
Class
八十七、void 也有 Class
void.class
也存在 Class 对象。
八十八、ClassLoader
类加载器:
ClassLoader
负责:
加载 .class
到 JVM。
八十九、获取类加载器
ClassLoader loader =
Student.class
.getClassLoader();
九十、类加载器和 Class.forName()
Class.forName(...)
本质上与:
类加载机制
密切相关。
框架可以:
根据字符串类名
动态加载类
九十一、常见类加载器简单了解
常见概念:
Bootstrap ClassLoader
Platform ClassLoader
Application ClassLoader
应用自己的类通常由:
Application ClassLoader
加载。
当前阶段了解即可。
九十二、类加载过程简单了解
类从 .class 到可用,
大致经历:
加载
↓
连接
↓
初始化
连接又包括:
验证
准备
解析
后面 SpringBoot 底层原理时再深入。
九十三、Class.forName() 会初始化类吗
常见:
Class.forName(
"com.example.Demo"
);
通常:
会触发类初始化
所以静态代码块可能执行。
九十四、不初始化加载简单了解
可以使用:
Class.forName(
className,
false,
classLoader
);
第二个参数:
false
表示:
只加载
暂不初始化
九十五、反射访问父类字段
getDeclaredFields():
只拿当前类
如果需要:
包括父类 private 字段
需要自己沿继承链向上找。
九十六、遍历继承链
例如:
Class<?> current =
clazz;
while (
current != null
&& current
!= Object.class
) {
Field[] fields =
current
.getDeclaredFields();
for (
Field field :
fields
) {
System.out.println(
field.getName()
);
}
current =
current
.getSuperclass();
}
很多框架工具都会做类似处理。
九十七、简单通用对象打印器
可以用反射写:
public static void printObject(
Object object
)
throws IllegalAccessException {
if (
object == null
) {
System.out.println(
"null"
);
return;
}
Class<?> clazz =
object
.getClass();
for (
Field field :
clazz
.getDeclaredFields()
) {
field.setAccessible(
true
);
Object value =
field.get(
object
);
System.out.println(
field.getName()
+ " = "
+ value
);
}
}
九十八、为什么这个工具不需要知道 Student
它接收:
Object
所以:
Student
User
Order
Product
都可以传。
这就是反射:
通用性
的体现。
九十九、综合案例:通用对象赋值
例如:
public static void setField(
Object object,
String fieldName,
Object value
)
throws Exception {
Class<?> clazz =
object
.getClass();
Field field =
clazz
.getDeclaredField(
fieldName
);
field.setAccessible(
true
);
field.set(
object,
value
);
}
使用:
Student student =
new Student();
setField(
student,
"name",
"张三"
);
一百、反射与类型安全
上面:
setField(
student,
"age",
"abc"
);
如果 age 是:
Integer
会运行时报错。
这说明反射:
绕过了很多编译期检查
错误会延迟到:
运行时
一百零一、字符串方法名的问题
例如:
clazz.getMethod(
"setNmae"
);
拼错:
编译器无法发现
程序运行时才:
NoSuchMethodException
这也是反射的缺点。
一百零二、反射安全建议
不要:
把任意用户输入
直接当类名 / 方法名
反射执行
否则可能造成:
严重安全问题
应该:
白名单
固定包范围
权限校验
一百零三、反射和安全边界
例如危险思路:
用户传 methodName
↓
服务器直接 getMethod(methodName)
↓
invoke
用户可能调用:
本不应该开放的方法
所以:
反射不能替代权限设计
一百零四、反射操作 static 方法
假设:
public static void hello() {
}
获取:
Method method =
clazz.getMethod(
"hello"
);
调用:
method.invoke(
null
);
因为 static 方法:
不依赖具体对象
一百零五、反射操作 static 字段
例如:
public static String
APP_NAME =
"Demo";
读取:
Field field =
clazz.getField(
"APP_NAME"
);
Object value =
field.get(
null
);
一百零六、反射操作枚举
Class<OrderStatus> clazz =
OrderStatus.class;
判断:
clazz.isEnum();
获取常量:
OrderStatus[] values =
clazz.getEnumConstants();
一百零七、getEnumConstants()
for (
OrderStatus status :
clazz.getEnumConstants()
) {
System.out.println(
status
);
}
框架构建:
下拉选项
枚举映射
时可能使用。
一百零八、反射读取注解预览
假设:
@MyComponent
public class UserService {
}
反射:
boolean present =
UserService.class
.isAnnotationPresent(
MyComponent.class
);
真正注解机制下一篇之后详细讲。
一百零九、动态工厂案例
接口:
public interface Animal {
void sound();
}
实现:
public class Dog
implements Animal {
@Override
public void sound() {
System.out.println(
"汪汪"
);
}
}
一百一十、传统工厂
public static Animal create(
String type
) {
if (
"dog".equals(type)
) {
return new Dog();
}
return null;
}
问题:
每增加一个类
都要改工厂
一百一十一、反射动态工厂
public static Animal create(
String className
)
throws Exception {
Class<?> clazz =
Class.forName(
className
);
Object object =
clazz
.getDeclaredConstructor()
.newInstance();
return (Animal) object;
}
使用:
Animal animal =
create(
"com.example.Dog"
);
animal.sound();
一百一十二、反射动态工厂的价值
工厂不再写死:
Dog
Cat
Bird
只要:
配置类名
就可以动态创建。
这就是:
解耦
可扩展
一百一十三、简易 IOC 容器思路
可以设计配置:
userService=com.example.UserService
orderService=com.example.OrderService
程序:
读取配置
↓
遍历 className
↓
Class.forName
↓
newInstance
↓
Map 保存对象
这就是:
最简 IOC 容器
一百一十四、简单 BeanFactory
public class SimpleBeanFactory {
private final Map<
String,
Object
> beans =
new HashMap<>();
public void register(
String name,
String className
)
throws Exception {
Class<?> clazz =
Class.forName(
className
);
Object object =
clazz
.getDeclaredConstructor()
.newInstance();
beans.put(
name,
object
);
}
public Object getBean(
String name
) {
return beans.get(
name
);
}
}
一百一十五、使用 SimpleBeanFactory
SimpleBeanFactory factory =
new SimpleBeanFactory();
factory.register(
"userService",
"com.example.UserService"
);
Object bean =
factory.getBean(
"userService"
);
这已经有:
Spring IOC
的一点影子。
一百一十六、反射和设计模式
反射经常结合:
工厂模式
代理模式
IOC
依赖注入
插件模式
使用。
后面学习 Spring AOP 时:
动态代理
也会继续接触反射。
一百一十七、Proxy 简单预览
Java 提供:
java.lang.reflect.Proxy
可以动态生成:
接口代理对象
后面 AOP 时会重点学习。
现在只需要知道:
反射包不只有 Class / Field / Method
还有动态代理
一百一十八、反射包
主要:
java.lang.Class
以及:
java.lang.reflect
包。
常见:
Constructor
Field
Method
Modifier
ParameterizedType
Proxy
一百一十九、IDEA 查看类结构
在 IDEA 中:
Alt + 7
可以查看:
Structure
看到:
字段
方法
构造
这和反射看到的结构概念很类似。
一百二十、常见错误:Class.forName 类名写错
错误:
Class.forName(
"Student"
);
如果类不在默认包:
会找不到
应该:
完整包名 + 类名
例如:
Class.forName(
"com.example.entity.Student"
);
一百二十一、常见错误:getField 获取 private
clazz.getField(
"name"
);
如果 name 是:
private
会找不到。
应该:
getDeclaredField(
"name"
);
一百二十二、常见错误:忘记 setAccessible
private 成员:
field.get(object)
可能:
IllegalAccessException
需要:
field.setAccessible(
true
);
但现代 JDK 模块限制仍需注意。
一百二十三、常见错误:参数类型不匹配
方法:
setAge(
Integer age
)
获取时:
getMethod(
"setAge",
int.class
)
可能找不到,
因为:
Integer.class
和
int.class
不是同一个 Class
一百二十四、int.class 和 Integer.class
System.out.println(
int.class
== Integer.class
);
结果:
false
基本类型和包装类型:
不同
反射查找方法时必须:
参数类型完全匹配
一百二十五、常见错误:invoke 参数数量错误
方法:
setName(
String name
)
调用:
method.invoke(
student
);
没有传参数:
会报错
必须:
method.invoke(
student,
"张三"
);
一百二十六、常见错误:把反射当正常业务首选
普通代码:
student.getName();
不要写成:
clazz
.getMethod("getName")
.invoke(student);
除非真的需要:
动态性
一百二十七、常见错误:吞掉反射异常
不要:
catch (
Exception e
) {
}
反射错误通常很难排查。
至少:
保留异常堆栈
保留 cause
输出具体类名 / 方法名
一百二十八、练习题 1:获取 Class
分别使用:
类名.class
对象.getClass()
Class.forName()
获取 Student 的 Class。
判断:
是不是同一个对象
一百二十九、练习题 2:查看类信息
输出:
全限定类名
简单类名
包名
父类
接口
一百三十、练习题 3:构造方法
Student:
无参构造
name + age 有参构造
使用反射分别创建对象。
一百三十一、练习题 4:字段
Student:
private String name;
通过反射:
读取
修改
name。
一百三十二、练习题 5:方法
通过反射调用:
setName(
String name
)
然后再反射调用:
getName()
一百三十三、练习题 6:打印所有字段
编写:
printFields(
Object object
)
输出所有字段名和字段值。
一百三十四、练习题 7:动态工厂
配置:
animal.class=com.example.Dog
读取后:
Class.forName
创建 Animal 对象
一百三十五、练习题 8:通用对象赋值
编写:
setField(
Object object,
String fieldName,
Object value
)
实现:
动态给对象字段赋值
一百三十六、练习题 9:简易 IOC
配置:
userService=com.example.UserService
orderService=com.example.OrderService
使用:
Map<String,Object>
保存反射创建的对象。
一百三十七、练习题 10:泛型反射
定义:
private List<String> names;
获取:
字段原始类型
实际泛型参数
一百三十八、反射必记 API
Class
Class.forName()
getName()
getSimpleName()
getSuperclass()
getInterfaces()
Constructor
getConstructor()
getDeclaredConstructor()
newInstance()
Field
getField()
getDeclaredField()
getFields()
getDeclaredFields()
get()
set()
setAccessible()
Method
getMethod()
getDeclaredMethod()
getMethods()
getDeclaredMethods()
invoke()
一百三十九、必须回答的问题
学完后应该能够回答:
1. 什么是反射?
2. Class 对象是什么?
3. 获取 Class 的三种方式是什么?
4. Class.forName 有什么作用?
5. getField 和 getDeclaredField 区别?
6. getMethod 和 getDeclaredMethod 区别?
7. 如何通过反射创建对象?
8. 如何反射调用 private 方法?
9. invoke 是什么?
10. InvocationTargetException 是什么?
11. setAccessible 有什么作用?
12. 反射为什么会破坏封装?
13. MyBatis 为什么需要反射?
14. Spring 为什么需要反射?
15. 反射有什么优缺点?
一百四十、反射知识结构
Reflection
│
├─ Class
│ ├─ 类信息
│ ├─ 父类
│ ├─ 接口
│ └─ 类加载
│
├─ Constructor
│ └─ 创建对象
│
├─ Field
│ ├─ 获取字段
│ ├─ 读取值
│ └─ 修改值
│
├─ Method
│ ├─ 获取方法
│ └─ invoke
│
├─ Annotation
│ └─ 读取注解
│
├─ Generic
│ └─ 泛型信息
│
└─ Framework
├─ MyBatis
├─ Spring
├─ JUnit
└─ Servlet
一百四十一、从反射到内省
反射可以直接:
字段
方法
构造方法
但是如果我们只想处理:
JavaBean 属性
例如:
name
age
score
每次都手工:
找 getName
找 setName
拼字符串
会比较麻烦。
Java 还提供:
内省
Introspection
专门从 JavaBean 角度分析:
属性
Getter
Setter
所以:
反射
范围更底层、更通用
内省
更专注 JavaBean 属性
下一篇就继续整理:
Java 内省机制详解
一百四十二、本章总结
反射最核心的一句话:
在运行时动态获取类的信息并操作对象。
普通代码:
编译时知道类
反射:
运行时才知道类
最核心流程:
Class
↓
Constructor
↓
Field
↓
Method
↓
动态操作
后面很多框架:
MyBatis
Spring
SpringBoot
JUnit
Servlet
之所以能够做到:
自动创建对象
自动属性赋值
自动扫描类
自动调用方法
自动读取注解
底层都离不开:
反射
所以反射并不是为了让普通业务代码“写得高级”,
而是为了:
让框架拥有动态处理未知类的能力
掌握这一点,后面学习 Spring 和 MyBatis 会容易很多。