知识卡片

元数据映射的两种实现途径:代码生成 vs 反射编程

结构图卡

内容

把关系-对象映射的细节浓缩进元数据后,让这份元数据真正驱动运行代码主要有两条路。代码生成:写一个程序,输入元数据、输出映射实现类的源代码,这些代码在构建阶段(通常紧邻编译前)生成,看起来和手写的没什么两样,但不应该被手工编辑、也不需要纳入源代码控制——好处是调试时更直观,能在调试器里清楚看到实际运行的代码,作者本人更偏爱这条路,认为对水平一般的开发者更友好;代价是缺乏动态性,映射一旦要改,至少要重新编译、重新部署相关部分。反射编程:把方法和域当成数据,运行时从元数据文件里读出域/方法名字,动态调用(比如动态找到并调用setName)来完成映射——好处是动态,改一下映射数据文件,现有的类立刻就能用上新元数据,甚至可以在程序运行期间监听某种特殊中断来触发重新读取;作者虽然承认反射非常适合数据库映射这个场景,却仍然通常不建议用它,主要顾虑不是速度(反射调用本身慢,但架在同样慢的远程SQL调用之上,这点损耗未必是大问题,具体影响需要实测),而是反射代码往往很难调试。两种方式都不太好调试,最终选哪个很大程度上取决于开发团队对”生成代码”和”反射代码”各自的熟悉程度。

结构图

flowchart LR
  A["元数据驱动映射的两条路"] --> B["代码生成<br/>构建期生成源码<br/>调试直观但改动需重新编译部署"]
  A --> C["反射编程<br/>运行时动态调用<br/>改元数据即生效但难调试"]

参考来源

- 位置:《企业应用架构模式》第二部分"模式"之"第13章 对象-关系元数据映射模式"之"13.1 元数据映射"(源文件:_epub-src/OEBPS/Text/000143.html) - 结论依据:原文说明"使用代码生成时需要写这样一个程序:输入是元数据,输出是映射实现类的源代码……反射程序可能要求对象有一个名为setName的方法……我通常建议不要采用反射,部分原因是它慢,但主要原因是它往往会产生很难调试的代码……对反射方法而言,只要改变映射数据文件,现有的类就会使用新的元数据",直接支撑本卡结构图。 - 原始内容:生成代码更加显式,这样可以看到调试器中的运行情况。因此我通常更加愿意使用生成而不是反射。