Implementing component enhancements for the Chain of Responsibility pattern using custom annotations and @Aspect

Chain of responsibility model

Chain of Responsibility pattern is a behavioral design pattern that decouples the sender and receiver of requests so that requests can be organized and processed flexibly. It works by passing and processing requests along a chain of processors until a processor is able to handle the request or the end of the chain.

The main functions of this mode are:

  1. Decoupling request sender and receiver: The chain of responsibility mode decouples the sender and receiver. The sender does not need to know which receiver is handling the request, and the receiver does not need to know who the sender of the request is, thereby improving system efficiency. Flexibility and maintainability.
  2. Dynamically organize and process requests: The Chain of Responsibility model can dynamically organize and process requests, flexibly adjust the order and number of processors on the link according to actual conditions, and implement different business logic and processing strategies.
  3. Avoid hard coding of requests: The Chain of Responsibility pattern handles requests through configuration and links, avoiding hard coding of requests and making the system easier to expand and maintain.

Application scenario:

  1. The processing of the request involves multiple links or multiple objects: When a request needs to be processed by multiple processing links or multiple objects, the chain of responsibility pattern can be used. For example, the processing of requests needs to be processed by multiple processors such as authentication, logging, and caching.
  2. Dynamic selection of processors: When the selection and order of processors need to be dynamically adjusted according to the actual situation, the Chain of Responsibility pattern can be used. For example, dynamically select a processor based on the type or priority of the request.
  3. Scenarios where requests need to be filtered or intercepted: When requests need to be filtered, intercepted, or processed according to conditions, the Chain of Responsibility pattern can be used. For example, operations such as permission verification, security check, and data verification are performed on the request.
  4. Reduce coupling and improve system flexibility and maintainability: When it is necessary to reduce the coupling between sender and receiver and improve system flexibility and maintainability, the chain of responsibility pattern can be considered.

In short, the Chain of Responsibility pattern is suitable for scenarios where the processing request chain is long, the processors are loosely coupled, and requests need to be dynamically organized and processed. It can help us achieve a more flexible, scalable and maintainable system design.

Practice case

Combined with custom annotations and @Aspect, we can enhance the component to have the function of the chain of responsibility mode.

First, we need to define a custom annotation to identify the method or class that needs to apply the Chain of Responsibility pattern. The following annotations can be defined:

java copy code package com.example.demo.design.chain;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy. RUNTIME)
@Target(ElementType. METHOD)
public @interface MyChain {}

Next, we use the @Aspect annotation to create an aspect class and implement the logic of the chain of responsibility pattern in the aspect class. Aspect classes can be defined as follows

ini copy code package com.example.demo.design.chain;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;

@Aspect
@Component
public class ChainHandlerAspect implements InitializingBean {
    @Autowired
    // Inject all chain-of-responsibility handlers that implement the ChainHandler interface
    private List<ChainHandler> chainHandlers;

    // The head node of the chain of responsibility
    private ChainHandler chainHandler;

    @Around("@annotation(com.example.demo.design.chain.MyChain)")
    public Object checkParam(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs(); // Get all parameters of the method
        MethodSignature signature = (MethodSignature) pjp. getSignature();
        Method method = signature. getMethod();
        Class<?>[] parameterTypes = method.getParameterTypes(); // Get the parameter type list of the method

        for (int i = 0; i < parameterTypes. length; i ++ ) {
            if (parameterTypes[i].getName().equals("java.lang.String")) {
                chainHandler. handle(new Request((String) args[0]));
            }
        }
        return pjp. proceed();
    }

    /**
     * Build the processor chain
     */
    private ChainHandler buildHandlerChain() {
        ChainHandler headChainHandler = null;
        ChainHandler currentChainHandler = null;
        for (ChainHandler chainHandler : chainHandlers) {
            if (headChainHandler == null) {
                headChainHandler = chainHandler;
                currentChainHandler = headChainHandler;
            } else {
                currentChainHandler.setNextHandler(chainHandler);
                currentChainHandler = chainHandler;
            }
        }
        return headChainHandler;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // build chain of responsibility
        chainHandler = this. buildHandlerChain();
    }
}

Responsibility chain related components

processor interface

java copy code package com.example.demo.design.chain;

public interface ChainHandler {
    void handle(Request request);

    void setNextHandler(ChainHandler handler);
}

Processor implementation class

java copy code package com.example.demo.design.chain;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class FirstHandler implements ChainHandler {
    private ChainHandler nextHandler;

    @Override
    public void handle(Request request) {
        // process the request
        System.out.println("FirstHandler handling request " + request);

        // Pass the request to the next handler
        if (nextHandler != null) {
            nextHandler. handle(request);
        }
    }

    @Override
    public void setNextHandler(ChainHandler handler) {
        this. nextHandler = handler;
    }
}
java copy code package com.example.demo.design.chain;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(2)
public class SecondHandler implements ChainHandler {
    private ChainHandler nextHandler;

    @Override
    public void handle(Request request) {
        // process the request
        System.out.println("SecondHandler handling request " + request);

        // Pass the request to the next handler
        if (nextHandler != null) {
            nextHandler. handle(request);
        }
    }

    @Override
    public void setNextHandler(ChainHandler handler) {
        this. nextHandler = handler;
    }
}
java copy code package com.example.demo.design.chain;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(3)
public class ThirdHandler implements ChainHandler {
    private ChainHandler nextHandler;

    @Override
    public void handle(Request request) {
        // process the request
        System.out.println("ThirdHandler handling request " + request);
    }

    @Override
    public void setNextHandler(ChainHandler handler) {
        this. nextHandler = handler;
    }
}

The above codes are similar, and the execution order of the processor is specified through the @Order annotation. The smaller the number, the higher the priority.

The setNextHandler method is used to set the next handler, receives a ChainHandler object as a parameter, and saves it in the nextHandler field.

Request class

kotlin copy code package com.example.demo.design.chain;

public class Request {
    private String data;

    public Request(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Request{" +
                "data='" + data + ''' +
                '}';
    }
}

business code component

kotlin copy code package com.example.demo.design.chain;

import org.springframework.stereotype.Component;

@Component
public class BizComponent {
    @MyChain
    public void process(String data) {
        System.out.println(data);
    }
}

The @MyChain annotation is used to mark methods or classes that need to apply the Chain of Responsibility pattern.

Startup class

typescript copy code package com.example.demo.design.chain;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(com.example.demo.design.chain.Application.class, args);
    }
}

The @EnableAspectJAutoProxy annotation is used to enable the AspectJ automatic proxy function, enabling Spring to recognize and apply aspects.

java copy code package com.example.demo.design.chain;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
@Data
public class ComponentRunner implements CommandLineRunner {

    @Autowired
    private BizComponent bizComponent;

    @Override
    public void run(String... args) throws Exception {
        // Execute a method (with Chain annotation)
        bizComponent. process("Hello world!");
    }
}

The execution logic of this component class is automatically executed after the application is started, and appropriate business logic and processing flow can be written in the run method according to actual needs.

The main purpose is to simulate the initiation of the request, which can be accessed by the Controller.

**Execution effect**

We can see that before executing bizComponent.process(“Hello world!”), we have been enhanced by the annotation @MyChain, so it will be pre-processed through the chain of responsibility.

Summary

In this way, we can flexibly expand and customize the processing logic of the chain of responsibility, decouple the chain of responsibility from business components through annotations and AOP, and realize component enhancement and reuse.

The application scenarios of the chain of responsibility mode are very wide. For example, in web development, the chain of responsibility mode can be used to handle operations such as request interception, verification, and logging; in the workflow engine, the chain of responsibility mode can be used to implement task processing and Flow; In an event-driven system, the Chain of Responsibility pattern can be used to handle the triggering and delivery of events, etc.

By combining custom annotations and Spring’s AOP function, we can more easily apply the chain of responsibility pattern and gain higher scalability and flexibility in the development process. This method of component enhancement makes the implementation of the chain of responsibility pattern more concise and more readable, and also improves the maintainability and testability of the code.

All in all, using custom annotations and @Aspect to implement component enhancement of the chain of responsibility pattern is a powerful programming technique that can effectively decouple the logic of the chain of responsibility from business components and provide a flexible and extensible way to Handles processing of requests and logic chains. This method of combining annotations and AOP makes the application of the chain of responsibility model simpler and more elegant, and brings convenience and benefits to our development work.