spring + dubbo provider启动过程

spring + dubbo provider启动过程

前言

今天是卸载lol的第一天,在此定一个目标,不拿offer,不重装游戏。本着轻松写技术文章的原则,以后前言部分宏小白我都说一些有的没得,如果有幸看到我的文章,希望大家都是有共同爱好,可以畅所欲言,而不是为了生活孤独着煎熬的前行。

​ 如果你玩lol, 在lol你最喜欢玩的英雄是啥,宏小白最喜欢玩的是”文森特“,怀恋有杀人刀的日子,一刀一个小朋友。
img

欢迎来到dubbo provider启动分析(de lai lian meng)

说明

​ 本次分析的是dubbo2.6.1中dubbo-demo-provider的启动过程,配置文件采用xml的方式,过程比较枯燥,但可以让我们加深理解下spring容器初始化过程,以及结合dubbo,是如何完成服务的注册的,还有如何使用netty完成服务提供者的启动。

准备阶段

环境准备

本地一份dubbo的代码 2.6.1

自行安装zookeeper

开发工具 idea或者eclipse

样例代码

启动类入口Provider.java

public class Provider {

    public static void main(String[] args) throws Exception {
        //Prevent to get IPV6 address,this way only work in debug mode
        //But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
        System.setProperty("java.net.preferIPv4Stack", "true");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
        context.start();
//        ProtocolConfig.destroyAll();
        System.in.read(); // press any key to exit
    }

}

配置文件 dubbo-demo-provider.xml

详细的xml配置参考dubbo官方网站

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- provider's application name, used for tracing dependency relationship -->
    <dubbo:application name="demo-provider" logger="jcl"/>
	<!--提供者配置 delay设为-1时,表示延迟到Spring容器初始化完成时暴露服务 -->
    <dubbo:provider delay="-1" retries="0"  >
    </dubbo:provider>

   <!--注册中心 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"  />
 
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
	<!--对外暴露的一个服务 -->
    <dubbo:service  interface="com.alibaba.dubbo.demo.DemoService" group="g1" ref="demoService" filter="demo" deprecated="false" callbacks="1000" timeout="200000" accesslog="true">
        <!--方法配置 -->
        <dubbo:method name="say01" deprecated="true" />
        <!--协议配置 -->
    </dubbo:service>
    <dubbo:protocol accesslog="true" name="dubbo" port="-1" server="netty4" />

</beans>

服务调用过程不是本文分析的内容,这边不贴对应的code。

调整日志级别为debug
在这里插入图片描述

步骤

加载配置文件

进入容器构造方法

	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {

		super(parent);
        //加载xml文件 可以是多个
		setConfigLocations(configLocations);
		if (refresh) {
            //容器启动 重点方法
			refresh();
		}
	}
@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
            //构造beanFactory 完成xml文件标签的解析
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
             // 子类方法实现
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
                //先执行beanFactoryPostProcessors接口 再执行BeanPostProcessors接口
                //dobbo2.6版本中未定义
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
                //dobbo2.6版本中未定义
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
                // 初始国际化配置 未定义使用默认的DelegatingMessageSource
				initMessageSource();

				// Initialize event multicaster for this context.
                // 未定义使用默认的SimpleApplicationEventMulticaster
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
                // 子类方法实现
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
                // 实列化未配置懒加载的单列类 重点类
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.完成事件发送
				finishRefresh();
			}

			catch (BeansException ex) {
				 ........
			finally {
				 ........
			}
		}
	}

解析xml标签

在这里插入图片描述
这一步在obtainFreshBeanFactory()方法的refreshBeanFactory()完成

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   refreshBeanFactory();
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}

spring容器加载所有单列对象

针对xml文件依次加载对应的配置类

调用流程

单列加载过程 详细步骤不展开

AbstractApplicationContext.finishBeanFactoryInitialization();
	beanFactory.preInstantiateSingletons();
		this.getBean(beanName);
			this.doGetBean(name, (Class)null, (Object[])null, false);
				this.getSingleton(beanName, new ObjectFactory<Object>()
					AbstractBeanFactory.this.createBean(beanName, mbd, args);
						// 这一步可能生成代理类
						this.resolveBeforeInstantiation(beanName, mbdToUse);
						this.doCreateBean(beanName, mbdToUse, args);
						

最开始加载ApplicationConfig
在这里插入图片描述
在这里插入图片描述

加载dubbo服务

服务的创建

当加载beanName为com.alibaba.dubbo.demo.DemoService时,可以看出其对应是一个ServiceBean类
在这里插入图片描述
大胆猜测一下ServiceBean是服务提供者启动的入口,来看下ServiceBean的类结构

其中包括方法配置类,接口配置类,服务配置类,以及实现spring相关的aware接口,spring的相关接口会在spring的生命周期触发对应的方法
在这里插入图片描述

接着跟踪代码

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
    }

    if (instanceWrapper == null) {
    	// 创建单列的实列 返回的是一个BeanWrapper 装饰者模式
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

reslolved为true autowiredNecessary为false 所以进入this.instantiateBean方法
在这里插入图片描述

protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
    try {
        Object beanInstance;
        if (System.getSecurityManager() != null) {
            beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return AbstractAutowireCapableBeanFactory.this.getInstantiationStrategy().instantiate(mbd, beanName, AbstractAutowireCapableBeanFactory.this);
                }
            }, this.getAccessControlContext());
        } else {
            // 选择一个类初始化策略 进行初始化
            beanInstance = this.getInstantiationStrategy().instantiate(mbd, beanName, this);
        }
		//初始化的类包装进一个beanWrapper
        BeanWrapper bw = new BeanWrapperImpl(beanInstance);
        this.initBeanWrapper(bw);
        return bw;
    } catch (Throwable var6) {
        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", var6);
    }
}

调用BeanUtils采用默认的构造函数初始化ServiceBean 这时我们可以进入到SeviceBean类中了。
在这里插入图片描述
按照类加载的顺序, 先加载父类的静态初始化方法,再加载子类的初始化方法,再加载父类的,再加载子类的

其中父类静态包含以下两个方法

    /**
     * 自适应 Protocol 实现对象
     * 使用javaassit字节码工具生成Protocol$Adaptive类 生成有Adaptive注解的方法
     */
    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    /**
     * 自适应 ProxyFactory 实现对象 ProxyFactory$Adaptive
     */
    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

执行完构造函数以后

满足三个条件后singletonfactories加入该类用来解决循环依赖问题

//单列+允许循环依赖+当前类正在创建
mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);

在这里插入图片描述
在这里插入图片描述

服务的初始化

由于ServiceBean实现了InitializingBean接口,接着初始化过程进入afterPropertiesSet方法

类属性转载中可能包含多个bean的加载 包含其中配置的方法

<dubbo:method name=“say01” deprecated=“true” />
在这里插入图片描述

该方法我们单独拿出来看

    public void afterPropertiesSet() throws Exception {
        //如果provider为空,从spring容器中拿到provider配置并进行设置
        ........
        //如果Application配置为空 从容器拿到并配置
		 ........
        //设置module模块信息配置 可以为空
  		........
        //设置registry中心
           
        //设置监视器
    	..........
        //设置协议
    	.........
        //非延迟,直接暴露服务
        if (!isDelay()) {
            //服务暴露 重点方法
            export();
        }
    }
服务暴露

由于我们初始设置了delay="-1",服务的暴露阶段会在spring所有非懒加载的单列bean厚的阶段执行

由于实现了ApplicationListener的接口,监听了对应的ContextRefreshedEvent事件,我们又进入了ServiceBean方法体中

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }
 /**
     * 暴露服务 synchronized修饰,保证线程安全
     */
    public synchronized void export() {
   		 ........
        // 延迟暴露
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        // 立即暴露
        } else {
        	//方法逻辑与spring类似 核心逻辑在doXXX中
            doExport();
        }
    }

doExport()方法核心逻辑


 /**
     * 执行暴露服务
     */
    protected synchronized void doExport() {
       ...........
        // 校验 ApplicationConfig 配置。
        checkApplication();
        // 校验 RegistryConfig 配置。
        checkRegistry();
        // 校验 ProtocolConfig 配置数组。
        checkProtocol();
        // 读取环境变量和 properties 配置到 ServiceConfig 对象。
        appendProperties(this);
        // 校验 Stub 和 Mock 相关的配置
        checkStubAndMock(interfaceClass);
        // 服务路径,缺省为接口名
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        // 暴露服务
        doExportUrls();
        // 等待 qos
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }

private void doExportUrls() {
    	//获取所有注册中心
       List<URL> registryURLs = loadRegistries(true);  // @1
        for (ProtocolConfig protocolConfig : protocols) {
            //依次暴露
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);    // @2
       }
 }

生成的registryUrl格式为:

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&logger=jcl&pid=19364&qos.port=22222&registry=zookeeper&timestamp=1585492774389
doExportUrlsFor1Protocol分析

调用链

ServiceBean#onApplicationEvent
	ServiceConfig#export
		ServiceConfig#doExport
			doExportUrlsFor1Protocol
  1. 用Map存储该协议的所有配置参数,包括协议名称、dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig)、协议配置、服务提供Dubbo:service的属性。

  2. 如果dubbo:service有dubbo:method子标签,则dubbo:method以及其子标签的配置属性,都存入到Map中,属性名称加上对应的方法名作为前缀。dubbo:method的子标签dubbo:argument,其键为方法名.参数序号。

  3. 添加methods键值对,存放dubbo:service的所有方法名,多个方法名用,隔开,如果是泛化实现,填充genric=true,methods为"*";

  4. 根据是否开启令牌机制,如果开启,设置token键,值为静态值或uuid。

  5. 如果协议为本地协议(injvm),则设置protocolConfig#register属性为false,表示不向注册中心注册服务,在map中存储键为notify,值为false,表示当注册中心监听到服务提供者发送变化(服务提供者增加、服务提供者减少等事件时不通知。

  6. 解析服务提供者的IP地址与端口。

  7. 根据协议名称、协议host、协议端口、contextPath、相关配置属性(application、module、provider、protocolConfig、service及其子标签)构建服务提供者URI。

  8. 获取dubbo:service标签的scope属性,其可选值为none(不暴露)、local(本地)、remote(远程),如果配置为none,则不暴露。默认为local。

  9. 根据scope来暴露服务,如果scope不配置,则默认本地与远程都会暴露,如果配置成local或remote,那就只能是二选一。

具体服务暴露使用了自适应的类还有动态代理类JavassistProxyFactory 笔者再多准备一些知识后文进行讲解补充

Protocol A d a p t i v e 和 P r o x y F a c t o r y Adaptive和 ProxyFactory AdaptiveProxyFactoryAdaptive

本地服务暴露
在这里插入图片描述
远程服务暴露
在这里插入图片描述
可以看出这边都生成了一个Invoker 那么Invoker是什么呢 看一下大佬的总结:

/**
 * Invoker. (API/SPI, Prototype, ThreadSafe)
 *
 * Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它。
 * 它代表一个可执行体,可向它发起 invoke 调用。
 * 它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
 *
 * @see com.alibaba.dubbo.rpc.Protocol#refer(Class, com.alibaba.dubbo.common.URL)
 * @see com.alibaba.dubbo.rpc.InvokerListener
 * @see com.alibaba.dubbo.rpc.protocol.AbstractInvoker
 */
public interface Invoker<T> extends Node {

    /**
     * get service interface.
     *
     * @return service interface.
     */
    Class<T> getInterface();

    /**
     * invoke.
     *
     * @param invocation
     * @return result
     * @throws RpcException
     */
    Result invoke(Invocation invocation) throws RpcException;

}

待续。。。

总结

​ 这里主要笔者也想熟悉下spring容器的生命周期,所以没有完全按照第二个参考文章的流程跑一变。另外笔者在debug觉得绕来绕去很是头疼,这边也总结一下个人方法,再调试断点的时候,应该先抓住主干流程,不要一直往里面走,尤其是这种开源框架,调用层次很深,有时候就不知道把自己带到那里的,所以要有效的结合debug的堆栈信息,当不小心进入到方法体,想直接出来的时候,告诉大家一个小技巧 drop frame,可以退出当前方法栈帧,回到上一个方法。
在这里插入图片描述

参考

dubbo官方网站

java知音 ——知音不火,天理难容。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 配置dubbo 首先,在项目中添加dubbo相关的依赖,如下: ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.3</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.2.5.RELEASE</version> </dependency> ``` 在dubbo的配置文件中配置dubbo的注册中心和自己的服务,如下: ```xml <dubbo:application name="spring-mvc-dubbo"/> <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/> <dubbo:protocol name="dubbo" port="20880"/> <!-- 配置服务 --> <dubbo:service interface="com.test.service.UserService" ref="userService"/> <!-- 配置消费者 --> <dubbo:reference id="userService" interface="com.test.service.UserService"/> ``` 2. 配置Spring MVC 在Spring MVC的配置文件中配置dubbo的扫描包和相关的注解,如下: ```xml <!-- 配置dubbo扫描包 --> <dubbo:annotation package="com.test.service"/> <!-- 配置Spring MVC扫描包 --> <context:component-scan base-package="com.test.controller"/> <!-- 配置Spring MVC视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 配置Spring MVC静态资源访问 --> <mvc:resources mapping="/static/**" location="/static/"/> ``` 3. 编写Controller 在Controller中注入dubbo的服务,并使用相关注解进行调用,如下: ```java @Controller public class UserController { @Autowired private UserService userService; @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) @ResponseBody public User getUserById(@PathVariable("id") Long id) { return userService.getUserById(id); } } ``` 以上就是Spring MVC集成dubbo的基本配置步骤。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值