Hook Starter
受众:开发者 摘要:方法级 Hook 基础组件。通过
@Hook.Execute或Hook.execute(...)在业务方法外层执行一组HookHandler,适合日志、审计、指标、鉴权、幂等、风控和平台扩展点。
何时使用
| 场景 | 建议 |
|---|---|
| 方法入口需要统一日志、审计、指标或 Trace | 用 Hook |
| 关键写操作需要同步鉴权、幂等或风控校验 | 用 Hook |
| 希望业务方法外层可插拔扩展,但不想把扩展散落在 Service | 用 Hook |
| 不确定该用 Hook、Pipeline 还是 Chain | 先看 钩子 / 管道 / 责任链 专题 |
Hook 的定位是:方法级同步技术关注点和扩展口。它包住业务方法,但不接管业务状态机、事务边界、最终一致性或跨系统恢复。钩子 / 管道 / 责任链的选型、注解糖和组合使用见 钩子 / 管道 / 责任链 专题。
Starter 入口与源码骨架
Hook starter 采用 Facade / Annotation -> Executor -> Registry -> Handler 的骨架。
| 层 | 类 | 职责 |
|---|---|---|
| Facade | Hook | 手动调用入口:Hook.execute(point, param, target) |
| Annotation | @Hook.Execute / HookAspect | 注解式入口,把目标方法交给 Hook executor 执行 |
| Executor | DefaultHookExecutor | 校验 point / context、处理 point 启停、执行 before / target / after / onError、生成执行快照并通知 listener |
| Registry | DefaultHookHandlerRegistry | 按 HookHandler.getPoint() 绑定 Handler,应用 point / handler 启停配置并排序 |
| Handler | HookHandler<T, R> | 单个外层扩展动作,支持 before / after / onError |
| Context | HookContext<T, R> | 继承共享 ExecutionContext,暴露 point、traceId、入参、方法、结果、异常、ExecutionKey<T> 和中止状态 |
| Listener | HookExecutionListener | 接收最终执行快照,用于审计、指标、日志和运维观测 |
关键机制顺序:
@Hook.Execute("mall.order.create")或Hook.execute("mall.order.create", cmd, target)触发 Hook。DefaultHookExecutor判断 point 是否启用。DefaultHookHandlerRegistry查找同 point 的 Handler,按配置 order、HookHandler.order()、Ordered、@Order排序。- Handler 的
supports(context)返回false时跳过并记录 trace。 before按正序执行;失败会调用已执行 Handler 的onError,并阻断目标方法。- 目标方法执行成功后,
after按逆序执行;after可替换返回值。 after/onError/ listener 失败不会反向破坏已经成功的目标方法结果,只记录失败快照。
快速开始
spring:
open:
hook:
enabled: true
trace-enabled: true注解方式
业务方法上加 @Hook.Execute:
public class OrderService {
@Hook.Execute("mall.order.create")
public Order createOrder(CreateOrderCommand command) {
// 业务主流程仍写在这里:校验库存、创建订单、扣库存、创建支付单等。
return doCreate(command);
}
}@Hook.Execute 有三点注意:
- 自调用不生效:注解走 Spring AOP 代理,必须从其他 Spring Bean 调用该方法;同一个类内部
this.createOrder(...)自调用不会触发注解(与@Pipeline.Execute一致)。需要类内调用时改用Hook.execute(...)手动入口。 - parameter 默认取第一个参数:handler 的
getParameter()默认是方法第一个参数。若业务对象不在第一个位置(如create(Long tenantId, OrderCmd cmd)),用@Hook.Execute(value = "...", paramArg = 1)指定,避免 handler 收到错误类型。paramArg会在启动期校验,必须指向真实参数;负数、越界,或无参方法显式配置非零下标,都会直接失败。 - 启动期校验是常规业务 Bean 的 fail-fast 护栏:
@Hook.Execute(paramArg)会在 Spring 容器启动时扫描常规业务 Bean 并提前发现配置错误。极早创建的基础设施 Bean 可能早于 Hook 校验器实例化,此时启动期校验会退化为运行时校验;真正执行方法时仍会由 Hook 切面兜底抛出HookException。
添加一个 Handler:
@Component
public class OrderCreateAuditHookHandler implements HookHandler<CreateOrderCommand, Order> {
@Override
public String getPoint() {
return "mall.order.create";
}
@Override
public String getCode() {
return "audit";
}
@Override
public int order() {
return 100;
}
@Override
public void before(HookContext<CreateOrderCommand, Order> context) {
auditService.recordStart(context.getParameter().userId());
}
@Override
public Order after(HookContext<CreateOrderCommand, Order> context, Order result) {
auditService.recordSuccess(result.id());
return result;
}
@Override
public void onError(HookContext<CreateOrderCommand, Order> context, Throwable error) {
auditService.recordFailure(context.getTraceId(), error.getMessage());
}
}手动方式
不方便使用注解时,直接用 Hook.execute(...):
Order order = Hook.execute(
"mall.order.create",
command,
() -> doCreate(command));Hook.execute 适合在已有 Service 中手动包住一段动作;@Hook.Execute 适合固定方法入口。
Handler 语义
public interface HookHandler<T, R> {
String getPoint();
default String getCode();
default int order();
default boolean supports(HookContext<T, R> context);
default void before(HookContext<T, R> context);
default R after(HookContext<T, R> context, R result);
default void onError(HookContext<T, R> context, Throwable error);
}| 方法 | 说明 |
|---|---|
getPoint() | 精确 Hook point,例如 mall.order.create |
getCode() | 稳定 handler code,用于配置、trace 和运维展示 |
order() | 默认排序,未设置时等同 Spring Ordered.LOWEST_PRECEDENCE |
supports(context) | 运行期条件判断,返回 false 时跳过 |
before(context) | 目标方法前执行;抛异常会阻断目标方法 |
after(context, result) | 目标方法成功后逆序执行;返回值会成为新的结果 |
onError(context, error) | before、target 或 after 失败时执行;自身异常会被隔离 |
Context 可用数据
HookContext<T, R> 会把一次调用中的关键信息传给 Handler:
| 字段 / 方法 | 说明 |
|---|---|
getPoint() | 当前 Hook point |
getTraceId() | 本次 Hook 执行 trace id |
getParameter() | 手动调用时传入的参数;注解方式默认取第一个方法参数,可用 @Hook.Execute(paramArg = n) 指定;无参方法默认 paramArg = 0 时为 null |
getArgs() | 注解方式下的完整方法参数数组 |
getTarget() | 注解方式下的目标 Bean |
getMethod() | 注解方式下的目标方法 |
getResult() | 目标方法或 after 处理后的当前结果 |
getError() | 当前失败异常 |
set(key, value) / get(key) | Handler 间共享的轻量属性 |
set(ExecutionKey<T>, value) / get(ExecutionKey<T>) | 与 Pipeline / Chain 共用的类型安全属性 |
terminate(reason) / isTerminated() | 与 Pipeline / Chain 共用的同步中止语义 |
排序与启停
排序来源按优先级生效:
| 来源 | 示例 | 优先级 |
|---|---|---|
| 配置覆盖 | points.mall.order.create.handlers.audit.order: 10 | 最高 |
| Handler 默认排序 | HookHandler.order() | 中 |
| Spring 排序 | Ordered / @Order | 低 |
| 默认值 | Ordered.LOWEST_PRECEDENCE | 最低 |
配置示例:
spring:
open:
hook:
points:
mall.order.create:
enabled: true
handlers:
risk-check:
enabled: true
order: 10
audit:
enabled: true
order: 100如果多个扩展之间存在强业务依赖,不要靠复杂 Hook 顺序硬拼;应改用 Pipeline 表达主流程步骤。
失败策略
| 失败位置 | 默认行为 |
|---|---|
supports / before | 阻断目标方法,调用已执行 Handler 的 onError,最终向调用方抛出异常 |
| 目标方法 | 调用已执行 Handler 的 onError,向调用方抛出原始异常 |
after | 记录失败快照并调用 onError,但返回目标方法已有结果 |
onError | 记录 warning,不覆盖原始失败 |
HookExecutionListener | 记录 warning,不影响调用方 |
手动 Hook.execute(...) 遇到目标方法 checked exception 时会包装为 HookException;注解方式由 AOP 入口保留目标方法抛出的异常语义。
Listener 与 Trace
实现 HookExecutionListener 可以接收最终执行快照:
@Component
public class HookMetricsListener implements HookExecutionListener {
@Override
public void onExecution(HookExecutionResult<?, ?> result) {
meterRegistry.counter("spring_open_hook_total",
"point", result.getPoint(),
"status", result.getStatus().name()).increment();
}
}HookExecutionResult 包含 point、traceId、status、result、exception、elapsedMillis 和 HookHandlerTrace 列表。关闭 trace-enabled 后,快照仍会生成,但 handler trace 为空。
生产接入点
框架内置业务会在稳定方法入口外层接入 Hook。业务 Service 仍负责事务、落库、状态流转、事件、通知和回执,Hook 只承接同步扩展关注点。
| 模块 | Hook point | 上下文 | 边界 |
|---|---|---|---|
| Conversation | conversation.message.send | ConversationMessageSendExecutionContext 暴露会话 ID / 类型、发送人、客户端消息 ID、内容类型、附件数量和原始发送命令 | 可做审计、埋点、轻量同步或可解释阻断;不替代内容安全、消息落库、WebSocket 推送、站内通知或 ACK 回执 |
| Mall | mall.order.create | MallOrderCreateExecutionContext 暴露用户、收货地址、幂等键、优惠券码、购物车商品数量、购物车项 ID、地址快照和原始下单命令 | 可做下单审计、指标、营销埋点、内部提醒和轻量风控;阻断发生在库存锁定和订单落库前,不替代扣库存、锁券、支付单、虚拟交付或状态流转 |
| Mall | mall.order.paid | MallOrderPaidExecutionContext 暴露订单 ID / 编号 / 用户、支付前后状态、原因、traceId、支付 Provider / 网关 / 交易号、支付金额、币种、支付时间、订单和支付结果 | 可做支付成功后的审计、指标和轻量运营动作;在支付成功主链路副作用完成后执行,扩展失败只记录 warning,不回滚已确认支付结果,不替代 Payment Provider、账务一致性、优惠券核销、秒杀确认、虚拟交付或订单状态流转 |
| IAM | iam.user.register | UserRegisterExecutionContext 暴露注册用户 ID / 用户名 / 昵称 / 邮箱 / 手机号 / 头像、注册来源 / 设备、用户实体和原始注册命令 | 可做注册成功后的欢迎任务、审计增强、埋点、CRM 同步和默认画像补充;在用户落库和注册审计成功后执行,扩展失败只记录 warning,不回滚已注册用户,不替代用户名 / 身份唯一性、密码策略或用户落库主流程 |
| Blog | blog.article.save | Blog 文章保存命令 | 可做站点二开策略、租户限制、发布窗口检查和审计;不接管文章字段规范化、内容安全、状态处理或落库 |
| Forum | forum.topic.publish / forum.reply.publish | ForumPublishExecutionContext 暴露作者、板块、主题、父回复和原始命令 | 可做发布方法外层策略;内部内容安全、计数和事件仍由 Forum Service 控制 |
| Forum | forum.topic.report / forum.reply.report / forum.report.status.update | ForumReportExecutionContext 暴露举报对象、举报人、处理人和状态流转 | 可做举报治理审计或策略;不接管处理记录、Outbox 或状态事件 |
| CMS | cms.content.review.submit / cms.content.review.finish | CmsReviewExecutionContext 暴露内容、客户端、操作人、审核动作和备注 | 可做审核方法外层策略;不替代审批工作流、审核状态字段、操作日志或通知事件 |
| Open Platform | open-platform.app-key.create | OpenPlatformAppKeyCreateExecutionContext 暴露应用、创建命令、密钥 Key、脱敏密钥、hint、过期时间、轮换来源、状态原因、操作人和创建结果 | 可做 App Key 创建审计、运营埋点、开发者欢迎流或轻量同步;不暴露一次性明文 secret,创建后扩展失败只记录 warning,不回滚已创建密钥 |
| Runtime Content Safety | content-safety.review | ContentSafetyReviewExecutionContext 暴露检测日志、scene、业务对象、用户、风险等级、复核前后状态、复核人、动作、备注和权限检查标识 | 可做复核动作审计、指标、轻量二开策略或同步扩展;阻断发生在复核状态落库前,跨模块事后通知仍优先 Event / Notification 规则 |
不建议用 Hook 的场景
| 场景 | 原因 |
|---|---|
| 高频循环中的细粒度方法 | Handler 链和 trace 会带来额外成本 |
| 复杂主流程编排 | 应使用 Pipeline,使步骤、跳过、中断和补偿显式可见 |
| 条件决策责任链 | 应使用 Chain,使每条规则通过 proceed 明确是否继续传递 |
| 异步通知、跨模块广播 | 应使用 Event |
| 分布式事务 / 跨进程恢复 | Hook 不落库,不承担恢复语义 |
| 供应商多实现选择 | 应使用 Provider 扩展模型 |
验证命令
./mvnw -pl spring-open-starters/spring-open-starter-hook -am test -DskipITs -DskipFrontend
git diff --check