- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
导读
Context Object(上下文对象)模式是 Core J2EE 经典设计模式之一,其核心思想是把与具体协议/环境绑定的上下文数据(如请求、会话、安全凭证)抽取出来,封装进一个与协议无关的普通对象中,再跨层共享传递。本文以 java-design-patterns 仓库中context-object模块(含阿拉伯语文档 localization/ar/context-object/README.md 与英文版 context-object/README.md)为主线,结合完整源码与测试,讲解该模式的意图、三层传递实现、工厂创建方式、适用场景与权衡,并给出可运行验证方式。读完你将掌握如何用 Context Object 避免在多层系统中逐参数传递、如何保持层间解耦,以及它在 SpringApplicationContext、JAX-RSSecurityContext等真实框架中的对应形态。
模式概览:名称、别名与意图
- 名称 / 分类:Context Object(上下文对象),属于创建型(Creational)到行为型(Behavioral)之间用于组织上下文数据的设计模式;在英文版 README 中归类为 Behavioral,并打有
Context、Decoupling、Encapsulation、Session management等标签。 - 别名:Context、Context Encapsulation、Context Holder、Encapsulate Context(上下文封装 / 上下文持有者 / 封装上下文),阿拉伯语文档中对应“السياق、تجميع السياق”(上下文、上下文聚合)。
- 意图:将状态与行为封装进与具体协议无关的上下文对象中,使应用组件与环境的复杂性解耦,并以统一方式在整个应用中共享上下文状态。
来自 Core J2EE Patterns 的经典表述(阿拉伯语文档第 31-33 行亦引用):
使用 Context Object,以与协议无关的方式封装状态,并在整个应用中共享。
通俗解释(文档“In plain words”):
创建一个对象来存储上下文数据,然后把这个对象传递到任何需要它的地方。
模式动机:为什么需要上下文对象
阿拉伯语文档给出的现实世界例子是一个多分层应用:系统包含 A、B、C 等多个带标签的层,每层都要从同一个上下文中提取特定信息供后续使用。如果逐条把信息作为参数单独传递,将非常低效;此时需要一个既能存储又能高效传递信息的手段。
英文版 README 用机场登机流程做类比:乘客从值机、安检、登机到客服,多个服务都需要共享乘客身份、航班、偏好等数据。与其让每个服务单独索取并逐项传递,不如维护一个统一的“乘客上下文对象”,各服务按需读取或更新。这与软件中的 Context Object 模式完全同构——既保证了信息一致性,又避免了服务间的紧耦合。
项目中的实现剖析:源码级拆解
本模块源码位于 context-object/src/main/java/com/iluwatar/context/object/,共 6 个类,职责清晰:
| 类 | 角色 | 文件路径 |
|---|---|---|
ServiceContext | 协议无关的上下文载体(POJO) | ServiceContext.java |
ServiceContextFactory | 创建上下文的静态工厂 | ServiceContextFactory.java |
LayerA | 第一层:创建上下文并写入 account 信息 | LayerA.java |
LayerB | 第二层:从 LayerA 取上下文,追加 session 信息 | LayerB.java |
LayerC | 第三层:从 LayerB 取上下文,追加 search 信息 | LayerC.java |
App | 演示入口,串联三层调用 | App.java |
1. 定义上下文载体:ServiceContext
ServiceContext只持有三个业务相关字段,不带任何协议/框架依赖:
@Getter @Setter public class ServiceContext { String accountService; String sessionService; String searchService; }注意:源码中的字段命名是驼峰式的accountService、sessionService、searchService,并通过 Lombok 的@Getter/@Setter自动生成 getter/setter(getAccountService()、setSessionService()等);而阿拉伯语文档与 UML 图中使用了大写风格的ACCOUNT_SERVICE等命名。两处命名风格不同,但结构等价——字段名只是标识符,模式语义完全一致。
2. 统一创建入口:ServiceContextFactory
为保证应用各部分以一致方式获得上下文实例,仓库提供了静态工厂:
public class ServiceContextFactory { public static ServiceContext createContext() { return new ServiceContext(); } }工厂的 Javadoc 明确其职责是“创建可在各层间传递的上下文对象的接口”(见 ServiceContextFactory.java)。把new ServiceContext()收敛到工厂方法,便于日后扩展——例如按环境注入不同实现、加入默认值或池化,调用方无需改动。
3. 三层传递:LayerA → LayerB → LayerC
三个层类通过构造函数接收上一层并取出其持有的同一上下文对象,实现“引用传递、增量填充”:
@Getter public class LayerA { private ServiceContext context; public LayerA() { context = ServiceContextFactory.createContext(); } public void addAccountInfo(String accountService) { context.setAccountService(accountService); } } @Getter public class LayerB { private ServiceContext context; public LayerB(LayerA layerA) { this.context = layerA.getContext(); } public void addSessionInfo(String sessionService) { context.setSessionService(sessionService); } } @Getter public class LayerC { public ServiceContext context; public LayerC(LayerB layerB) { this.context = layerB.getContext(); } public void addSearchInfo(String searchService) { context.setSearchService(searchService); } }关键点:
- 创建发生在第一层:
LayerA构造时通过ServiceContextFactory.createContext()首次实例化上下文。 - 后续层只做传递与增量写入:
LayerB、LayerC分别从上一层的getContext()拿到同一个上下文实例,再分别补充 session、search 信息,而无需感知上下文内部结构。 - UML 佐证:context-object/etc/context-object.urm.puml 中可以看到
LayerB ..|> LayerA、LayerC ..|> LayerB的依赖关系,以及ServiceContext --> LayerA/B/C的引用关系和ServiceContextFactory ..|> ServiceContext的创建关系,与代码完全对应。
4. 运行演示:App 入口
App.java 串联整个流程:
@Slf4j public class App { private static final String SERVICE = "SERVICE"; public static void main(String[] args) { // 第一层:创建上下文并写入 account 信息 var layerA = new LayerA(); layerA.addAccountInfo(SERVICE); logContext(layerA.getContext()); // 第二层:沿用第一层的上下文,追加 session 信息 var layerB = new LayerB(layerA); layerB.addSessionInfo(SERVICE); logContext(layerB.getContext()); // 第三层:沿用前两层的上下文,追加 search 信息 var layerC = new LayerC(layerB); layerC.addSearchInfo(SERVICE); logContext(layerC.getContext()); } private static void logContext(ServiceContext context) { LOGGER.info("Context = {}", context); } }阿拉伯语文档给出的输出示意(字段依次为 account / session / search):
Context = SERVICE null null Context = SERVICE SERVICE null Context = SERVICE SERVICE SERVICE可以看到上下文对象在各层“接力”中被不断充实——这正是“存储 + 共享”模式的直观体现。英文版 README 记录的真实运行日志打印的是对象引用(如ServiceContext@5577140b),三次输出指向同一对象实例,同样印证了“同一上下文跨层共享”的语义。
测试验证:模式语义的自动化保障
本模块测试位于 context-object/src/test/java/com/iluwatar/contect/object/(注意测试包名contect为仓库既有拼写),核心断言来自 ServiceContextTest.java:
testSameContextPassedBetweenLayers:使用assertSame断言 LayerA、LayerB、LayerC 拿到的上下文是同一个对象实例,直接验证“引用共享”而非拷贝复制;testScopedDataPassedBetweenLayers:LayerA 写入 account、LayerC 写入 search 后,account 与 search 均等于SERVICE,而 session 仍为null,验证各层写入的字段作用域互不污染、上下文增量填充正确;testLayerContexts:按层逐步断言——LayerA 只有 account 有值、LayerB 增加 session 有值、LayerC 三个字段全部有值,完整覆盖三层递增填充的时序。
此外 AppTest.java 用assertDoesNotThrow保证App.main可无异常执行。context-object/pom.xml中声明了 JUnit Jupiter 与 SLF4J/Logback 依赖(context-object/pom.xml),说明该项目使用 JUnit 5 进行单元测试、SLF4J 输出日志。
何时使用 Context Object 模式
结合阿拉伯语文档(“适用性”章节)与英文版 README 的“When to Use”,适用场景包括:
- 跨层共享信息:在系统不同层/组件之间共享上下文数据,避免把环境相关信息散落进业务逻辑;
- 协议无关的数据分离:把数据从特定协议(HTTP、Servlet、JAX-RS 等)绑定类中解耦出来,存入独立于底层协议的对象;
- 按需暴露接口:只向调用方暴露与当前上下文相关的 API,隐藏不必要的环境细节;
- Web 应用请求封装:封装请求相关的信息(请求/响应对象、会话、用户偏好、安全凭证),使其在整个应用中易于访问,无需在函数间显式逐参数传递;
- 分布式系统:封装任务上下文、用户偏好或安全凭证,便于跨组件、跨服务传播。
真实世界对应形态
文档列出的知名框架示例(均为上下文对象的工业级实例):
- Spring:
ApplicationContext——作为整个 Spring 容器的中央上下文对象,封装 Bean 定义、环境信息并对外共享; - Oracle Java EE:
javax.ws.rs.core.SecurityContext——封装与安全相关的上下文信息; - Oracle Java EE:
javax.servlet.ServletContext——封装 Servlet 容器级的共享上下文。
(以上链接原文分别指向 Spring 官方 Javadoc 与 Oracle Java EE API 文档。)
优点与权衡
优点(Benefits):
- 解耦(Decoupling):组件与服务不再依赖执行环境的特定细节,模块化与可维护性提升;
- 集中化(Centralization):上下文信息集中在一处,更易管理、访问与调试;
- 灵活(Flexibility):上下文管理可随环境或需求变化而动态调整。
权衡(Trade-offs):
- 开销(Overhead):若实现不当,上下文对象会引入额外的性能开销;
- 膨胀风险(Complexity):若上下文对象设计不佳,容易变成臃肿复杂、难以维护的“上帝对象”。因此实践中建议字段按领域收敛、避免把所有信息都塞进同一个上下文。
与相关模式的关系
- Singleton(单例):Context Object 常以 Singleton 方式实现,以保证全局唯一的访问点(本项目三层共享同一实例,语义上接近单实例共享);
- Strategy(策略):Context Object 可组合策略,根据所封装上下文调整自身行为;
- Decorator(装饰器):可为 Context Object 动态增加职责。
如何运行与验证本模块
仓库根 pom.xml 已将context-object声明为子模块(第 106 行)。在仓库根目录下执行 Maven 即可构建并运行测试:
./mvnw -pl context-object test-pl context-object:仅构建该模块;test:运行 JUnit 5 测试,覆盖“同一实例跨层传递”与“三层字段递增填充”等断言;- 如需观察演示输出,可运行
App的主方法(com.iluwatar.context.object.App),其日志依赖已在 context-object/pom.xml 中配置 SLF4J + Logback。
小结
Context Object 模式用“一个协议无关的载体对象 + 一次创建、跨层引用传递”的方式,优雅地解决了多层系统中上下文数据共享与协议解耦两大问题。java-design-patterns 仓库的context-object模块通过ServiceContext、ServiceContextFactory与 LayerA/B/C 三层,给出了最小可运行且附带完整测试的参考实现;从 SpringApplicationContext到 Servlet/Security Context,都能看到该模式在企业级框架中的实战影子。若你的应用正面临“层层传参、环境逻辑侵入业务”的困境,不妨按此模式提炼一个自己的上下文对象。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Context Object 模式在 Java 设计模式中的实践:用协议无关的上下文对象解耦多层应用
Context Object 模式在 Java 设计模式中的实践:用协议无关的上下文对象解耦多层应用 Context Object(上下文对象)是 Core J
示例工程教程Java 设计模式实战:Balking 模式在 java-design-patterns 中的实现与并发应用
Java 设计模式实战:Balking 模式在 java design patterns 中的实现与并发应用 Balking(退缩/迟疑)模式是 Java 并发
示例工程教程Java 数据传递对象(DTO)模式实战:用 Data Transfer Object 简化分层子系统间的数据交换
Java 数据传递对象(DTO)模式实战:用 Data Transfer Object 简化分层子系统间的数据交换 数据传递对象(DTO)是一种结构型设计模式,
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考