Eliminate if‑else with Annotation‑Driven Strategy Pattern in Spring Boot
This article explains how to replace bulky if‑else statements with a clean, annotation‑based Strategy pattern in a Spring Boot application, covering interface definition, custom annotations, dynamic proxy handling, map injection, and the crucial hashCode/equals overrides for reliable handler lookup.
Strategy Pattern
Many articles claim that the Strategy pattern can replace cumbersome if‑else chains. The core idea is to define a common behavior (interface or abstract class) and provide different implementations for each strategy, letting the client select the appropriate implementation, often via a factory.
Annotation Implementation
This guide shows how to implement the Strategy pattern using custom annotations, with a concrete example of order processing.
First, define the Order entity:
@Data
public class Order {
/** Order source */
private String source;
/** Payment method */
private String payMethod;
/** Order code */
private String code;
/** Order amount */
private BigDecimal amount;
// ... other fields
}Assume the service currently contains a large if‑else block that handles orders differently based on their source (pc or mobile):
@Service
public class OrderService {
public void orderService(Order order) {
if (order.getSource().equals("pc")) {
// handle PC order
} else if (order.getSource().equals("mobile")) {
// handle mobile order
} else {
// other logic
}
}
}The goal is to remove this block.
1. Define a unified OrderHandler interface:
public interface OrderHandler {
void handle(Order order);
}2. Create a custom annotation to mark which handler processes which source:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface OrderHandlerType {
String source();
}3. Implement handlers for each source and annotate them:
@OrderHandlerType(source = "mobile")
public class MobileOrderHandler implements OrderHandler {
@Override
public void handle(Order order) {
System.out.println("处理移动端订单");
}
}
@OrderHandlerType(source = "pc")
public class PCOrderHandler implements OrderHandler {
@Override
public void handle(Order order) {
System.out.println("处理PC端订单");
}
}4. Inject all handlers into a map keyed by the annotation value:
@Service
public class OrderService {
private Map<String, OrderHandler> orderHandleMap;
@Autowired
public void setOrderHandleMap(List<OrderHandler> orderHandlers) {
orderHandleMap = orderHandlers.stream()
.collect(Collectors.toMap(
handler -> AnnotationUtils.findAnnotation(handler.getClass(), OrderHandlerType.class).source(),
v -> v,
(v1, v2) -> v1));
}
public void orderService(Order order) {
// ...pre‑processing
OrderHandler handler = orderHandleMap.get(order.getSource());
handler.handle(order);
// ...post‑processing
}
}Now the original if‑else disappears, replaced by a simple map lookup.
When business requirements evolve (e.g., handling both source and payment method), the map key can be extended. One approach is to make the key the annotation itself:
private Map<OrderHandlerType, OrderHandler> orderHandleMap;
@Autowired
public void setOrderHandleMap(List<OrderHandler> orderHandlers) {
orderHandleMap = orderHandlers.stream()
.collect(Collectors.toMap(
handler -> AnnotationUtils.findAnnotation(handler.getClass(), OrderHandlerType.class),
v -> v,
(v1, v2) -> v1));
}To retrieve a handler based on both source and payment method, implement a concrete class that mimics the annotation proxy and overrides hashCode and equals exactly as the JDK dynamic proxy does:
public class OrderHandlerTypeImpl implements OrderHandlerType {
private String source;
private String payMethod;
public OrderHandlerTypeImpl(String source, String payMethod) {
this.source = source;
this.payMethod = payMethod;
}
@Override
public String source() { return source; }
@Override
public String payMethod() { return payMethod; }
@Override
public Class<? extends Annotation> annotationType() { return OrderHandlerType.class; }
@Override
public int hashCode() {
int hash = 0;
hash += (127 * "source".hashCode()) ^ source.hashCode();
hash += (127 * "payMethod".hashCode()) ^ payMethod.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof OrderHandlerType)) return false;
OrderHandlerType other = (OrderHandlerType) obj;
return source.equals(other.source()) && payMethod.equals(other.payMethod());
}
}Using this implementation, the service can fetch the correct handler:
public void orderService(Order order) {
OrderHandlerType key = new OrderHandlerTypeImpl(order.getSource(), order.getPayMethod());
OrderHandler handler = orderHandleMap.get(key);
handler.handle(order);
}If hashCode and equals are not overridden, the map lookup fails with a NullPointerException because the proxy class used as the key differs from the custom implementation.
After adding the proper hashCode and equals methods, the handler is retrieved correctly, and the service logic remains unchanged regardless of future business variations.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
