Java内省机制详解
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 注解详解
把:
反射
内省
注解
这一组框架底层基础完整串起来。