概括总结
模板方法模式 定义InitialHandler接口,包含 层级type、同层级顺序order、初始化方法执行函数excuteInit
策略模式 实现 对应spring不同启动阶段的抽象类,不需要关注触发机制和事件监听
实现抽象类执行引擎abstractExcutor,定义excute方法,通过spi机制获取InitialHandler的Bean
stream流
筛选(与执行器相同type的Handler)
排序 order字段
执行 调用executeInit方法
不同策略的excutor继承抽象类,并根据spring规定的定义范式写代码即可
对修改关闭,对扩展开放。 如果需要新增一个初始化任务,只需要新增一个类继承对应的 Handler 即可,完全不需要修改现有的启动代码,极大地提高了系统的可维护性。”
树形动态编排框架
每个组件继承组件抽象类,定义组件的执行方法
继承Initial在spring初始化时,构建组件树(根据每个组件的type、层级、order)
container存储每个组件树的根节点,container.execute(业务type,业务参数DTO)
根据type获取组件树根节点,执行层序遍历依次执行组件
简历中的写法(项目经历/专业技能)
遇到的问题:服务启动逻辑分散(InitlizingBean,@PostConstruct,ApplicationListener,CommandLineRunner),依赖关系混乱,执行顺序不可控。
为什么要用这个技术:需要一个统一的机制来管理这些启动任务,而不是让它们散落在各个业务Service中。业务耦合度高
达到什么效果:集中式管理(统一接口),动态顺序编排(可配置顺序),降低耦合(业务逻辑不需要关心具体的Spring启动钩子)。
请详细讲讲这个“统一初始化编排框架”是如何实现的?
回答逻辑: 你需要展示你对 Spring Bean生命周期、设计模式(模板方法、策略模式) 以及 Spring容器机制 的深刻理解。
参考回答: “这个框架的核心目的是为了规范化管理项目中各种各样的启动初始化任务。
第一,在顶层设计上,我定义了一个统一的策略接口(InitializeHandler)。 这个接口主要包含两个核心方法:一个是 type(),用于区分初始化的场景( InitializingBean 阶段,或者是监听Spring事件阶段);另一个是 executeOrder(),用于返回一个整数,决定同类型任务的执行优先级。
第二,我利用模板方法模式封装了不同生命周期的抽象基类。 我为 Spring 的不同启动阶段提供了抽象类,例如 AbstractApplicationCommandLineRunnerHandler 或 AbstractApplicationPostConstructHandler。业务只需要继承对应的抽象类,关注具体的业务初始化逻辑,而不需要关心底层的事件监听和触发机制。
第三,核心的执行引擎是基于 Spring 容器的 Bean 发现机制实现的。 我实现了一个核心执行器(AbstractApplicationExecute),它利用 ApplicationContext 获取容器中所有实现了 InitializeHandler 接口的 Bean。 当 Spring 触发某个生命周期事件(比如应用启动完成)时,对应的执行器会:
筛选:根据 type() 筛选出当前阶段需要执行的所有 Handler。排序:根据 executeOrder() 对这些 Handler 进行排序。执行:依次调用它们的 executeInit() 方法。最后,通过这种设计,我们实现了‘对修改关闭,对扩展开放’。 如果需要新增一个初始化任务,只需要新增一个类继承对应的 Handler 即可,完全不需要修改现有的启动代码,极大地提高了系统的可维护性。”
技术关键词(面试时可以带出的亮点): Spring SPI / Bean 发现机制 (getBeansOfType)
模板方法模式 (AbstractHandler 屏蔽细节) 策略模式 (不同的 Handler 实现)
Spring 生命周期 (CommandLineRunner, InitializingBean, ApplicationListener) 开闭原则 (Open/Closed Principle)
示例
在我们介绍统一管理组件之前,我们先来回忆下这几种初始化的操作是怎么使用的
InitializingBean
@Component
public class TestInitializingBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
System.out.println("======afterPropertiesSet执行======");
}
}PostConstruct
@Component
public class TestPostConstruct {
@PostConstruct
public void postConstruct(){
System.out.println("======postConstruct执行======");
}
}ApplicationListener
@Component
public class TestEventListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
System.out.println("======ApplicationStartedEvent执行======");
}
}CommandLineRunner
@Component
public class TestCommandLineRunner implements CommandLineRunner {
@Override
public void run(final String... args) {
System.out.println("======run执行======");
}
}结果
======initializingBean afterPropertiesSet执行======
======postConstruct执行======
======ApplicationStartedEvent执行======
======run执行======可以看到这四种都是在服务启动后会执行,执行顺序是afterPropertiesSet > postConstruct > ApplicationStartedEvent > run
详细讲讲这个树形结构的动态编排框架是怎么实现的吗?”
组合模式(Composite Pattern)通常用于处理“部分-整体”的层次结构,
回答策略: 由浅入深,先讲设计思路(接口设计),再讲核心实现(Spring集成+树构建),最后讲执行机制。 参考回答话术:
设计初衷(背景): “当时我们的场景是这样的,服务启动(或某个复杂业务)需要执行很多步骤,比如加载配置、预热缓存、检查依赖等。这些步骤之间有层级关系,也有先后顺序。如果全写在 main 方法或者一个大 Service 里,代码非常乱,而且想插一个新的步骤很麻烦。所以我想利用组合模式把这些步骤抽象成一个个独立的节点,自动组装成一棵树来执行。”
核心抽象(AbstractComposite): “首先,我定义了一个抽象基类 AbstractComposite。 这个类里定义了三个核心要素: Tier(层级):决定这个节点在树的第几层。 Order(顺序):决定同层级下的执行顺序。 ParentOrder(父节点标识):决定它的父节点是谁。 同时,它定义了一个 execute 方法,所有的具体业务逻辑(比如‘预热Redis’)都去继承这个类并实现这个方法。”
容器与自动组装(CompositeContainer - 核心亮点): “然后,我实现了一个核心容器 CompositeContainer,利用 Spring 的扩展点来实现自动组装: 服务发现:在 init 方法中,利用 ApplicationContext.getBeansOfType 一次性扫描容器中所有实现了 AbstractComposite 的 Bean。 分组与排序:拿到所有 Bean 后,我先按业务类型(Type)分组,然后利用 TreeMap 按 Tier(层级)进行排序,保证处理顺序是从上到下的。 树形构建(算法细节):这是最关键的一步。我写了一个递归或者分层遍历的逻辑(buildTree),遍历每一层的节点,根据节点配置的 ParentOrder,自动把它这就挂载到上一层对应的父节点对象中。这样,原本散落在 Spring 容器里的扁平 Bean,就被我在内存中组装成了一棵棵完整的对象树。”
统一执行: “最后,对外暴露一个 execute(type, param) 接口。业务方只需要传入业务类型,容器就会找到这棵树的根节点,调用根节点的执行方法。由于是组合模式,根节点执行完会自动触发子节点的执行(或者由容器控制遍历执行),从而完成整个复杂的调用链。”
价值总结: “这样做的好处是,以后同事要加一个新的校验逻辑,只需要写一个 Bean 继承我的基类,指定好它是谁的子节点,Spring 启动时就会自动把它编排进去,完全不需要改动现有的代码,真正实现了开闭原则。”
以下是将您提供的内容整理成的 Markdown 格式文档,结构清晰,便于阅读和复习。
基于组合模式与 Spring IOC 的通用执行框架解析
这段代码实现了一个基于 组合模式(Composite Pattern) 和 Spring IOC 容器 的通用执行框架。它的核心目的是将复杂的业务逻辑拆解为一个个独立的组件(节点),并在应用启动时自动组装成“执行树”,最后对外提供统一的调用入口。
在面试中,你可以按照 “核心概念 -> 设计亮点 -> 实现细节 -> 业务价值” 的逻辑来阐述。
一、 核心设计理念
1. 组合模式 (Composite Pattern)
- 定义:将对象组合成树形结构以表示“部分-整体”的层次结构。
- 角色映射:
AbstractComposite:抽象构件。- 具体的业务逻辑 Bean:叶子节点或树枝节点。
CompositeContainer:Client 角色,负责构建和持有这棵树。
2. Spring 容器扩展 (Inversion of Control)
- 自动发现:利用 Spring 的
getBeansOfType自动发现所有组件。 - 开闭原则 (OCP):实现了开闭原则,新增业务逻辑只需添加一个 Bean,无需修改容器代码。
3. 扁平数据转树形结构
- 问题:Spring 容器中的 Bean 是扁平存储的(Map 结构)。
- 解决:该类通过算法将扁平的 Bean 列表,根据
tier(层级)、order(自身顺序)、parentOrder(父级顺序)重新“编织”成树状结构。
二、 代码细节解析
1. init 方法 (初始化与组装)
- 扫描:通过
applicationEvent.getBeansOfType(AbstractComposite.class)获取系统中定义的所有组件。 - 分组:使用 Java Stream 的
groupingBy根据type进行分组。- 意义:这意味着你可以定义多棵树(例如:一棵树负责“初始化检查”,另一棵树负责“订单校验”),互不干扰。
- 构建:对每一组组件调用
build()方法生成树根。
2. build & buildTree (核心算法)
这是最复杂的逻辑部分。
- 排序存储:使用
TreeMap<Integer, Map...>。TreeMap会根据 key(tier 层级)自动排序,保证处理时是从上层(Tier 1)到下层(Tier 2...)有序进行的。 - 父子关联:
- 代码逻辑是 “双指针”扫描:
currentTier(父层)和currentTier + 1(子层)。 - 遍历子层组件,通过
child.executeParentOrder()找到它在父层对应的组件。 - 建立引用:
parent.add(child)。
- 代码逻辑是 “双指针”扫描:
- 寻找根节点:
- 通常 Min Tier (最小层级) 的节点就是根。
- 代码通过过滤
executeParentOrder() == null || 0作为根节点的标识。
3. execute (统一入口)
- 逻辑:外部不需要关心树内部多复杂,只需要传入
type(树的标识)和param(上下文参数)。 - 触发:容器会找到对应的 Root 节点,调用
allExecute,这通常会触发整棵树的递归执行。
三、 面试话术 (建议)
面试官:请介绍一下你项目中的这个 CompositeContainer 是怎么设计的?
你的回答:
“这个类是我设计的一个基于 Spring 上下文的组合模式容器,主要用于解决复杂业务流程的编排和解耦问题。可以从以下四个方面来理解:
1. 设计背景 我们在系统初始化(或复杂链路处理)时,有许多步骤是层级依赖的,比如先做基础检查,通过后再做并行的数据加载。如果全部写在 Service 里,代码会变成巨大的 if-else 嵌套,难以维护。所有的 Bean 虽然都在 Spring 里,但是它们之间缺乏逻辑上的‘父子结构’。
2. 核心方案 我利用了标准的设计模式——组合模式。我定义了一个
CompositeContainer,它充当管理者的角色。
- 自动化发现:在 Spring 启动时,容器会扫描所有实现了
AbstractComposite接口的 Bean。- 动态组装树:即代码中的
init和build方法。即使 Spring 里的 Bean 是扁平的,我通过设计元数据(Tier层级、Order序号、ParentOrder父级序号),编写了一个递归算法,将扁平的 Bean 列表自动‘挂载’成一棵或多棵执行树。- 分组隔离:通过
type字段,我们可以同时管理多套业务逻辑(比如启动树、校验树),互不冲突。3. 技术细节亮点 在构建树的
buildTree方法中,我使用了TreeMap来按层级自动排序,确保我们总是先处理父层,再处理子层。算法逻辑是遍历下一层的节点,根据ParentOrder主动去上一层寻找‘父亲’并完成挂载。4. 带来的收益
- 高扩展性:开发人员想加一个新逻辑,只需要写一个新的 Bean,指定好它是谁的子节点,无需修改核心代码。
- 逻辑清晰:业务流程可视化为树形结构,执行时从根节点一键启动,代码非常干净。”
四、 可能的追问及应对
Q: 如果出现循环依赖怎么办(A的父亲是B,B的父亲是A)?
A: 目前的设计是基于
Tier(层级)严格分层的。算法是处理Tier N和Tier N+1,子节点只能找上一层的节点做父亲,无法找同层或下层的节点,从结构上物理杜绝了循环依赖。
Q: 这个 param 是泛型 T,实际使用传什么?
A: 通常会传一个上下文对象(Context),里面包含所有环节共享的数据。因为树形执行时,局部变量无法跨节点传递,Context 是最佳实践。
Q: 如果某个子节点报错了,整个流程会怎样?
A: 这取决于
AbstractComposite.allExecute的实现。通常采用**“快速失败”**机制,抛出异常中断整个execute流程;或者在 Context 中记录错误并继续。在本框架中(根据DaMaiFrameException的引用),看起来是会抛异常中断的。