Chain of responsibility model

The Chain of Responsibility Pattern creates a Chain of recipient objects for the request.

This pattern gives the type of the request and decouples the sender and receiver of the request.This type of design pattern is a behavioral pattern.

In this pattern, each receiver typically contains a reference to another receiver.If an object cannot handle the request, it passes the same request to the next receiver, and so on.Cite the W3C description of chain of responsibility

We also encounter this in the real development, where we need to provide an interface to the external, which may result in many non-business processing methods (logging, permission verification, sensitive data cleaning….). But it’s transparent to the business. Each processor is independent, and there should be no coupling relationship that allows us to splice at will.

The code roughly implements relational dependencies

First we need to define a RequestPlugin interface, and then all the plug-ins need to implement this interface

/** * public interface requestPlugin {/** * route */ void interceptor(interceptorChainWrapper) routeChainWrapper); /** * Enable */ Boolean enable(); }

Each plug-in handles functions independently. However, there may be a sorting relationship between plugins. For example, the request entry should retain the most original parameters, so the plugins of the log are generally placed first. We define an annotation to sort the order by the value of the order.

*/ @Target(ElementType.type) @Retention(RetentionPolicy.Runtime) @Documented @Component public @Interface PluginAnno { int order() default Ordered.HIGHEST_PRECEDENCE; String name(); }

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

/** */ @Pluginanno (order = 1) name = "LogSavePlugin") public class LogSavePlugin implements RequestPlugin { @Override public void Interceptor (interceptorChainWrapper, interceptorChainWrapper, routechainWrapper) {System.out.println(" LogSavePlugin"); routeChainWrapper.interceptor(); } @Override public boolean enable() { return true; } /** * @Pluginanno (order = 2,) name = "ParseHandlePlugin") public class ParseHandlePlugin implements RequestPlugin { @Override public void Interceptor (interceptorChainWrapper, interceptorChainWrapper, routechainWrapper) {System.out.println(" ParseHandlePlugin"); routeChainWrapper.interceptor(); } @Override public boolean enable() { return false; } /** * @Pluginanno (order = 3,) name = "AuthorCheckPlugin") public class AuthorCheckPlugin implements RequestPlugin { @Override public void Interceptor (interceptorChainWrapper routechainWrapper) {System.out.println(" AuthorCheckPlugin"); routeChainWrapper.interceptor(); } @Override public boolean enable() { return true; }}

Inject three plug-ins and sort them.

Private List< requestPlugin () {private List< requestPlugin (); private List< requestPlugin (); Public requestPluginConfig (List< requestPlugin > requestPlugins) {this.requestPlugins = -> o.getClass().getAnnotation(PluginAnno.class).order())).collect(Collectors.toList()); } public InterceptorChainWrapper createChainWrapper() { return new InterceptorChainWrapper(requestPlugins); }}

The plug-in actually enables invocation classes and tests

/** * public class InterceptorChainWrapper {private final 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. The size () {return; } RequestPlugin plugin = requestPlugins.get(atomicInteger.get()); if (! plugin.enable()) { interceptor(); return; } plugin.interceptor(this); Public StudyApplication {public static void main(String[] args) {public StudyApplication {public static void main(String[] args) { ConfigurableApplicationContext context =, args); RequestPluginConfig requestPluginConfig = context.getBean(RequestPluginConfig.class); requestPluginConfig.createChainWrapper().interceptor(); }}

We have seen the above implementation, let’s look at the actual implementation of the framework, here is the MyBatis interceptor, and see how it is different from our implementation. MyBatis interceptor is to use the object interceptor to wrap the actual object as a proxy class.

Define an interceptor interface as usual

/** * @author Clinton Begin */ public interface Interceptor {/** */ Object intercept(Invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }

The Intercepts annotation identifies the interceptor class, and the Signature annotation identifies the intercepted methods, parameters, and execution types

/** * @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(); }

And then the interception chain. InterceptorChain is an InterceptorChain. The pluginAll() method iterates through the InterceptorChain one by one, calling the plugin() method of the interceptor, which in turn calls the warp() method of the Plugin class to create a proxy class. The actual calling object will then be wrapped over and over again and eventually the proxy object will be returned. The actual object execution method will call invoke(), and the intercept() method will be called if it conforms to the bar, and our processing logic will 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) { = 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()]); }}

Unfortunately, the MyBatis interceptor does not implement Order, so the order in which interceptors are added requires special attention.