Java反射机制详解

O泡李华 6

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 会容易很多。