三、Dubbo源码学习笔记(一)之 Spring容器启动

一、准备工作

  1、下载dubbo源码,地址:https://github.com/alibaba/dubbo;并将源码导入eclipse

  2、启动zookeeper注册中心服务;

  3、通过debug的方式运行dubbo-demo,跟进分析源码;如下图运行DemoProvider.java中的main方法

 

二、源码分析

  DemoProvider.java调用com.alibaba.dubbo.container.Main.main(args)方法;代码如下:

public static void main(String[] args) {
        try {
       //args为空
if (args == null || args.length == 0) {
          //读取dubbo.properties中的dubbo.container的属性值,为空时通过loader.getDefaultExtensionName()获取默认值 String config
= ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName()); args = Constants.COMMA_SPLIT_PATTERN.split(config); } final List<Container> containers = new ArrayList<Container>();
       //遍历,获取指定名称的扩展加入到列表中
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."); //添加jvm关闭的钩子,用来在jvm关闭时关闭容器 if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) { 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; Main.class.notify(); } } } }); } //遍历列表,启动容器 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); } synchronized (Main.class) { while (running) { try { Main.class.wait(); } catch (Throwable e) { } } } }

示例中启动了两个容器,分别是:Log4jContainer,SpringContainer。Log4jContainer容器不用理会,主要是对日志初始化;重点在SpringContainer容器,其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(); }

查看ClassPathXmlApplicationContext源码会发现其调用了refresh()方法,该方法会对给定路径的文件进行解析装配,这里涉及到spring解析xml相关知识。Spring在解析xml文件时遇到dubbo名称空间时,会回调DubboNamespaceHandler,对所有dubbo的标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。如下图:spring.handlers文件指定了Dubbo命名空间处理器,spring.schemas文件配置了路径映射;DubboNamespaceHandler.java,DubboBeanDefinitionParser.java为Dubbo对spring的自定义的扩展。

官方文档给出的解析服务大致过程,如下:

  • 基于dubbo.jar内的META-INF/spring.handlers配置,Spring在遇到dubbo名称空间时,会回调DubboNamespaceHandler。
  • 所有dubbo的标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。
  • 在ServiceConfig.export()或ReferenceConfig.get()初始化时,将Bean对象转换URL格式,所有Bean属性转成URL的参数。
  • 然后将URL传给Protocol扩展点,基于扩展点的Adaptive机制,根据URL的协议头,进行不同协议的服务暴露或引用。

那么,ServiceConfig.export()和ReferenceConfig.get()这两个方法是在何时调用的了?这里要提到两个很关键的类:ServiceBean.javaReferenceBean.java

public class ServiceBean<T> extends ServiceConfig<T> 
    implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware{...}

对服务提供方:ServiceBean继承了ServiceConfig,并且实现了一些列的Spring接口。实现了InitializingBean,所以在bean的创建过程中,由于ServiceBean是单例模式(每个配置就是唯一的一个ServiceBean,包含的interface以及ref都已经固定了),每个接口配置<dubbo:service ...>被解析成ServiceBean实例, 其在初始化后,会调用afterPropertiesSet()方法完成整个dubbo配置的加载过程;实现了ApplicationListener,所以会在整个Spring容器加载完成后接收到消息,完成onApplicationEvent()方法的调用,该方法就会将该ServiceBean配置的接口等服务进行发布和注册。

public class ReferenceBean<T> extends ReferenceConfig<T> 
    implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean{...}

 同理,对服务消费方:ReferenceBean继承了ReferenceConfig,原理对应ServiceBean和ServiceConfig。不同之处在于,服务提供方ServiceBean需要在Spring启动的时候就提供服务,所以是通过onApplicationEvent在容器初始化完成之后立即就发布和注册了服务;但是服务消费方ReferenceBean是在程序需要的时候才会去执行,如果通过配置在Spring启动的过程中完成初始化是并不是合适的做法,而是在应用程序需要用到的时候,再去创建,所以ReferenceBean实现了FactoryBean,实现了接口方法getObject(),那么在spring容器getBean()方法获取对象实例其实调用的是ReferenceBean的getObject()方法完成消费地址的注册以及服务的订阅;返回的是

    // 接口代理类引用
    private transient volatile T ref;

而这个引用对于每个<dubbo:reference>也是唯一的,因为会缓存起来,所以虽然ReferenceBean看上去是工厂模式,实际上返回的都是同一个引用,所以模拟成工厂bean主要是为了在应用程序需要使用的时候才去创建对象,毕竟Proxy创建的成本还是比较大的,这样做也能很大程序提高程序的效率。

 

至此整个Spring容器启动之后服务提供者、服务消费者相关初始化工作基本完成。

 

转载于:https://www.cnblogs.com/francis-alice/p/6709096.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值