一、从 IoC 看"对象创建逻辑的抽象"
1. IoC 的本质:把"new XXX()"和装配逻辑移出业务代码
传统写法里,你的代码里到处是:
- new Service()
- new Repository()
- 自己设置各种依赖(setter / 构造器)
这些是"对象怎么创建、怎么组装"的具体逻辑,本质上是一种"程序的结构"。一旦写死,就不太容易在外部控制这个结构。
Spring 的 IoC 容器要做的是:
- 让你"描述"要什么对象(Bean 定义)
- 让你"描述"依赖关系(依赖注入)
- 容器根据这些描述(元数据)来统一负责创建、初始化、销毁对象
"描述"这个步骤,就是元编程的体现:你在"写关于代码结构的代码"。
2. BeanDefinition:对象创建的"元模型"
Spring 内核里最重要的抽象之一就是 BeanDefinition。它不是一个业务对象,而是"一个对象应该怎么创建"的描述:
- 这个 Bean 是什么类(beanClassName)
- 它的作用域是 singleton / prototype(scope)
- 它依赖哪些其他 Bean(dependencies / property values)
- 它是不是懒加载(lazyInit)
- 初始化、销毁时要调哪些方法(initMethodName / destroyMethodName)
你通过 XML / JavaConfig / 注解写下的各种配置,最终都会被解析成一堆 BeanDefinition,由容器在运行期解释并执行。这些 BeanDefinition 就是"关于对象如何被创建的数据结构",是 Spring 在对象创建维度做元编程的基础。
3. 依赖注入:用元数据描述"依赖结构"
当你写:
- @Autowired private UserService userService;
- 或者构造器注入、setter 注入
实际上你是在写"这个类在运行时需要某个依赖"的元数据,容器拿到这些元数据后,帮你完成依赖图的构建和注入,而不是在业务代码里手动 new。
这就是"把依赖结构从业务代码中抽象出来,放在配置/注解元数据里",在元层统一管理。
4. 生命周期回调:把"对象生命周期间做什么动作"变成元数据
比如:
- @PostConstruct / @PreDestroy
- InitializingBean / DisposableBean 接口
- XML 中的 init-method / destroy-method
这些都是让对象在某个"生命阶段"执行一些逻辑,而这些逻辑由容器在统一的框架层调用,相当于:
- 你声明"这个对象在某个阶段要做什么"
- 容器在"元层"负责在适当时机调用
这也是元编程:生命周期逻辑是"程序结构的一部分",但由框架控制执行时机。
5. 条件注解 & Profile:根据"元条件"决定加载哪些 Bean
- @Conditional(xxx.class)
- @Profile("dev" / "prod")
本质上都是"在加载哪些对象、构建什么样的对象图"这个层面上做条件判断,是"对对象集合(结构)进行编程"。
- 你写的不再是业务逻辑 if/else
- 而是写"在什么环境下(元条件)包含/排除哪些 Bean"
6. 元注解:在注解层面做"元编程"
Spring 里的 @Service、@Controller、@Repository 其实都是 @Component 的"组合/别名",这是典型的元注解用法:定义"关于注解的注解"。
你还可以自己组合,比如:
- @MyController = @Controller + @RequestMapping("/api") + @Secured("ROLE_USER")
这样你在代码里只需要写一个 @MyController,就相当于同时应用了多条语义。这里的"注解组合"本身就是一种对注解元数据的编程,属于元编程范畴。
二、从 AOP 看"业务逻辑执行流程的抽象"
1. AOP 的动机:横切关注点(Cross-cutting Concerns)
很多非功能性需求,比如:
- 事务管理(在方法前开启、方法后提交/回滚)
- 日志记录(入参、出参、异常)
- 安全检查(是否有权限访问此方法)
- 性能监控(执行时间统计)
这些逻辑会"横切"大量业务方法。如果每个方法都手动写,会:
- 重复代码太多
- 污染业务逻辑
- 很难统一维护
AOP 的思想是:把这些"横切关注点"从业务方法中抽离出来,用一种统一的方式描述它们"在什么条件下执行、在什么位置执行",这就是对"业务逻辑执行流程"的抽象。
2. 切面(Aspect)、切点(Pointcut)、通知(Advice):元模型
Spring AOP 把"执行流程"抽象成几个元概念:
-
切面(Aspect)
- 一个封装了横切逻辑的模块,本质上是一个"在程序执行流程中如何切入"的描述。
-
切点(Pointcut)
- 描述"在哪些连接点(Join Point,通常是方法)上应用切面"
- 使用表达式来匹配方法名、包名、参数类型等,如:
- execution(* com.example.service.*.*(...))
- 这是典型的"在元层对执行位置进行编程"。
-
通知(Advice)
- 描述"在连接点的什么时机执行逻辑":
- @Before(方法前)
- @AfterReturning(正常返回后)
- @AfterThrowing(抛异常后)
- @Around(环绕整个方法调用)
- 描述"在连接点的什么时机执行逻辑":
你写的不是"在每个方法内部写逻辑",而是:
- 在元层(AOP 配置)定义"在哪些方法的什么时机,执行什么逻辑"
这就是对"执行流程"的抽象。
3. 动态代理:Spring AOP 的底层机制
Spring AOP 在运行期通过动态代理(JDK 动态代理或 CGLIB)织入切面逻辑:
- 原始对象:被 Spring 管理的业务 bean
- 代理对象:容器在原始对象外再包一层壳,在壳里执行"前置逻辑 -> 调用目标 -> 后置逻辑"
对调用方来说,接口是没变的,但实际执行时"被注入了额外流程"。这个壳是由框架根据元数据(Aspect + Pointcut + Advice)自动生成的。
这本身也是一种元编程行为:
- 你提供的只是"在什么方法前后做什么"的描述
- 框架自动生成/修改执行路径(字节码/代理对象),实现你描述的执行流程
4. 典型场景:事务管理
最经典的就是 @Transactional:
- 你只在方法/类上标记注解
- Spring AOP 根据这个注解生成事务切面
- 在方法调用前开启事务
- 方法抛异常时回滚,正常返回时提交
这一整条"执行流程"的编排都由 Spring 在框架层完成,业务代码只需声明"这个方法需要事务"。
5. 与 IoC 联合:元模型之间的联动
在实际 Spring 项目里,AOP 通常是跟 IoC 容器联动的:
- Bean 定义里包含"这个 Bean 要不要被代理"的信息(比如有 @Transactional、自己定义的 @Aspect 等)
- 容器在初始化 Bean 时,发现需要应用切面,就用代理包装原始 Bean,再放入容器
从元编程视角看:
- IoC 负责管理"有哪些对象(Bean)"
- AOP 负责管理"这些对象在执行时的额外流程是什么"
- 两者通过统一的元数据模型(注解 + 配置)协同工作
三、从 Template 看"流程控制结构的抽象"
1. 模板方法模式:固定骨架 + 可变步骤
模板方法模式的核心是:
- 在父类中定义算法的骨架(整体流程)
- 把其中一些步骤的具体实现留给子类(或通过回调接口实现)
GoF 的经典描述是:在操作中定义算法的框架,将一些步骤推迟到子类tencent.com。Spring 中大量使用了这个模式,比如 JdbcTemplate、RestTemplate、各种 *Template 类,都是典型示例。
2. Template 作为"控制结构"的元抽象
你写业务代码时,重复的控制结构如:
- 获取连接
- 开启事务
- 执行 SQL / 请求
- 处理结果集 / 响应
- 关闭资源 / 处理异常
这些步骤可以看作一个"算法结构",只是每次具体 SQL / 具体请求不同。
Template 把这个控制结构固化为一个模板方法,比如 JdbcTemplate 的 execute/query 方法,里面会:
- 处理连接获取和释放
- 处理异常转换
- 你只需要提供"真正可变"的部分(SQL、参数映射、结果映射)
这相当于:你在元层定义了一个"标准的执行流程",把可变部分通过回调接口暴露给业务代码。
3. JdbcTemplate 示例:用回调抽象可变步骤
典型写法:
- jdbcTemplate.query(sql, rowMapper, args);
这里的:
- sql:可变的语句
- rowMapper:把 ResultSet 映射为对象的逻辑(可变步骤)
- args:参数(可变部分)
而"获取连接、执行查询、遍历 ResultSet、关闭连接、翻译异常"这些固定流程都封装在 JdbcTemplate 内部,由模板方法控制。
模板方法本身就是一个"控制结构的抽象",你在写业务代码时,只是在填空:给这个结构提供"变量部分",整个结构如何执行,是由 Template 在框架层完成的。
4. 回调接口 vs 继承:更灵活的元控制
Spring 的 Template 通常更倾向于使用回调接口(比如 RowMapper、ConnectionCallback)而不是继承,这样的好处是:
- 业务类不需要继承特定的 Template 类
- 可以在 Lambda 表达式里提供简短的逻辑块
- 控制结构仍然由 Template 完全掌控,你只是提供"一段可执行片段"
本质上和模板方法模式一样:控制结构的"骨架"在框架层,业务代码只提供"步骤内容"。
5. 与 IoC/AOP 的关系:三层元抽象的协同
- IoC:负责管理 JdbcTemplate/RestTemplate 这些 Template 对象本身的生命周期和依赖注入
- AOP:可以在 Template 执行前后,再叠加一层横切逻辑(如监控、日志)
- Template:在执行某个具体操作(JDBC 请求、HTTP 请求)时,控制其"内部流程"
从元编程的视角看:
- IoC 抽象的是"有哪些对象、怎么装配"
- AOP 抽象的是"这些对象的方法调用时,整体执行路径中横切什么逻辑"
- Template 抽象的是"对象执行某个具体任务时的内部流程结构"
三层层层递进:对象 → 对象之间的调用流程 → 单个任务内部的流程结构。
小结:Template 维度的 Spring 元编程
- 元对象:各种 Template 类 + 回调接口(RowMapper、ResultSetExtractor 等)
- 元逻辑:在 Template 内部写死算法骨架,在运行期调用回调中的可变步骤
- 效果:业务代码只提供"关键步骤",重复而容易出错的流程控制结构被抽象成模板,减少样板代码,降低出错概率。
这三者合在一起,就是 Spring 的一整套"元编程体系":你描述结构(IoC)、描述流程(AOP)、使用模板执行结构(Template),剩下的生成、组装、织入和执行细节由框架帮你搞定。