SOFA source code analysis - filter design

foreword

Usually web servers use the filter mode when processing requests. Whether it is Tomcat or Netty, the advantage of filters is that they can separate and decouple the processing process. For example, when an Http request enters the server, it may be necessary to parse the http header. Permission verification, internationalization processing, etc., filters can isolate these processes well, and filters can be uninstalled and installed at any time.

The idea of ​​filters is similar for each web server, just implemented in slightly different ways.

For example, Tomcat, Tomcat uses a FilterChain object to save all filters, and completes the filtering process by looping through all filters. For Tomcat's filter source code, please see the previous article of the landlord: In
-depth understanding of the request process of Tomcat (nine) source code analysis

Netty uses the pipeline as the filter pipeline, the handler is used for interception processing in the pipeline, and the handler uses a handlerInvoker (Context) for isolation processing, that is, the handler and the handler are isolated, and the Context context is used for the flow in the middle. About Netty's pipeline, you can check the previous article of the landlord:
Netty core component Pipeline source code analysis (1) Analysis of pipeline Three giant
Netty core components Pipeline source code analysis (2) A requested pipeline journey

And SOFA uses slightly different from the above two, we will analyze it through the source code today.

design

SOFA's filters consist of 3 main classes:

  1. FilterInvoker The Invoker object wrapped by the filter mainly isolates the relationship between filter and service;
  2. Filter filter (extendable via SPI)
  3. FilterChain The start interface of the filter chain is actually an Invoker.

Let's take a look at the main methods of these three classes, and we'll know how to design them.

Filter main method:

public abstract SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException;

The invoke method is an abstract method that users can implement by themselves, and the method body is the user's processing logic. Usually this method ends with:

return invoker.invoke(request);

The invoke method of the parameter invoker object is called. Let's look at this FilterInvoker.

FilterInvoker main method

Construction method:

public FilterInvoker(Filter nextFilter, FilterInvoker invoker, AbstractInterfaceConfig config) {
    this.nextFilter = nextFilter;
    this.invoker = invoker;
    this.config = config;
    if (config != null) {
        this.configContext = config.getConfigValueCache(false);
    }
}

The landlord introduces his main construction method here. Pass in a filter and an invoker.

This filter is the filter wrapped by the current invoker, and the parameter invoker is his next invoker node. When the invoke method of FilterInvoker is executed, the invoke method of filter is usually called, and the invoker parameter is passed in.

This goes back to the invoke method of the filter we analyzed above, which will call the invoke method of the invoker internally to complete a cycle.

Take another look at FilterChain.

FilterChain main method

FilterChain is an instance of the framework's direct operation, each caller indirectly holds a FilterChain instance, and this instance is equivalent to the head node of the filter linked list.

Construction method:

protected FilterChain(List<Filter> filters, FilterInvoker lastInvoker, AbstractInterfaceConfig config) {
    // 调用过程外面包装多层自定义filter
    // 前面的过滤器在最外层
    invokerChain = lastInvoker;
    if (CommonUtils.isNotEmpty(filters)) {
        loadedFilters = new ArrayList<Filter>();
        for (int i = filters.size() - 1; i >= 0; i--) {// 从最大的开始,从小到大开始执行
            Filter filter = filters.get(i);
            if (filter.needToLoad(invokerChain)) {
                invokerChain = new FilterInvoker(filter, invokerChain, config);
                // cache this for filter when async respond
                loadedFilters.add(filter);
            }
        }
    }
}

When constructing a filter chain, an array of filters is passed in, and a FilterInvoker is passed in. This Invoker is the real business method, and the framework will reflect the implementation class of the calling interface in the invoke method, that is, the business code.

The main logic of the above construction method is:

Circulate the Filter instances in the List in reverse order, encapsulate the Filter with FilterInvoker, and pass the last FilterInvoker into the construction method of FilterInvoker to form a linked list. The FilterInvoker passed in alone will be placed in the last node. `

So, in the end, when FilterChain calls the filter chain, it will start from the filter with the smallest order, and finally execute the business method.

Note: In the SOFA filter, it is not the Filter that actually executes the business method, but the specific implementation class of FilterInvoker. In the invoke method, the method of the interface implementation class is invoked by reflection. The reason is the invoker.invoke that the filter calls last. There is no need to construct a filter anymore.

The above is the filter design of SOFA. Generally speaking, it is similar to Tomcat's filter, but it is just an array used by Tomcat, and the Service is treated differently, that is, after all the filters are executed, the Service is executed. And SOFA uses a linked list, and does not treat Services differently.

One more thing

Filter is an interface and annotated with @Extensible(singleton = false)annotations , indicating that this is an extension point, which is a design of the SOFA microkernel. All middleware can be added to the framework through extension points.

The extension point is actually a bit similar to the Spring Bean. The Spring Bean and the core data structure are BeanDefine, and the core data structure of the SOFA extension point is ExtensionClass, which defines all the relevant information of the extension point.

SOFA will place all extension points in an ExtensionLoader's ConcurrentHashMap<String, ExtensionClass > Medium.

ExtensionLoader can be called an extension class loader, and an ExtensionLoader corresponds to an extensible interface.

Summarize

In terms of design, SOFA's filter is more similar to Tomcat's filter, and has its own characteristics compared to Netty's filter. Netty's filters can be plugged and unplugged at any time. Perhaps from a business perspective, SOFA does not need such a function.

At the same time, Filter is based on the extension point of SOFA. Dubbo author said:

Most of the well-developed frameworks follow the concept
of microkernel. The microkernel of Eclipse is OSGi, the microkernel of Spring is BeanFactory, and the microkernel of Maven is Plexus.
Usually, the core should not be functional, but a Life cycle and integrated containers,
so that each function can interact and expand in the same way, and any function can be replaced.
If the microkernel cannot be achieved, at least the third party must be treated equally,
that is, the function that the original author can implement, the extender It should be possible to do it all by means of extension, and the
original author should also regard himself as an extender, so as to ensure the sustainability of the framework and the stability from the inside to the outside.

Microkernel plug-in, equal treatment of third parties For the framework, it is very important.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325125545&siteId=291194637