资讯专栏INFORMATION COLUMN

dubbo扩展点机制

Rindia / 3131人阅读

摘要:在中配置,以配置为例整个,最先使用的地方从里面读取这个配置使用接口的中获取具体的实现类中有两个值当主线程被外部终止时,会触发,执行的与方法通知下面的锁操作,主线程正常走完代码,并最终停止。

spring是如何启动容器的

常见的一种在本地使用main方法启动spring的方法

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
        context.start();
        ...
        //System.in.read(); // 按任意键退出
        context.close();
    }
dubbo是如何启动容器的

这个大家应该都知道,通过com.alibaba.dubbo.container.Main.main方法来启动的。

public class Main {

    //在dubbo.properties中配置, 以配置dubbo.container=log4j,spring为例
    public static final String CONTAINER_KEY = "dubbo.container";

    public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";

    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    
    //整个dubbo,最先使用ExtensionLoader的地方
    private static final ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Container.class);

    private static volatile boolean running = true;

    public static void main(String[] args) {
        try {
            //1. 从dubbo.properties里面读取dubbo.container这个配置;
            if (args == null || args.length == 0) {
                String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }
            //2. 使用Container接口的ExtensionLoader中获取具体的Container实现类;
            final List containers = new ArrayList();
            //agrs中有两个值 "log4j,spring"
            for (int i = 0; i < args.length; i++) {
                containers.add(loader.getExtension(args[i]));
            }
            logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");

            if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
                //5. 当主线程被外部终止时,会触发 shutdownhook,执行Container的stop与close方法
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        for (Container container : containers) {
                            try {
                                container.stop();
                                logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
                            } catch (Throwable t) {
                                logger.error(t.getMessage(), t);
                            }
                            synchronized (Main.class) {
                                running = false;
                                //6.通知下面的锁操作,主线程正常走完代码,并最终停止。
                                Main.class.notify();
                            }
                        }
                    }
                });
            }
            //3. 执行Container接口的start方法;
            for (Container container : containers) {
                container.start();
                logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
            }
            System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
        } catch (RuntimeException e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e);
            System.exit(1);
        }
        //4. 用一个死循环,保留主线程;
        synchronized (Main.class) {
            while (running) {
                try {
                    Main.class.wait();
                } catch (Throwable e) {
                }
            }
        }
    }

}
dubbo容器的SPI功能实现

明确下面几个概念

扩展接口 com.alibaba.dubbo.container.Container

扩展配置 dubbo.container = log4j,spring

扩展实现

com.alibaba.dubbo.container.log4j.Log4jContainer

  log4j的日志初始工作,当多进程启动时,做日志隔离

com.alibaba.dubbo.container.logback.LogbackContainer

  logback的日志初始工作

com.alibaba.dubbo.container.spring.SpringContainer

  spring容器的启动,使用spring容器来实现aop与ioc,**【这个配置,往往是必选的】**

com.alibaba.dubbo.container.jetty.JettyContainer

  启动一个Servlet Web容器,提供了一个web页面,做一些监控之类的时期,注意:在写HttpResponse的时候,也是用SPI机制,不同的请 

求页面经过PageServlet交个不同的PageHandler去实现

com.alibaba.dubbo.monitor.simple.RegistryContainer

我们来想一个这样的问题,上面是dubbo支持的容器,包括log4j、logback、spring、jetty、registry,那么dubbo是如何通过配置的方式来实现容器的可扩展的呢?假如给你做你怎么做呢?

spring的API(Application Programming Interface、应用编程接口)方式,接口多实现类的动态调动;

JDK标准的SPI(Service Provider Interface、)机制
dubbo的扩展点加载机制是从JDK的spi机制加强而来。
dubbo改进了JDK标准的SPI机制以下问题:

spring与JDK的SPI都会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上,也会加载。

JDK的SPI机制不支持Ioc与Aop功能,而dubbo中的扩展点可以直接setter注入其他扩展点。【这个一部分,下面会有涉及,我们会在下一个文章中详细描述】

扩展接口Container源码

关键说明,

必须带有SPI注解

注解里面的值,是默认实现,在ExtensionLoader源码去细讲。

/**
 * Container. (SPI, Singleton, ThreadSafe)
 *
 * @author william.liangf
 */
@SPI("spring")
public interface Container {

    /**
     * start.
     */
    void start();

    /**
     * stop.
     */
    void stop();

}
ExtensionLoader源码

关键说明,

1.  ExtensionLoader有一个private的构造函数,并通过getExtensionLoader这个镜头方法返回实例,是一个单例工厂类。
2.  一个扩展接口对应一个ExtensionLoader实例,也就是说最终我们加载了多少个扩展接口(注意是扩展接口,而不是扩展实现类),就多少个实例;
3.  关键static final变量,所有实例共享
    private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap, ExtensionLoader>();

    private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>();
4.  所有的final变量,单个实例共享,每一个扩展接口对应的ExtensionLoader都不一样
    //扩展接口名称
    private final Class type;
    //也是一个扩展接口,用于注入扩展接口中需要注入的类,实现dubbo的扩展点的自动注入
    private final ExtensionFactory objectFactory;

    private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>();

    private final Holder>> cachedClasses = new Holder>>();

    private final Map cachedActivates = new ConcurrentHashMap();
    private final ConcurrentMap> cachedInstances = new ConcurrentHashMap>();
    private final Holder cachedAdaptiveInstance = new Holder();
    private volatile Class cachedAdaptiveClass = null;
    private String cachedDefaultName;
    private volatile Throwable createAdaptiveInstanceError;

    private Set> cachedWrapperClasses;

    private Map exceptions = new ConcurrentHashMap();

结合Main类的使用,讲一下几个核心方法

核心方法 -> ExtensionLoader.getExtensionLoader

获得ExtensionLoader实例

private static final ExtensionLoader loader = ExtensionLoader.**getExtensionLoader**(Container.class);

获取ExtensionLoader实例

getExtensionLoader(Container.class)【将返回的实例放到EXTENSION_LOADERS变量中】

new ExtensionLoader(type) 【初始化type与objectFactory变量,初始化objectFactory变量的时候有一点点的绕。假如这个接口不是ExtensionFactory,就需要初始化这样的一个objectFactory,否则就需要,具体后面会将】

获取ExtensionLoader实例结束

核心方法 -> ExtensionLoader.getExtension

获得扩展实现
注意此时已经拿到了扩展接口Container对应的那个ExtensionLoader实例了,在下面的处理中,基本都是更新这个实例的变量,而很少会更新类变量了。

for (int i = 0; i < args.length; i++) {
   containers.add(loader.getExtension(args[i]));
}

getExtension("log4j" or "spring" or "logback" ....)

createExtension("log4j" or "spring" or "logback" ....) --创建指定类型的扩展接口的instance

getExtensionClasses() --加载扩展接口的所有class文件

loadExtensionClasses() --扩展接口的所有的class文件

loadFile() --从三个路径下,查找class文件

clazz.newInstance() --创建指定class的instance

injectExtension(instace) --注入属性Ioc

objectFactory.getExtension(pt, property) --反射的方式,解析setXxx(Xxx xxx)方法,注入Xxx实例

injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); --对实例进行层层包装,最终返回一个包装过后的instance

上面总体逻辑就是
图片
具体介绍一下loadFile方法

    //...
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    //...
    Map> extensionClasses = new HashMap>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
if (clazz.isAnnotationPresent(Adaptive.class)) {
    if (cachedAdaptiveClass == null) {
        cachedAdaptiveClass = clazz;
    } else if (!cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
                + cachedAdaptiveClass.getClass().getName()
                + ", " + clazz.getClass().getName());
    }
} else {
    try {
        clazz.getConstructor(type);
        Set> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } catch (NoSuchMethodException e) {
        clazz.getConstructor();
        if (name == null || name.length() == 0) {
            name = findAnnotationName(clazz);
            if (name == null || name.length() == 0) {
                if (clazz.getSimpleName().length() > type.getSimpleName().length()
                        && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                    name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                } else {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                }
            }
        }
        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class c = extensionClasses.get(n);
                if (c == null) {
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                }
            }
        }
    }
}

从上面三个路径下加载dubbo扩展点的配置。我们以DUBBO_INTERNAL_DIRECTORY路径下的配置文件为例,说明下dubbo下扩展的配置。

扩展接口实现类,实现Container接口,例如SpringContainer.java

在资源META-INF.dubbo.internal文件夹下,有一个以Container接口全路径名称为名字的文件;

上述文件名中内容格式为 {key}={value},key为扩展点实现类的配置名称,例如spring、log4j等;value为SpringContainer类的全路径名称

loadFile中就是以这样的规则,解析这样的配置文件,并放到extensionClasses这样的Map中返回,extensionClasses的key是这个{key},value是这个{value}对应的class。

这里面主要是四个逻辑,涉及到几种情况。
图片

拿到所有配置的Container实例
for (Container container : containers) {
    container.start();
    logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}

执行SpringContainer.java的start方法

    public void start() {
        String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
        if (configPath == null || configPath.length() == 0) {
            configPath = DEFAULT_SPRING_CONFIG;
        }
        context = new ClassPathXmlApplicationContext(configPath.split("[,s]+"));
        context.start();
    }
这个不是这篇文章最开始的那个问题的答案嘛,原来dubbo就是通过这么简单的方式的来启动spring容器的。这算是一个首尾呼应嘛~

终于,终于,第一篇文章写完了~ 下篇文章会讲解扩展点是如何IOC的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69058.html

相关文章

  • 聊聊Dubbo - Dubbo扩展机制实战

    摘要:今天我想聊聊的另一个很棒的特性就是它的可扩展性。的扩展机制在的官网上,描述自己是一个高性能的框架。接下来的章节中我们会慢慢揭开扩展机制的神秘面纱。扩展扩展点的实现类。的定义在配置文件中可以看到文件中定义了个的扩展实现。 摘要: 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架。今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性。 Dubbo的扩展机制 在Dub...

    techstay 评论0 收藏0
  • 聊聊Dubbo - Dubbo扩展机制源码解析

    摘要:什么是类那什么样类的才是扩展机制中的类呢类是一个有复制构造函数的类,也是典型的装饰者模式。代码如下有一个参数是的复制构造函数有一个构造函数,参数是扩展点,所以它是一个扩展机制中的类。 摘要: 在Dubbo可扩展机制实战中,我们了解了Dubbo扩展机制的一些概念,初探了Dubbo中LoadBalance的实现,并自己实现了一个LoadBalance。是不是觉得Dubbo的扩展机制很不错呀...

    lmxdawn 评论0 收藏0
  • dubbo源码解析(二)Dubbo扩展机制SPI

    摘要:二注解该注解为了保证在内部调用具体实现的时候不是硬编码来指定引用哪个实现,也就是为了适配一个接口的多种实现,这样做符合模块接口设计的可插拔原则,也增加了整个框架的灵活性,该注解也实现了扩展点自动装配的特性。 Dubbo扩展机制SPI 前一篇文章《dubbo源码解析(一)Hello,Dubbo》是对dubbo整个项目大体的介绍,而从这篇文章开始,我将会从源码来解读dubbo再各个模块的实...

    DirtyMind 评论0 收藏0
  • Dubbo SPI机制和IOC

    摘要:要构建自适应实例,先要有自适应的实现类,实现类有两种方式一种通过配置文件,一种是通过是字节码的方式动态生成。 SPI机制 SPI,即(service provider interface)机制,有很多组件的实现,如日志、数据库访问等都是采用这样的方式,一般通用组件为了提升可扩展性,基于接口编程,将操作接口形成标准规范,但是可以开放多种扩展实现,这种做法也符合开闭设计原则,使组件具有可插...

    Scorpion 评论0 收藏0
  • Dubbo Spi机制

    摘要:为了实现在模块装配的时候,不在模块里写死代码,就需要一种服务发现机制。就提供了这样一种机制为某个接口寻找服务实现,有点类似思想,将装配的控制权移到代码之外。即接口文件的全类名。五示例遵循上述第一条第点,这里为接口文件,其中和为两个实现类。 一、Dubbo内核 Dubbo内核主要包含SPI、AOP、IOC、Compiler。 二、JDK的SPI 1.spi的设计目标: 面向对象的设计里...

    mrli2016 评论0 收藏0

发表评论

0条评论

Rindia

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<