1、dubbo扩展点机制

spring的几个问题

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的几个问题

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<Container> 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<Container> containers = new ArrayList<Container>();
            //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容器的可扩展功能实现

明确下面几个概念

  1. 扩展接口 com.alibaba.dubbo.container.Container
  2. 扩展配置 dubbo.container = log4j,spring
  3. 扩展实现
  • 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源码

关键说明,

  1. 必须带有SPI注解
  2. 注解里面的值,是默认实现,在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<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

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

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

    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

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

    private Set<Class<?>> cachedWrapperClasses;

    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

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

1.核心方法 -> ExtensionLoader.getExtensionLoader 返回ExtensionLoader实例

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

获取ExtensionLoader实例

  • getExtensionLoader(Container.class)【将返回的实例放到EXTENSION_LOADERS变量中】
    • new ExtensionLoader<T>(type) 【初始化type与objectFactory变量,初始化objectFactory变量的时候有一点点的绕。假如这个接口不是ExtensionFactory,就需要初始化这样的一个objectFactory,否则就需要,具体后面会将】 获取ExtensionLoader实例结束

2. 核心方法 -> 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<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    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<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            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下扩展的配置。

  1. 扩展接口实现类,实现Container接口,例如SpringContainer.java
  2. 在资源META-INF.dubbo.internal文件夹下,有一个以Container接口全路径名称为名字的文件;
  3. 上述文件名中内容格式为 {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容器的。这算是一个首尾呼应嘛~

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

转载于:https://my.oschina.net/baoyuexing/blog/1791677

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值