Spring 源码中的“隐形向导”:迭代器模式如何优雅地遍历复杂对象?
导语: 在我们日常的业务开发中,遍历一个
List或Map简直就像呼吸一样自然。然而,当面对一个层层嵌套、结构极其复杂的容器时,普通的for循环往往会让我们陷入代码的“屎山”之中。Spring 框架作为 Java 开发者的“军火库”,不仅存纳了成百上千个 Bean,还处理着各种复杂的配置源、拦截器链。它究竟是如何在不暴露内部复杂结构的前提下,依然能让我们优雅、顺畅地完成遍历的?今天,我们将深入剖析 Spring 源码中的“隐形向导”——迭代器模式。
一、 引入:从“套娃”式的集合遍历说起
在业务系统启动或配置加载时,我们经常会遇到这样的场景:
假设我们有一个复杂的容器 ComplexContainer,里面既直接存放了普通的 Bean,又可能包含一个子容器 SubContainer(子容器里可能还有它的子容器……),并且子容器里还挂着一个拦截器列表 Interceptors。
如果我们用最传统的“上帝视角”去遍历它,代码大概会变成这样:
// “上帝视角”下的糟糕遍历public void traverseComplexContainer(ComplexContainer container) {// 1. 遍历普通的 Beansfor (Bean bean : container.getBeans()) {process(bean);}// 2. 递归遍历子容器if (container.getSubContainer() != null) {traverseSubContainer(container.getSubContainer());}// 3. 遍历拦截器if (container.getInterceptors() != null) {for (Interceptor interceptor : container.getInterceptors()) {process(interceptor);}}// ... 未来可能还有更多层级或类型}
这样的代码有什么问题?
traverseComplexContainer)过度了解了容器内部的 getBeans()、getSubContainer() 等私有结构。这违反了“单一职责原则”和“开闭原则”。我们需要一个“私人向导”。理想的情况是,容器只负责“存”,它应该配一个专职的“私人向导”——不管里面是套娃、链表还是树,你只要问向导“下一个是谁”,而不需要关心向导是怎么翻山越岭把元素给你的。
这个向导,就是迭代器模式。
二、 模式内核:迭代器模式的“极简哲学”
迭代器模式(Iterator Pattern)的核心定义非常清晰:
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
它将“遍历逻辑”从聚合对象中剥离出来,封装进一个独立的迭代器对象中。
核心组件:
hasNext()、next())。•ConcreteIterator(具体迭代器): 实现具体容器的遍历逻辑。•Aggregate(抽象容器): 定义一个“创建迭代器”的接口。•ConcreteAggregate(具体容器): 返回一个具体迭代器的实例。手写 1.0:如果没有 Iterator,客户端会如何?
我们来看看如果不使用迭代器,客户端代码会如何依赖容器的内部实现:
// 1. 客户端public class Client {publicstaticvoidmain(String[] args) {// 自定义一个简单的聚合对象SimpleList list = new SimpleList(3);list.add("Item 1");list.add("Item 2");list.add("Item 3");// 如果没有 Iterator,客户端必须知道它内部是用数组存的,并且必须用 index 遍历for (int i = 0; i < list.getElements().length; i++) {System.out.println("Item: " + list.getElements()[i]);}}}// 2. 具体聚合对象class SimpleList {private String[] elements; // 内部是用数组存的private int size = 0;publicSimpleList(int capacity) { this.elements = new String[capacity]; }publicvoidadd(String element) { elements[size++] = element; }// 暴露了内部实现细节!public String[] getElements() { return elements; }}
客户端直接依赖了 elements 数组和其索引逻辑。一旦我们将 SimpleList 的内部实现改为 List<String>,客户端代码就得重写。
有了迭代器模式,客户端只需要关心统一的迭代器接口,代码将变得极其解耦。
三、 Spring 深度实战:迭代器模式的“高级玩法”
Spring 框架在处理复杂层级结构和解耦方面,将迭代器模式运用到了极致。
1. CompositeIterator:处理“套娃”的神器
这是 Spring 中最能体现迭代器模式威力的一个类。
场景描述:组合多个配置源在 Spring Boot 启动时,它需要处理多个外部配置源(PropertySources),如 application.yml、环境变量、命令行参数等。有些 PropertySources 本身也是复合结构。
Spring 需要把这些多个迭代器合并成一个,让外部调用者对内部的复杂嵌套完全无感知。
源码解读:`org.springframework.util.CompositeIterator`
Spring 设计了一个复合迭代器,它本身实现了 Iterator 接口,但其内部持有了多个子迭代器。
public class CompositeIterator<E> implements Iterator<E> {private final List<Iterator<E>> iterators = new LinkedList<>(); // 持有多个子迭代器public void add(Iterator<E> iterator) { this.iterators.add(iterator); } // 可以动态添加@Overridepublic boolean hasNext() {// 精妙之处:它会递归判断,遍历完一个子迭代器,自动切换到下一个for (Iterator<E> iterator : this.iterators) {if (iterator.hasNext()) { return true; }}return false;}@Overridepublic E next() {for (Iterator<E> iterator : this.iterators) {if (iterator.hasNext()) { return iterator.next(); }}throw new NoSuchElementException("All iterators exhausted");}}
形象比喻:“多节火车的贯通门”可以把 CompositeIterator 比作“多节火车的贯通门”。每一节车厢(一个子迭代器)都有自己的门和内部乘客。如果你是验票员(客户端),CompositeIterator 这个贯通门能让你顺畅地从第一节车厢遍历到最后一节车厢的所有乘客,而你不需要关心车厢之间是如何连接的。
模式应用:組合模式 + 迭代器模式
2. HandlerExecutionChain:Spring MVC 的执行链路
场景描述:拦截器链当一个 HTTP 请求进来,Spring MVC 需要按顺序调用那一串拦截器(Interceptors)的 preHandle 方法,然后再调用 Controller,最后反向调用 postHandle 方法。
模式应用:思想的延伸虽然 HandlerExecutionChain 在其内部为了性能使用了数组 HandlerInterceptor[] 来存纳拦截器,但它暴露出的遍历逻辑本质上就是迭代器思想的延伸。
// HandlerExecutionChain 简化代码public class HandlerExecutionChain {private HandlerInterceptor[] interceptors; // 内部是用数组存的private int interceptorIndex = -1; // 记录当前执行到的索引// 实际上这就是迭代器思想的延伸boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {if (getInterceptors() != null) {for (int i = 0; i < getInterceptors().length; i++) {HandlerInterceptor interceptor = getInterceptors()[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i; // 迭代过程}}return true;}// ...}
它虽然没有直接返回一个 Iterator 对象,但在内部优雅地处理了遍历、中断和状态维护(interceptorIndex),实现了类似的效果。
3. ConfigurableListableBeanFactory:Bean 的巡检员
场景描述:Bean 巡检Spring 容器启动时,比如在执行 BeanFactoryPostProcessor 或进行 Bean 的依赖注入时,它需要高效、安全地巡检容器内成百上千个已注册的 BeanDefinition。
模式应用:封装性和只读性你可能会问:为什么不直接暴露一个 List<BeanDefinition> 呢?
// ConfigurableListableBeanFactory 接口public interface ConfigurableListableBeanFactory extends ListableBeanFactory, ... {// 这里不是返回一个 List,而是可以像迭代器一样遍历Iterator<String> getBeanNamesIterator();// ...}
对比思考:如果暴露 List,不仅暴露了内部数据结构,还可能导致客户端不小心修改了 List 的大小,从而破坏 BeanFactory 的一致性。而通过迭代器模式,Spring 可以返回一个“只读”的迭代器,既允许你顺序巡检,又保证了容器的安全性。
四、 升华:为什么 Spring 离不开迭代器?
通过上面的例子,我们不难看出 Spring 对迭代器模式的深度依赖。原因在于:
五、 总结与避坑指南
迭代器模式不是为了消除循环,而是为了隐藏复杂的逻辑。
for (E e : collection) 或 Stream API 即可。•不要过度设计。如果容器结构非常清晰简单,强行引入迭代器只会增加代码量。💡 文末互动
你在写业务代码时,有没有遇到过普通 for 循环搞不定的复杂遍历场景?比如,当面对一个具有无限层级、既有叶子节点又有组合节点的层级结构时,你是如何优雅地处理遍历逻辑的?
欢迎留言分享,我会选一个最有趣的案例送出技术书籍。
夜雨聆风
