Practical application of responsibility chain model

Time:2021-7-31

Responsibility chain model

The chain of responsibility pattern creates a chain of receiver objects for requests
This mode gives the type of request and decouples the sender and receiver of the requestThis type of design pattern belongs to behavioral pattern
In this pattern, each recipient usually contains a reference to another recipientIf an object cannot process the request, it passes the same request to the next recipient, and so on。 Reference W3C the description of the responsibility chain
In the actual development, we will also encounter that we need to provide an external interface, which may result in many non business processing methods (logging, permission verification, sensitive data cleaning…) but it is transparent to the business. Each processor is independent. There should be no coupling relationship before we can splice it at will.
Practical application of responsibility chain model
Code implementation dependencies
Practical application of responsibility chain model
First, we need to define an interface requestplugin, and all plug-ins need to implement this interface

/**
 *Plug in interface definition
 */
public interface RequestPlugin {

    /**
     *Routing
     */
    void interceptor(InterceptorChainWrapper routeChainWrapper);

    /**
     *Enable
     */
    boolean enable();
}

The functions handled by each plug-in are independent. However, there may be a sorting relationship between plug-ins. Who loads and executes first then who executes. For example, the request entry should retain the most original parameters, so the log plug-ins are generally placed first.
At this time, whether each plug-in needs to have a sort, so an annotation is defined here to sort by the value of order.

/**
 *Plug in annotation
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface PluginAnno {

    int order() default Ordered.HIGHEST_PRECEDENCE;

    String name();
}

Here, I have implemented three plug-ins to build a plug-in responsibility chain: log processing plug-in, parsing plug-in and permission verification plug-in;

/**
 *Log processing
 */
@PluginAnno(order = 1, name = "LogSavePlugin")
public class LogSavePlugin implements RequestPlugin {

    @Override
    public void interceptor(InterceptorChainWrapper routeChainWrapper) {
        System. Out. Println ("log plug-in: logsaveplugin");
        routeChainWrapper.interceptor();
    }

    @Override
    public boolean enable() {
        return true;
    }
}


/**
 *Analytical processing
 */
@PluginAnno(order = 2, name = "ParseHandlePlugin")
public class ParseHandlePlugin implements RequestPlugin {

    @Override
    public void interceptor(InterceptorChainWrapper routeChainWrapper) {
        System. Out. Println ("parse processing plug-in: parsehandleplugin");
        routeChainWrapper.interceptor();
    }

    @Override
    public boolean enable() {
        return false;
    }
}


/**
 *Permission verification
 */
@PluginAnno(order = 3, name = "AuthorCheckPlugin")
public class AuthorCheckPlugin implements RequestPlugin {

    @Override
    public void interceptor(InterceptorChainWrapper routeChainWrapper) {
        System.out.println ("permission plug-in: authorcheckplugin");
        routeChainWrapper.interceptor();
    }

    @Override
    public boolean enable() {
        return true;
    }
}

Inject three plug-ins and sort them.

/**
 *Configure plug-ins
 */
@Configuration
public class RequestPluginConfig {

    private List<RequestPlugin> requestPlugins;

    /**
     *Injection related processor
     *To processor
     */
    public RequestPluginConfig(List<RequestPlugin> requestPlugins) {
        this.requestPlugins = requestPlugins.stream().sorted(Comparator.comparingInt(o -> o.getClass().getAnnotation(PluginAnno.class).order())).collect(Collectors.toList());
    }

    public InterceptorChainWrapper createChainWrapper() {
        return new InterceptorChainWrapper(requestPlugins);
    }
}

Actually enable plug-in call classes and tests

/**
 *Plug in call chain
 */
public class InterceptorChainWrapper {
    private final AtomicInteger atomicInteger = new AtomicInteger(-1);

    private List<RequestPlugin> requestPlugins;

    public InterceptorChainWrapper(List<RequestPlugin> requestPlugins) {
        this.requestPlugins = requestPlugins;
    }

    /**
     *Actual trigger
     */
    public void interceptor() {
        if (atomicInteger.incrementAndGet() == requestPlugins.size()) {
            return;
        }
        RequestPlugin plugin = requestPlugins.get(atomicInteger.get());
        if (!plugin.enable()) {
            interceptor();
            return;
        }
        plugin.interceptor(this);
    }
}


/**
*Simulation call
**/
public class StudyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(StudyApplication.class, args);
        RequestPluginConfig requestPluginConfig = context.getBean(RequestPluginConfig.class);
        requestPluginConfig.createChainWrapper().interceptor();
    }

}

After looking at the above implementation scheme, let’s take a look at how the actual framework is implemented. Here, let’s take a look at the mybatis interceptor and see how it is different from our implementation. The interceptor of mybatis is to wrap the actual object as a proxy class through the interceptor.
Practical application of responsibility chain model
Define an interception interface as usual

/**
 * @author Clinton Begin
 */
public interface Interceptor {
  /**
  *Interception method
  */
  Object intercept(Invocation invocation) throws Throwable;
  
  
  Object plugin(Object target);

  void setProperties(Properties properties);

}

The intercepts annotation identifies the interception class, and the signature annotation identifies some methods, parameters and execution types of interception

/**
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}


/**
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  Class<?> type();

  String method();

  Class<?>[] args();
}

The following is the interception chain. Interceptorchain is an interception chain. The pluginall () method will traverse the added interception chains one by one, and call the plugin () method of the interceptor. In the plugin () method, go back and call the warp () method of the plugin class to create a proxy class. In this way, the actual calling object will be wrapped over and over again, and finally the proxy object will be returned. Later, the actual object execution method will call the invoke () method, and the intercept () method will be called if it meets the requirements, and our processing logic should be written in the intercept () method.

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}





public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
}

It is a pity that the mybatis interceptor does not implement order, and the addition order of interceptors needs special attention.

Recommended Today

Ffmpeg plays RTSP / webcam stream

This article will introduce how ffmpeg plays RTSP / webcam / file streams. The process is as follows: RTSP/Webcam/File > FFmpeg open and decode to BGR/YUV > OpenCV/OpenGL display code:https://github.com/ikuokuo/rt…, sub module RTSP local player Ffmpeg preparation git clone https://github.com/ikuokuo/rtsp-wasm-player.git cd rtsp-wasm-player export MY_ROOT=`pwd` # ffmpeg: https://ffmpeg.org/ git clone –depth 1 -b n4.4 https://git.ffmpeg.org/ffmpeg.git $MY_ROOT/3rdparty/source/ffmpeg […]