Dubbo Architecture Design and Source Code Analysis (Part Three) Chain of Responsibility Pattern

0 22
Part One: Introduction to Chain of Responsibility Pattern1. Definition of Chain...

Part One: Introduction to Chain of Responsibility Pattern

1. Definition of Chain of Responsibility Pattern

The definition of the Chain of Responsibility (Chain of Responsibility) pattern: In order to avoid the coupling between the request sender and multiple request handlers, all request handlers are connected into a chain by the previous object keeping a reference to the next object; when a request occurs, the request can be passed along this chain until an object handles it. In the Chain of Responsibility pattern, the client only needs to send the request to the chain, without concerning about the details of request handling and the process of request transmission. The request will be automatically transmitted. Therefore, the Chain of Responsibility decouples the request sender and the request handler.

2. Characteristics of Chain of Responsibility

The Chain of Responsibility pattern is an object behavioral pattern,

Dubbo Architecture Design and Source Code Analysis (Part Three) Chain of Responsibility Pattern

Its main advantages are as follows.

1) It reduces the coupling between objects. This pattern makes it unnecessary for an object to know which object handles its request and the structure of the chain, and the sender and receiver do not need to have clear information about each other.

2) It enhances the scalability of the system. New request processing classes can be added as needed, satisfying the open-closed principle.

3) It enhances the flexibility of assigning responsibilities to objects. When the workflow changes, the members within the chain can be dynamically changed or their order can be changed, and responsibilities can also be dynamically added or deleted. The chain of responsibility simplifies the connection between objects. Each object only needs to keep a reference to its successor, without keeping references to all other processors, which avoids using a large number of if or if···else statements.

4) Responsibility sharing. Each class only needs to handle its own work, and that which should not be handled is passed to the next object to complete, clearly defining the responsibility scope of each class, which conforms to the single responsibility principle of classes.

Its main shortcomings are as follows.

1) It cannot be guaranteed that each request will be processed. Since there is no clear recipient for a request, it cannot be guaranteed that it will be processed, and the request may reach the end of the chain without being processed.

2) For longer chains of responsibility, the processing of requests may involve multiple processing objects, which will affect the system performance to a certain extent.

3) The rationality of establishing the chain of responsibility depends on the client, which increases the complexity of the client and may cause system errors due to the incorrect setting of the chain of responsibility, such as causing circular calls.

3. Chain of Responsibility Structure Diagram

2022-09-05-23-408P9oe8wXRr7tPWY.png

2022-09-05-23-400He7uYsEyGDgBB0.png

Part Two: Chain of Responsibility in Dubbo

1. Filter Log

By printing the filter logs, we can see that during the process of publishing services, each filter class of dubbo will be passed in turn to ensure the completeness of the service.

2022-09-05-23-48kGqXFiXjdro9T0d.png

2. Filter Diagram

Dubbo encapsulates each filter class into the core model invoker, and finally forms the evening filter responsibility chain filterChain.

2022-09-05-23-52A9mUNPwQuHyoc8Y.png

3. Filter class diagram

Protocol is the main functional entry point for the core model invoker to expose and refer, using the SPI interface, its two methods export and refer correspond to the service functions of the provider and consumer ends, respectively. ProtocolFilterWapper is the main implementation class of Dubbo's filter, which points to the buildInvokerChain method through the overridden export and refer, performs the acquisition and assembly of the responsibility chain in buildInvokerChain, obtains various implementation classes of Filter through SPI in extensionLoader, and sorts them through ActivateComparator, and finally forms a complete responsibility chain.

2022-09-05-23-54PDQyXyarHGTHE8F.png

3. Introduction of various Filter responsibilities in Dubbo

1. Filters used by provider

2022-09-06-00-20GVIiHE12206JX6amK.png

2. Filters used by consumer

2022-09-06-00-21ZuA39xBRVeIyCmL12.png

4. Source Code Analysis

Enter the core class ProtocolFilterWrapper, both export and refer in the implementation class adopt the same construction responsibility chain method buildInvokerChain, but they are distinguished by the parameter group

2022-09-06-00-34eIdgo9AgokKBHcb.png

2022-09-06-00-34eIdgo9AgokKBHcb.png

In buildInvokerChain, obtain the array of filters through getActivateExtension and then encapsulate the core model invoker and assemble it into a responsibility chain

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        // Obtain the array of filters (already sorted)
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        // Create an Invoker object with a Filter chain
        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        return filter.invoke(next, invocation);
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        System.out.println("group:" + group);
        for (Filter filter : filters) {
            System.out.println(filter.getClass());
        }
        return last;
    }

getActivateExtension is the main assembly logic, which includes logic such as acquisition and sorting

Firstly, judge whether to use the system default Filter filter, and check whether each system filter should be removed, then sort the system filters, and finally add the user-defined filters to assemble the responsibility chain through the specified parameters

public List<T> getActivateExtension(URL url, String key, String group) {
        // Get the parameter value from the Dubbo URL
        String value = url.getParameter(key);
        // Obtain the array of extension objects that meet the automatic activation conditions
        return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
    }
public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<T>();
        // All filter information configured by users (some Filters are activated by default, and some are configured to be activated, here the names refer to the filter information configured to be activated)
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
        // Process the extension objects to be automatically activated
        // Check if the configuration "-name" does not exist. For example, <dubbo:service filter="-default" />, it represents removing all default filters.
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            // Get the array of extension implementation classes
            getExtensionClasses();
            // Loop
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                // The name refers to the key of the configuration file read by SPI
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) { // Match group
                    // Get the extension object
                    T ext = getExtension(name);
                    if (!names.contains(name) // Not included in custom configuration. If included, it will be processed in the following code
                            // Check if configured to remove. For example, <dubbo:service filter="-monitor" />, then MonitorFilter will be removed
                            && isActive(activate, url)) { // Determine if activated
                        exts.add(ext);
                    }
                }
            }
            // Sorting
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        // Process the custom extension objects configured. For example, in <dubbo:service filter="demo" />, it represents that DemoFilter needs to be added.
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // Determine non-removal
                // Place the configured custom extensions before the automatically activated extension objects. For example, in <dubbo:service filter="demo,default,demo2" />, the DemoFilter will be placed before the default filter.
                if (Constants.DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    // Get the extension object
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        // Add to the result set
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

The system's default filters and udf filters are distinguished from each other.

Taking ContextFilter as an example, the system's default filter includes the Activate annotation, which is used to specify the group and sorting weight. Filters implemented by the user themselves cannot add the Activate annotation to specify the required filters when deployed.

2022-09-06-00-46Vxg6X6zYcxPfrGZ.png

Let's take a look at the specific sorting comparison method. First, we need to determine if the Activate annotation specifies the before and after parameters for sorting. If not, we will use the order weight for sorting.

ActivateComparator.class
public int compare(Object o1, Object o2) {
        // Basic sorting
        if (o1 == null && o2 == null) {
            return 0;
        }
        if (o1 == null) {
            return -1;
        }
        if (o2 == null) {
            return 1;
        }
        if (o1.equals(o2)) {
            return 0;
        }

        Activate a1 = o1.getClass().getAnnotation(Activate.class);
        Activate a2 = o2.getClass().getAnnotation(Activate.class);

        // Use the `after` and `before` properties of annotations for sorting
        if ((a1.before().length > 0 || a1.after().length > 0 || a2.before().length > 0 || a2.after().length > 0) // (a1 or a2) has (`after` or `before`) properties.
                && o1.getClass().getInterfaces().length > 0 && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) { // The implemented interface has the @SPI annotation.
            // Obtain the extension loader
            ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
            // From the perspective of a1,Make a comparison
            if (a1.before().length > 0 || a1.after().length > 0) {
                String n2 = extensionLoader.getExtensionName(o2.getClass());
                for (String before : a1.before()) {
                    if (before.equals(n2)) {
                        return -1;
                    }
                }
                for (String after : a1.after()) {
                    if (after.equals(n2)) {
                        return 1;
                    }
                }
            }
            // From the perspective of a2, make a comparison.
            if (a2.before().length > 0 || a2.after().length > 0) {}}
                String n1 = extensionLoader.getExtensionName(o1.getClass());
                for (String before : a2.before()) {
                    if (before.equals(n1)) {
                        return 1;
                    }
                }
                for (String after : a2.after()) {
                    if (after.equals(n1)) {
                        return -1;
                    }
                }
            }
        }

        // Use the `order` attribute of the annotation for sorting.
        int n1 = a1 == null ? 0 : a1.order();
        int n2 = a2 == null ? 0 : a2.order();
        // Never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in a collection like HashSet
        return n1 > n2 ? 1 : -1;
    }

Summary:

The Responsibility Chain pattern is a simple and common design pattern in design patterns, and it is possible that we may also frequently apply the Responsibility Chain pattern in our daily lives. The Responsibility Chain pattern in dubbo fully utilizes flexibility, whether it is from the concept of grouping, specifying the priority of sorting through annotations, or whether each filter should be removed, etc., making each filter pluggable and reducing the invasiveness of the code, which is very worthy of our learning.

你可能想看:
最后修改时间:
admin
上一篇 2025年03月28日 14:36
下一篇 2025年03月28日 14:59

评论已关闭