Java内省机制详解

O泡李华 6

Java 内省机制详解

本章位置:第二阶段 Java 核心框架
前置知识:JavaBean、反射机制
本章内容:Java Introspection 内省机制
下一篇:Java 注解详解
学习目标:理解内省机制、BeanInfo、PropertyDescriptor、Getter/Setter 识别、属性读写,并理解内省与反射、JavaBean、Spring/MyBatis 属性绑定的关系。


一、什么是内省

内省:

Introspection

可以理解为:

Java 程序在运行时,从 JavaBean 的角度分析一个类有哪些“属性”,以及这些属性对应哪些 Getter 和 Setter。

例如:

public class Student {

    private String name;

    private Integer age;

    public String getName() {

        return name;
    }

    public void setName(
            String name
    ) {

        this.name = name;
    }

    public Integer getAge() {

        return age;
    }

    public void setAge(
            Integer age
    ) {

        this.age = age;
    }
}

从 JavaBean 角度看:

name 属性
age 属性

二、内省和反射的关系

反射:

Reflection

可以获取:

字段
方法
构造方法
注解
接口
父类

它关注的是整个类结构。

内省:

Introspection

更关注:

JavaBean 属性
Getter
Setter

可以简单记:

反射
更底层、更通用

内省
更专门处理 JavaBean

三、为什么需要内省

假设框架收到:

name=张三
age=20

需要自动写入:

Student

如果只使用反射,需要自己:

把 name 拼成 setName
把 age 拼成 setAge
查找方法
判断参数类型
调用 Setter

内省机制已经帮我们:

分析 JavaBean 属性
找到对应 Getter
找到对应 Setter

四、内省核心类

主要位于:

java.beans

核心:

Introspector
BeanInfo
PropertyDescriptor
MethodDescriptor
BeanDescriptor

初学阶段最重要:

Introspector
BeanInfo
PropertyDescriptor

五、Introspector

Introspector 是内省入口。

最常用:

Introspector.getBeanInfo(...)

例如:

BeanInfo beanInfo =
        Introspector
                .getBeanInfo(
                        Student.class
                );

六、BeanInfo

BeanInfo 可以理解为:

JavaBean 的描述信息。

里面可以获取:

属性描述
方法描述
Bean 描述
事件描述

七、PropertyDescriptor

PropertyDescriptor:

属性描述器

一个 PropertyDescriptor 可以描述:

属性名
属性类型
Getter
Setter

八、获取所有属性

PropertyDescriptor[]
        descriptors =
        beanInfo
                .getPropertyDescriptors();

遍历:

for (
    PropertyDescriptor descriptor :
    descriptors
) {

    System.out.println(
            descriptor
                    .getName()
    );
}

Student 可能输出:

age
class
name

九、为什么会多出 class 属性

因为所有对象都继承:

Object

Object 有:

getClass()

按照 JavaBean Getter 规则:

getClass()

会被识别为:

class 属性

十、过滤 class 属性

方式一:

for (
    PropertyDescriptor descriptor :
    descriptors
) {

    if (
        "class".equals(
                descriptor
                        .getName()
        )
    ) {

        continue;
    }

    System.out.println(
            descriptor
                    .getName()
    );
}

方式二:

BeanInfo beanInfo =
        Introspector
                .getBeanInfo(
                        Student.class,
                        Object.class
                );

表示分析 Student,但不继续分析 Object。


十一、getName()

descriptor.getName();

得到:

name
age

十二、getPropertyType()

Class<?> type =
        descriptor
                .getPropertyType();

例如:

name → String
age → Integer

十三、getReadMethod()

Method readMethod =
        descriptor
                .getReadMethod();

获取:

Getter

例如 name 属性:

getName()

十四、getWriteMethod()

Method writeMethod =
        descriptor
                .getWriteMethod();

获取:

Setter

例如 name 属性:

setName(String)

十五、读取属性值

Student student =
        new Student();

student.setName(
        "张三"
);

BeanInfo beanInfo =
        Introspector
                .getBeanInfo(
                        Student.class,
                        Object.class
                );

for (
    PropertyDescriptor descriptor :
    beanInfo
            .getPropertyDescriptors()
) {

    if (
        "name".equals(
                descriptor
                        .getName()
        )
    ) {

        Method getter =
                descriptor
                        .getReadMethod();

        Object value =
                getter.invoke(
                        student
                );

        System.out.println(
                value
        );
    }
}

输出:

张三

十六、写入属性值

for (
    PropertyDescriptor descriptor :
    beanInfo
            .getPropertyDescriptors()
) {

    if (
        "name".equals(
                descriptor
                        .getName()
        )
    ) {

        Method setter =
                descriptor
                        .getWriteMethod();

        setter.invoke(
                student,
                "李四"
        );
    }
}

十七、内省最终仍会使用反射

你会发现:

getReadMethod()

和:

getWriteMethod()

返回的都是:

Method

最后还是:

invoke()

所以:

内省是在反射基础上,对 JavaBean 属性操作做的一层更友好的封装。


十八、JavaBean 属性识别规则

传统 JavaBean 主要依靠:

Getter / Setter 命名规范

例如:

getName()

对应:

name

十九、Setter 规则

setName(
        String name
)

对应:

name 属性

通常 Setter:

一个参数
返回 void

二十、boolean Getter

如果:

private boolean enabled;

Getter 常见:

public boolean isEnabled() {

    return enabled;
}

会被识别成:

enabled 属性

二十一、JavaBean 属性不一定真的存在字段

例如:

public class Person {

    public String getName() {

        return "张三";
    }

    public void setName(
            String name
    ) {

    }
}

即使没有:

private String name;

从 JavaBean 内省角度仍然存在:

name 属性

因为属性主要由:

Getter / Setter

定义。


二十二、JavaBean 属性和 Field 不一样

反射中的:

Field

要求:

类中真的有字段

内省中的属性:

不一定要求有同名 Field

所以:

JavaBean 属性 ≠ Java 字段

这是非常重要的概念。


二十三、只读属性

如果只有:

public String getName() {

    return name;
}

没有:

setName(...)

那么:

name

仍然是属性。

但:

descriptor
        .getWriteMethod()

可能返回:

null

这叫:

只读属性

二十四、只写属性

如果只有:

public void setPassword(
        String password
) {

}

没有 Getter,

那么:

getReadMethod()

可能为:

null

可以理解为:

只写属性

二十五、调用 Getter 前检查 null

Method getter =
        descriptor
                .getReadMethod();

if (
    getter != null
) {

    Object value =
            getter.invoke(
                    bean
            );
}

二十六、调用 Setter 前检查 null

Method setter =
        descriptor
                .getWriteMethod();

if (
    setter != null
) {

    setter.invoke(
            bean,
            value
    );
}

二十七、直接创建 PropertyDescriptor

除了遍历 BeanInfo,

还可以:

PropertyDescriptor descriptor =
        new PropertyDescriptor(
                "name",
                Student.class
        );

二十八、直接读取属性

PropertyDescriptor descriptor =
        new PropertyDescriptor(
                "name",
                Student.class
        );

Method getter =
        descriptor
                .getReadMethod();

Object value =
        getter.invoke(
                student
        );

二十九、直接写属性

Method setter =
        descriptor
                .getWriteMethod();

setter.invoke(
        student,
        "张三"
);

三十、IntrospectionException

如果属性规则无法识别,例如:

new PropertyDescriptor(
        "abc",
        Student.class
);

可能抛:

IntrospectionException

三十一、通用读取 Bean 属性

public static Object getProperty(
        Object bean,
        String propertyName
)
        throws Exception {

    BeanInfo beanInfo =
            Introspector
                    .getBeanInfo(
                            bean
                                    .getClass(),
                            Object.class
                    );

    for (
        PropertyDescriptor descriptor :
        beanInfo
                .getPropertyDescriptors()
    ) {

        if (
            descriptor
                    .getName()
                    .equals(
                            propertyName
                    )
        ) {

            Method getter =
                    descriptor
                            .getReadMethod();

            if (
                getter == null
            ) {

                throw new IllegalArgumentException(
                        "属性不可读:"
                                + propertyName
                );
            }

            return getter.invoke(
                    bean
            );
        }
    }

    throw new IllegalArgumentException(
            "属性不存在:"
                    + propertyName
    );
}

三十二、使用通用读取工具

Student student =
        new Student();

student.setName(
        "张三"
);

Object name =
        getProperty(
                student,
                "name"
        );

结果:

张三

三十三、通用写入 Bean 属性

public static void setProperty(
        Object bean,
        String propertyName,
        Object value
)
        throws Exception {

    BeanInfo beanInfo =
            Introspector
                    .getBeanInfo(
                            bean
                                    .getClass(),
                            Object.class
                    );

    for (
        PropertyDescriptor descriptor :
        beanInfo
                .getPropertyDescriptors()
    ) {

        if (
            descriptor
                    .getName()
                    .equals(
                            propertyName
                    )
        ) {

            Method setter =
                    descriptor
                            .getWriteMethod();

            if (
                setter == null
            ) {

                throw new IllegalArgumentException(
                        "属性不可写:"
                                + propertyName
                );
            }

            setter.invoke(
                    bean,
                    value
            );

            return;
        }
    }

    throw new IllegalArgumentException(
            "属性不存在:"
                    + propertyName
    );
}

三十四、字符串参数和属性类型

假设浏览器传:

age=20

通常最初拿到的是:

String

但 Setter 是:

setAge(
        Integer age
)

不能直接:

setter.invoke(
        student,
        "20"
);

因为:

String
不是 Integer

三十五、获取属性类型

Class<?> propertyType =
        descriptor
                .getPropertyType();

如果:

propertyType
== Integer.class

就可以:

Integer.valueOf(
        text
);

三十六、简单类型转换工具

public static Object convert(
        String text,
        Class<?> type
) {

    if (
        type == String.class
    ) {

        return text;
    }

    if (
        type == Integer.class
        || type == int.class
    ) {

        return Integer
                .valueOf(
                        text
                );
    }

    if (
        type == Long.class
        || type == long.class
    ) {

        return Long
                .valueOf(
                        text
                );
    }

    if (
        type == Double.class
        || type == double.class
    ) {

        return Double
                .valueOf(
                        text
                );
    }

    if (
        type == Boolean.class
        || type == boolean.class
    ) {

        return Boolean
                .valueOf(
                        text
                );
    }

    throw new IllegalArgumentException(
            "暂不支持类型:"
                    + type
                            .getName()
    );
}

三十七、Map 自动绑定成 Bean

假设:

Map<String, String> data =
        new HashMap<>();

data.put(
        "name",
        "张三"
);

data.put(
        "age",
        "20"
);

目标:

Student

三十八、自动绑定思路

创建 Student
↓
内省获取所有属性
↓
根据属性名从 Map 取值
↓
根据属性类型做转换
↓
调用 Setter

三十九、简单 bind()

public static <T>
T bind(
        Map<String, String> data,
        Class<T> clazz
)
        throws Exception {

    T bean =
            clazz
                    .getDeclaredConstructor()
                    .newInstance();

    BeanInfo beanInfo =
            Introspector
                    .getBeanInfo(
                            clazz,
                            Object.class
                    );

    for (
        PropertyDescriptor descriptor :
        beanInfo
                .getPropertyDescriptors()
    ) {

        String propertyName =
                descriptor
                        .getName();

        if (
            !data.containsKey(
                    propertyName
            )
        ) {

            continue;
        }

        Method setter =
                descriptor
                        .getWriteMethod();

        if (
            setter == null
        ) {

            continue;
        }

        String text =
                data.get(
                        propertyName
                );

        Object value =
                convert(
                        text,
                        descriptor
                                .getPropertyType()
                );

        setter.invoke(
                bean,
                value
        );
    }

    return bean;
}

四十、调用 bind()

Map<String, String> data =
        Map.of(
                "name",
                "张三",
                "age",
                "20"
        );

Student student =
        bind(
                data,
                Student.class
        );

这样:

Map
自动转换成 Student

四十一、和 Servlet 参数封装的关系

后面 Servlet 会收到:

name=张三
age=20

理论上就可以:

参数名
↓
JavaBean 属性名
↓
类型转换
↓
Setter

自动封装对象。


四十二、和 Spring MVC 参数绑定的关系

以后可以写:

public void add(
        Student student
) {

}

浏览器提交:

name=张三
age=20

Spring 可以自动:

创建 Student
绑定属性

底层会结合:

Bean 属性描述
反射
类型转换
数据绑定

完成。


四十三、和 MyBatis 的关系

MyBatis 查询:

SELECT
    name,
    age
FROM student;

返回:

Student

框架需要:

识别属性
创建对象
进行类型转换
给属性赋值

MyBatis 自己实现了更完整的反射和属性体系,

但思想和 JavaBean 内省非常接近。


四十四、和 Spring BeanWrapper 的关系

Spring 提供:

BeanWrapper

用于:

读取属性
修改属性
类型转换
嵌套属性处理

可以理解:

比原生 PropertyDescriptor
更完整的 Bean 操作工具

四十五、MethodDescriptor

BeanInfo 还能获取:

MethodDescriptor[]
        methods =
        beanInfo
                .getMethodDescriptors();

用于描述:

Bean 方法

四十六、BeanDescriptor

BeanDescriptor descriptor =
        beanInfo
                .getBeanDescriptor();

可以获取:

Bean 类描述信息

四十七、事件内省简单了解

JavaBeans 规范最早还包含:

事件模型

例如:

EventSetDescriptor

在早期:

AWT / Swing

中更常见。

现代 Web 后端:

了解即可

四十八、Introspector 缓存

Introspector 可能缓存:

BeanInfo

需要时可以:

Introspector
        .flushCaches();

或:

Introspector
        .flushFromCaches(
                clazz
        );

普通开发几乎不需要手动清理。


四十九、为什么缓存 BeanInfo

因为内省需要:

扫描方法
分析 Getter
分析 Setter
构造 PropertyDescriptor

反复分析会有成本,

所以缓存可以:

提升性能

五十、JavaBean 命名规范的重要性

标准:

getName()

setName(...)

框架很容易识别。

如果写:

readName()

changeName(...)

Java 语法没有问题,

但传统 JavaBeans 工具:

不会自动认为这是 name 属性

五十一、Introspector.decapitalize()

属性名转换会涉及:

Introspector
        .decapitalize(
                "Name"
        );

结果:

name

五十二、特殊大写名称

Introspector
        .decapitalize(
                "URL"
        );

通常结果:

URL

因为前两个字符都是大写。

所以 Bean 属性命名最好保持:

正常驼峰规则

例如:

userName
studentNo
createTime

五十三、Boolean 与 boolean Getter

primitive:

private boolean enabled;

常见:

isEnabled()

包装类型:

private Boolean enabled;

更常见:

getEnabled()

五十四、内省通常通过方法而不是 private Field

内省主要走:

Getter / Setter

所以相比:

field.setAccessible(true)

更符合:

JavaBean 封装

五十五、Setter 中业务规则仍会执行

例如:

public void setAge(
        Integer age
) {

    if (
        age < 0
    ) {

        throw new IllegalArgumentException(
                "年龄不能小于 0"
        );
    }

    this.age = age;
}

通过内省:

setter.invoke(...)

仍然会执行:

Setter 内部逻辑

五十六、直接 Field 赋值可能绕过 Setter

如果反射直接:

field.set(
        student,
        -10
);

可能绕过:

setAge()

中的校验。

这就是:

属性访问

和:

字段访问

的差别。


五十七、属性描述器缓存优化

如果每次查一个属性都遍历:

全部 PropertyDescriptor

可以提前缓存:

Map<
    String,
    PropertyDescriptor
>

五十八、构建属性 Map

public static Map<
        String,
        PropertyDescriptor
        >
getPropertyMap(
        Class<?> clazz
)
        throws IntrospectionException {

    BeanInfo beanInfo =
            Introspector
                    .getBeanInfo(
                            clazz,
                            Object.class
                    );

    Map<
            String,
            PropertyDescriptor
            > map =
            new HashMap<>();

    for (
        PropertyDescriptor descriptor :
        beanInfo
                .getPropertyDescriptors()
    ) {

        map.put(
                descriptor
                        .getName(),
                descriptor
        );
    }

    return map;
}

五十九、BeanUtil 雏形

public final class BeanUtil {

    private BeanUtil() {

    }

    public static Object getProperty(
            Object bean,
            String property
    ) {

    }

    public static void setProperty(
            Object bean,
            String property,
            Object value
    ) {

    }

    public static <T> T bind(
            Map<String, String> data,
            Class<T> clazz
    ) {

    }
}

这已经很像:

框架内部工具

六十、Apache Commons BeanUtils

很多项目中曾大量使用:

Apache Commons BeanUtils

例如:

BeanUtils.setProperty(
        bean,
        "name",
        "张三"
);

它内部会处理:

内省
反射
类型转换

六十一、Spring BeanUtils

Spring 也有:

org.springframework.beans.BeanUtils

例如:

BeanUtils.copyProperties(
        source,
        target
);

它和 Apache Commons BeanUtils:

不是同一个类

以后使用时注意:

import 包路径

六十二、属性复制基本原理

例如:

UserEntity
→
UserVO

两边都有:

id
username

框架可以:

分析 source 可读属性
↓
分析 target 可写属性
↓
属性名匹配
↓
类型兼容
↓
Getter 获取
↓
Setter 写入

六十三、手写简单 copyProperties

public static void copyProperties(
        Object source,
        Object target
)
        throws Exception {

    BeanInfo sourceInfo =
            Introspector
                    .getBeanInfo(
                            source
                                    .getClass(),
                            Object.class
                    );

    BeanInfo targetInfo =
            Introspector
                    .getBeanInfo(
                            target
                                    .getClass(),
                            Object.class
                    );

    Map<
            String,
            PropertyDescriptor
            > targetMap =
            new HashMap<>();

    for (
        PropertyDescriptor descriptor :
        targetInfo
                .getPropertyDescriptors()
    ) {

        targetMap.put(
                descriptor
                        .getName(),
                descriptor
        );
    }

    for (
        PropertyDescriptor sourceDescriptor :
        sourceInfo
                .getPropertyDescriptors()
    ) {

        PropertyDescriptor targetDescriptor =
                targetMap.get(
                        sourceDescriptor
                                .getName()
                );

        if (
            targetDescriptor == null
        ) {

            continue;
        }

        Method getter =
                sourceDescriptor
                        .getReadMethod();

        Method setter =
                targetDescriptor
                        .getWriteMethod();

        if (
            getter == null
            || setter == null
        ) {

            continue;
        }

        if (
            !targetDescriptor
                    .getPropertyType()
                    .isAssignableFrom(
                            sourceDescriptor
                                    .getPropertyType()
                    )
        ) {

            continue;
        }

        Object value =
                getter.invoke(
                        source
                );

        setter.invoke(
                target,
                value
        );
    }
}

六十四、isAssignableFrom()

Number.class
        .isAssignableFrom(
                Integer.class
        );

结果:

true

表示:

Integer 可以赋值给 Number

六十五、属性复制不能无脑使用

需要注意:

同名属性不一定业务含义相同

嵌套对象不是自动深拷贝

类型可能不兼容

敏感字段可能被错误复制

六十六、DTO / VO 转换安全

例如:

UserEntity

有:

password

而返回前端的:

UserVO

不应该有:

password

不要为了方便属性复制:

把敏感字段也设计进 VO

六十七、内省与依赖注入

例如:

public class UserService {

    private UserDao userDao;

    public void setUserDao(
            UserDao userDao
    ) {

        this.userDao =
                userDao;
    }
}

早期 Spring XML 可以:

<property
    name="userDao"
    ref="userDao"
/>

框架大致:

看到 property name=userDao
↓
找到 setUserDao()
↓
获取 UserDao Bean
↓
调用 Setter

六十八、Spring Setter 注入底层思路

读取 XML
↓
Class.forName
↓
创建 UserService
↓
内省分析 userDao 属性
↓
找到 setUserDao()
↓
Method.invoke()

这就是:

反射 + 内省

协作。


六十九、反射、内省、注解三者关系

可以先记:

反射
动态获取和操作类

内省
按照 JavaBean 规则分析属性

注解
给类 / 方法 / 字段增加元数据

框架会把它们组合起来。


七十、Spring 自动化示例

以后看到:

@Service
public class UserService {

}

框架大致:

扫描类
↓
反射读取 @Service
↓
创建 Bean
↓
处理属性或依赖
↓
放入 IOC 容器

七十一、MyBatis 属性映射示例

数据库:

id
username
age

Java:

User

框架需要:

数据库列
↓
Java 属性
↓
类型转换
↓
赋值

七十二、Lombok 与内省

以后如果使用:

@Data

Lombok 在编译期生成:

Getter
Setter

最终字节码仍可能存在:

标准 Bean 方法

所以内省仍然能识别。


七十三、record 与传统 JavaBean

Java 17 可以:

record User(
        Long id,
        String name
) {

}

访问:

user.name()

而传统 Bean:

getName()

所以 record:

不完全遵循传统 JavaBeans Getter/Setter 模型

现代框架通常会单独支持 record。


七十四、JavaBean 仍然重要

即使现代 Java 有:

record
Lombok
构造器绑定

大量:

框架
老项目
企业代码

仍然基于:

JavaBean

所以内省值得掌握。


七十五、常见错误:把字段名和属性名绝对等同

例如:

private String value;

public String getName() {

    return value;
}

反射 Field:

value

JavaBean 属性:

name

两者不一定一致。


七十六、常见错误:没有 Getter

只有:

setName(...)

那么:

getReadMethod()

可能返回:

null

七十七、常见错误:没有 Setter

只有:

getName()

那么:

getWriteMethod()

可能返回:

null

七十八、常见错误:Getter / Setter 类型不一致

例如:

public String getAge() {

}

但是:

public void setAge(
        Integer age
) {

}

会让属性定义变得混乱。

应该:

保持类型一致

七十九、常见错误:命名不规范

例如:

public String fetchName() {

}

不会被标准 JavaBeans 内省自动当作:

name Getter

八十、常见错误:直接 invoke 字符串参数

表单:

age="20"

Setter:

setAge(
        Integer age
)

需要先:

String → Integer

再调用 Setter。


八十一、常见错误:不处理 read/write method 的 null

不要直接:

descriptor
        .getWriteMethod()
        .invoke(...)

因为:

只读属性

会导致:

NullPointerException

八十二、常见错误:把 class 当普通属性

遍历:

getPropertyDescriptors()

可能出现:

class

需要:

过滤

或者使用:

getBeanInfo(
        clazz,
        Object.class
)

八十三、常见错误:以为内省和反射完全无关

实际上:

内省底层仍然会得到 Method
再通过反射调用

所以它们是:

上下层关系

而不是互相替代。


八十四、练习题 1:获取属性

定义 Student:

name
age
score

使用:

Introspector

输出全部 Bean 属性。


八十五、练习题 2:过滤 class

分别尝试:

手动过滤 class

和:

getBeanInfo(
    Student.class,
    Object.class
)

比较结果。


八十六、练习题 3:读取属性

使用:

PropertyDescriptor

读取:

name

八十七、练习题 4:修改属性

通过:

getWriteMethod()

把 name 修改成:

李四

八十八、练习题 5:通用 getProperty

实现:

Object getProperty(
        Object bean,
        String propertyName
)

八十九、练习题 6:通用 setProperty

实现:

void setProperty(
        Object bean,
        String propertyName,
        Object value
)

九十、练习题 7:Map 转 Bean

Map:

name=张三
age=20
score=90

自动绑定成:

Student

九十一、练习题 8:Bean 属性复制

定义:

UserEntity
UserVO

只复制:

同名
类型兼容

的属性。


九十二、练习题 9:只读属性

创建:

public String getVersion() {

    return "1.0";
}

不写 Setter。

观察:

readMethod
writeMethod

九十三、练习题 10:boolean 属性

定义:

private boolean enabled;

使用:

isEnabled()

setEnabled()

观察 PropertyDescriptor。


九十四、内省必须掌握 API

Introspector
        .getBeanInfo()

BeanInfo
        .getPropertyDescriptors()

PropertyDescriptor
        .getName()

PropertyDescriptor
        .getPropertyType()

PropertyDescriptor
        .getReadMethod()

PropertyDescriptor
        .getWriteMethod()

九十五、反射和内省对比

对比 反射 内省
主要包 java.lang.reflect java.beans
关注点 整个类结构 JavaBean 属性
字段 Field 属性不等于字段
方法 所有 Method Getter / Setter
构造 支持 不是重点
属性识别 自己处理 自动分析
底层关系 基础能力 基于反射封装

九十六、必须回答的问题

学完后应该能够回答:

1. 什么是内省?

2. 内省和反射有什么区别?

3. BeanInfo 是什么?

4. PropertyDescriptor 是什么?

5. getReadMethod() 返回什么?

6. getWriteMethod() 返回什么?

7. 为什么会出现 class 属性?

8. JavaBean 属性和字段有什么区别?

9. 为什么 Getter / Setter 命名必须规范?

10. Spring / MyBatis 为什么需要 Bean 属性识别?

九十七、内省知识结构

Java Introspection
│
├─ Introspector
│   └─ getBeanInfo()
│
├─ BeanInfo
│   ├─ PropertyDescriptor
│   ├─ MethodDescriptor
│   └─ BeanDescriptor
│
├─ PropertyDescriptor
│   ├─ name
│   ├─ propertyType
│   ├─ readMethod
│   └─ writeMethod
│
├─ JavaBean
│   ├─ Getter
│   ├─ Setter
│   └─ Property
│
└─ Framework
    ├─ 参数绑定
    ├─ 属性复制
    ├─ MyBatis 映射
    └─ Spring Bean

九十八、从内省到注解

现在已经知道:

反射
可以动态操作类

内省
可以动态分析 Bean 属性

接下来还有一个关键问题:

框架怎么知道“哪些类需要创建”“哪些方法是测试方法”“哪些字段需要注入”?

例如:

@Service

@Autowired

@Test

@Override

这些就是:

注解
Annotation

注解相当于:

给程序元素附加元数据

框架再通过:

反射

读取这些元数据。


九十九、本章总结

内省最核心的一句话:

按照 JavaBean 规范,在运行时分析对象有哪些属性,以及每个属性对应哪些 Getter 和 Setter。

核心流程:

Class
↓
Introspector
↓
BeanInfo
↓
PropertyDescriptor
↓
Getter / Setter
↓
Method.invoke()

它和反射的关系:

反射
提供动态方法操作能力

内省
帮你按照 Bean 规范找到正确的方法

以后:

Servlet 参数封装
Spring 数据绑定
Spring IOC
MyBatis 对象映射
Bean 属性复制

都会涉及类似思想。

下一篇继续整理:

Java 注解详解

把:

反射
内省
注解

这一组框架底层基础完整串起来。