Dubbo source code analysis - microkernel plug-in development (ExtensionLoader)

1. Implementation principle description

The previous chapter said that Dubbo's microkernel plug-in development is actually an optimization and upgrade based on the SPI design idea. We all know that the SPI design idea is to use ServiceLoader to scan the META-INF/services/ directory of the third-party dependent JAR package when we want to obtain the implementation of an interface. Read the content in this file, and then SPI agrees that this content is the full path name of the implementation class, so ServiceLoader can instantiate and provide services according to this implementation class path. Dubbo has implemented a loader by himself, called ExtensionLoader. According to Dubbo, only the interface marked with @SPI annotation can use the ExtensionLoader to load the implementation class. ExtensionLoader will scan the META-INF/dubbo/internal/ directory, META-INF/dubbo/ directory, META-INF/services/ directory in turn, scan the file with the full path name of the interface, and then the content of the file agrees with key=value form, the key is the alias of the implementation class, and the value is the full path of the implementation class. Of course, in a project, there may be many implementations of the same interface, so which implementation is used, so here Dubbo has made a special design, each supporting SPI interface will need an adaptation implementation (you can implement it yourself, or The system can help you generate it), and then the user can decide which specific implementation to choose according to the configuration implementation. When the ExtensionLoader scans the implementation class and finds that the @Adaptive annotation is marked on the implementation class, it will treat the implementation class as an adaptive implementation. If it finds that none of the scanned implementation classes are marked with this annotation, then the ExtensionLoader An implementation class will be automatically generated as the configuration implementation of the interface, so all @SPI interfaces will have an adaptation implementation when they are used.

2. @SPI annotation

As mentioned above, ExtensionLoader supports loading extension implementations only when annotated with SPI-annotated interfaces. SPI has a value parameter. This value specifies the alias of the extension implementation. After specifying, the extension implementation corresponding to this alias is used by default. The SPI source code is as follows:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
	String value() default "";
}

Many internal implementations of Dubbo are based on this method. We all know that Dubbo supports many serialization protocols. I will take this example to illustrate. Looking at the Protocol protocol interface, he marked the SPI annotation and specified that the default implementation is the protocol implementation aliased as dubbo. Therefore, when obtaining the protocol implementation through ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); in the system, if no specific implementation is specified, the protocol alias dubbo will be used by default.

@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();
}

3. @Adaptive annotation

Function 1: The @Adaptive annotation is mainly used to mark the implementation of the adaptation class. As mentioned above, the ExtensionLoader will check whether the implementation class is marked with the @Adaptive mark when each dependent JAR is looking for the implementation class. If it is marked, it means that the implementation class is suitable. Match realization. It will be cached in the cachedAdaptiveClass variable of ExtensionLoader. For example, if I want to implement an adaptation implementation of the Protocol interface myself, I can write:

@Adaptive
public class AdapterProtocol implements Protocol {
    @Override
    int getDefaultPort(){//省略...}
    
    @Override
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException{//省略...}

    @Override
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException{//省略...}
    
    @Override
    void destroy(){//省略...}
}

Then place the file in the META-INF/services/ directory: com.alibaba.dubbo.rpc.Protocol. The content inside is set to adapterProtocol=mypackage.AdapterProtocol. In this way, we have implemented a Protocol configuration implementation. When the system uses ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); to load the implementation, what we get is the adaptation class we implemented.

Function 2: @Adaptive can also be marked on the methods of the interface. In this case, when the ExtensionLoader scans all the implementation classes, it finds that none of the implementation classes are marked with @Adaptive. So ExtensionLoader will use javasist to automatically generate a configuration class for us. When automatically implementing each interface, it will be generated according to the value[] array value of the @Adaptive annotation marked on the method. Throws an exception if not marked @Adaptive. For example, the decompiled class automatically implemented by the Protocol above looks like this:

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    //没有打上@Adaptive的方法如果被调到抛异常
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void 
        com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not 
        adaptive method!")
    }
    
    //接口中export方法打上@Adaptive注册
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws 
                                                              com.alibaba.dubbo.rpc.Invoker
    {
        if (arg0 == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        //参数类中要有URL属性
        if(arg0.getUrl() == null) 
            throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invoker argument 
            getUrl() == null");
            
        //从入参获取统一数据模型URL
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            
        //从统一数据模型URL获取协议,协议名就是SPI扩展点实现类的key
        if(extName == null) 
            throw new IllegalStateException( "Fail to 
            getextension(com.alibaba.dubbo.rpc.Protocol) name from url("  + url.toString() + 
            ") usekeys([protocol])");
          
        //利用dubbo服务查找机制根据名称找到具体的扩展点实现
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.
        getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
    }
    //其他方法省略...
}

The automatic implementation of the configuration code roughly means that if the method is not marked with the @Adaptive annotation, an exception will be thrown. If the @Adaptive annotation is marked, it will be judged whether the interface method has URL parameters, and if not, it will be found attached to the parameters. The URL in , and then synthesize the code according to the value[] array specified by @Adaptive. The meaning of the code is roughly that when this adaptation class is used, the implementation specified by value[] will be used in turn. If it is not found, @SPI will be used at the end. Specifies the default implementation.

4、@Activate

Let me just say something about this note. When we implement the extension, if we add this annotation. Then some restrictions can be configured through this annotation. Implementations marked with this annotation are cached when the system looks for an extension implementation. The implementations to activate can then be filtered out when appropriate. @Activate annotation source code:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    //group过滤条件,没有group就不过滤
    String[] group() default {};
    //key过滤条件,如没有设置,则不过滤
    String[] value() default {};
    //排序信息,可以不提供
    String[] before() default {};
    //排序信息,可以不提供
    String[] after() default {};
    //排序信息,可以不提供
    int order() default 0;
}

For example, if there is a CacheFilter in the source code, this annotation is used.

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {
    //省略...
}

This call filters out the implementation to be activated when the usage system is to be used

ExtensionLoader.getExtensionLoader(Filter.class)
                .getActivateExtension(url, new String[]{}, "value");

5. ExtensionLoader scan cache extension implementation process description

Guess you like

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