Implementing the Chain of Responsibility Pattern for Product Validation in Java
This article explains the Chain of Responsibility design pattern, demonstrates its application in a product creation workflow with concrete Java code, shows how to configure and assemble handlers dynamically using Spring, and discusses the pattern's advantages, drawbacks, and testing scenarios.
Introduction
The Chain of Responsibility pattern links multiple processing units into a chain, allowing a request to travel through each handler until one handles it or the chain ends. This article uses the pattern to validate product creation steps such as null checks, price checks, and stock checks.
Case Study 1: Multi‑Level Product Validation
Three validation steps are defined: create product, validate parameters, and save product. Each step is implemented as a separate handler (NullValueCheckHandler, PriceCheckHandler, StockCheckHandler) extending an abstract AbstractCheckHandler . The handlers are registered as Spring beans using @Component and injected via a @Resource Map<String, AbstractCheckHandler> handlerMap .
/**
* Product data object
*/
@Data
@Builder
public class ProductVO {
private Long skuId;
private String skuName;
private String imgPath;
private BigDecimal price;
private Integer stock;
} /**
* Abstract handler class
*/
@Component
public abstract class AbstractCheckHandler {
@Getter @Setter
protected AbstractCheckHandler nextHandler;
@Setter @Getter
protected ProductCheckHandlerConfig config;
public abstract Result handle(ProductVO param);
protected Result next(ProductVO param) {
if (Objects.isNull(nextHandler)) {
return Result.success();
}
return nextHandler.handle(param);
}
}The concrete handlers override handle() to perform specific checks and call super.next(param) when validation passes.
@Component
public class NullValueCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
System.out.println("空值校验 Handler 开始...");
if (super.getConfig().getDown()) {
System.out.println("空值校验 Handler 已降级,跳过...");
return super.next(param);
}
if (Objects.isNull(param) || Objects.isNull(param.getSkuId())
|| Objects.isNull(param.getPrice()) || Objects.isNull(param.getStock())) {
return Result.failure(ErrorCode.PARAM_NULL_ERROR);
}
System.out.println("空值校验 Handler 通过...");
return super.next(param);
}
} @Component
public class PriceCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
System.out.println("价格校验 Handler 开始...");
if (param.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
}
System.out.println("价格校验 Handler 通过...");
return super.next(param);
}
} @Component
public class StockCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
System.out.println("库存校验 Handler 开始...");
if (param.getStock() < 0) {
return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
}
System.out.println("库存校验 Handler 通过...");
return super.next(param);
}
}Dynamic Configuration
The handler chain is built from a JSON configuration stored in a configuration center. The JSON is parsed into ProductCheckHandlerConfig , which contains the bean name, a reference to the next handler, and a downgrade flag.
/**
* Handler configuration class
*/
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
private String handler; // bean name
private ProductCheckHandlerConfig next; // next node
private Boolean down = Boolean.FALSE; // downgrade flag
}The method getHandler(ProductCheckHandlerConfig config) recursively assembles the chain by retrieving beans from handlerMap , setting the config, and linking nextHandler .
private AbstractCheckHandler getHandler(ProductCheckHandlerConfig config) {
if (Objects.isNull(config) || StringUtils.isBlank(config.getHandler())) {
return null;
}
AbstractCheckHandler handler = handlerMap.get(config.getHandler());
if (Objects.isNull(handler)) {
return null;
}
handler.setConfig(config);
handler.setNextHandler(this.getHandler(config.getNext()));
return handler;
}Client Execution
The HandlerClient invokes the chain via executeChain(AbstractCheckHandler handler, ProductVO param) . If any handler returns a failure, the chain stops and the error is propagated; otherwise a successful result is returned.
public class HandlerClient {
public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
Result handlerResult = handler.handle(param);
if (!handlerResult.isSuccess()) {
System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult);
return handlerResult;
}
return Result.success();
}
}Testing Scenarios
Four test cases illustrate the chain behavior: missing SKU (null value), illegal price, illegal stock, and a fully valid product. Each case prints the corresponding error or success message.
Case Study 2: Expense Reimbursement Workflow
A second example applies the same pattern to a multi‑level approval workflow where the amount determines whether the request passes through third‑, second‑, and first‑level managers. The abstract AbstractFlowHandler defines an approve() method that concrete handlers override.
Advantages and Disadvantages
Images (omitted) summarize the benefits such as decoupling, dynamic extensibility, and the pitfalls like potential performance overhead and the need to guard against infinite recursion.
Source Code
The full source repository is available at https://github.com/rongtao7/MyNotes .
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.