preface

After reading crossoverJie’s article “Optimizing too much IF else code with strategic mode”, I was inspired to simplify too much if else code with strategic mode. In the article, it mentioned that self-registration of processor can be realized by scanning. Here I introduce the implementation method in Spring Boot framework.

demand

Here is a fictitious business requirement to make it easy to understand. Suppose you have an order system in which one of the functions is to make different processing based on different types of orders.

Order Entity:

public class OrderDTO {
    
    private String code;

    private BigDecimal price;

    /** * Order type * 1: ordinary order; * 2: Group purchase order; * 3: Promotional orders; * /
    private String type;
    
    / /... Omit get/set...
    
}
Copy the code

The service interface:

public interface IOrderService {

    /** * According to the different types of orders to make different processing **@paramDto order entity *@returnFor simplicity, return the string */
    String handle(OrderDTO dto);

}
Copy the code

Traditional implementation

Write a bunch of if else based on the order type:

public class OrderServiceImpl implements IOrderService {

    @Override
    public String handle(OrderDTO dto) {
        String type = dto.getType();
        if ("1".equals(type)) {
            return "Handle general orders";
        } else if ("2".equals(type)) {
            return "Deal with group purchase orders";
        } else if ("3".equals(type)) {
            return "Processing promotional orders";
        }
        return null; }}Copy the code

Policy pattern implementation

With the policy pattern, only two lines are needed to implement the business logic:

@Service
public class OrderServiceV2Impl implements IOrderService {

    @Autowired
    private HandlerContext handlerContext;

    @Override
    public String handle(OrderDTO dto) {
        AbstractHandler handler = handlerContext.getInstance(dto.getType());
        returnhandler.handle(dto); }}Copy the code

You can see that HandlerContext is injected into the above method, which is a processor context that holds different business handlers, as explained below. We took an abstract processor, AbstractHandler, and invoked its methods to implement the business logic.

You can now see that most of our business logic is implemented in processors, so there should be as many processors as there are order types. Later, when the requirements changed and the order type was added, all we needed to do was add the corresponding processor, and the OrderServiceV2Impl was left unchanged.

Let’s look at the business processor first:

@Component
@HandlerType("1")
public class NormalHandler extends AbstractHandler {

    @Override
    public String handle(OrderDTO dto) {
        return "Handle general orders"; }}Copy the code
@Component
@HandlerType("2")
public class GroupHandler extends AbstractHandler {

    @Override
    public String handle(OrderDTO dto) {
        return "Deal with group purchase orders"; }}Copy the code
@Component
@HandlerType("3")
public class PromotionHandler extends AbstractHandler {

    @Override
    public String handle(OrderDTO dto) {
        return "Processing promotional orders"; }}Copy the code

First, each processor must be added to the Spring container, hence the @Component annotation. Second, we need to add a custom annotation @HandlerType that identifies the order type of the processor. Finally, we need to inherit AbstractHandler to implement our own business logic.

Custom annotation @handlerType:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HandlerType {

    String value(a);

}
Copy the code

Abstract processor AbstractHandler:

public abstract class AbstractHandler {
    abstract public String handle(OrderDTO dto);
}
Copy the code

It’s easy to customize annotations and abstract handlers, so how do you register your handlers with the Spring container? The specific ideas are as follows:

  • Scan the specified packet@HandlerTypeIn the class;
  • Treat the type value in the annotation askey, the corresponding class acts asvalueAnd stored in theMap;
  • On the surface of the abovemapClass as a constructor parameterHandlerContextTo register it tospringThe container;

We encapsulate the core functions in the HandlerProcessor class to complete the above functions.

HandlerProcessor:

@Component
@SuppressWarnings("unchecked")
public class HandlerProcessor implements BeanFactoryPostProcessor {

    private static final String HANDLER_PACKAGE = "com.cipher.handler_demo.handler.biz";

    / * * * scan@HandlerTypeInitialize the HandlerContext and register it with the Spring container * *@paramBeanFactory Bean factory *@see HandlerType
     * @see HandlerContext
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, Class> handlerMap = Maps.newHashMapWithExpectedSize(3);
        ClassScaner.scan(HANDLER_PACKAGE, HandlerType.class).forEach(clazz -> {
            // Get the type value in the annotation
            String type = clazz.getAnnotation(HandlerType.class).value();
            // Store the type value in the annotation as key and the corresponding class as value in the Map
            handlerMap.put(type, clazz);
        });
        // Initialize the HandlerContext and register it with the Spring container
        HandlerContext context = newHandlerContext(handlerMap); beanFactory.registerSingleton(HandlerContext.class.getName(), context); }}Copy the code

ClassScaner: scans the source code of utility classes

HandlerProcessor needs to implement BeanFactoryPostProcessor, which registers custom beans into the container before Spring processes them.

Now that the core work is done, let’s see how HandlerContext gets its handler:

HandlerContext:

public class HandlerContext {

    private Map<String, Class> handlerMap;

    public HandlerContext(Map<String, Class> handlerMap) {
        this.handlerMap = handlerMap;
    }

    public AbstractHandler getInstance(String type) {
        Class clazz = handlerMap.get(type);
        if (clazz == null) {
            throw new IllegalArgumentException("not found handler for type: " + type);
        }
        return(AbstractHandler) BeanTool.getBean(clazz); }}Copy the code

BeanTool: Gets the bean utility class

The #getInstance method gets the corresponding class based on the type, and then gets the bean registered in Spring based on the class type.

Finally, note that HandlerProcessor and BeanTool must be scanned or explicitly registered via @bean to be effective at project startup.

conclusion

The policy pattern simplifies complex if and else code for easy maintenance, while custom annotations and self-registration make it easier to respond to changing requirements. This article is just a general idea, but there are many details that can be changed flexibly, such as using enumerated types or static constants as the types of orders. I’m sure you can think of more and better methods.

See the full example code: handler_demo

Read the original article: www.ciphermagic.cn

Hope to help you 🙂