Spring 之元编程

一、从 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),剩下的生成、组装、织入和执行细节由框架帮你搞定。

相关推荐
liliangcsdn2 小时前
python下载并转存http文件链接的示例
开发语言·python
我命由我123452 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
leoufung2 小时前
LeetCode 373. Find K Pairs with Smallest Sums:从暴力到堆优化的完整思路与踩坑
java·算法·leetcode
阿蒙Amon2 小时前
C#每日面试题-委托和事件的区别
java·开发语言·c#
宋情写2 小时前
java-IDEA
java·ide·intellij-idea
最贪吃的虎2 小时前
Git: rebase vs merge
java·运维·git·后端·mysql
资生算法程序员_畅想家_剑魔3 小时前
Java常见技术分享-12-多线程安全-锁机制
java·开发语言
一叶飘零_sweeeet3 小时前
吃透 Spring 体系结构
java·spring
胡楚昊3 小时前
NSSCTF动调题包通关
开发语言·javascript·算法