dubbo系列之-生产者初始化-2021-01-16

背景

在dubbo的使用过程中,我们更多的是结合基于spring容器整合,既然有用到spring,dubbo的对象初始化自然也会托管到容器上。

spring容器启动过程

这块不是文章重点,画一张流程图,我把dubbo 切入的时间点标注上,红色背景的流程是xml配置方式的启动事件,绿色背景的是注解形式的启动事件,我们只分析xml的配置方式。

image

spring 自定义标签解析

自定义标签解析主要是扩展spring 的handlers和schemas(这两个文件在dubbo包下面的/META-INF)下面,是spring自定义标签的扩展规范,我们看看spring.handlers,还做了alibaba的包兼容

http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler

点开类 DubboNamespaceHandler,registerBeanDefinitionParser方法会将比如标签解析为 ServiceBean class放到BeanDefination中,等spring初始化的时候容器里面就会注入ServiceBean对象,在初始化阶段会将我们配置文件中的 这些键值对也放到ServiceBean对象的属性中去,这块不是重点我们不再深入。

public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        //......
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

ServiceBean的事件处理

这里我们看看 ServiceBean 类,实现了很多spring能力,其中 ApplicationListener 也就是说spring初始化完成后会调用该对象的事件回调方法onApplicationEvent(),我们点进去

public class ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener, BeanNameAware,
        ApplicationEventPublisherAware {
//org.apache.dubbo.config.spring.ServiceBean#onApplicationEvent
public void onApplicationEvent(ContextRefreshedEvent event) {
    if (!isExported() && !isUnexported()) {
        export();
    }
}

//org.apache.dubbo.config.spring.ServiceBean#export
public void export() {
    super.export();
    // 发布 ServiceBeanExportedEvent 服务暴露事件
    publishExportEvent();
}
//org.apache.dubbo.config.ServiceConfig#export 
public synchronized void export() {//线程安全
    checkAndUpdateSubConfigs();//检查配置
    if (shouldDelay()) {
        //延迟启动,如果有空dubbo2.4.8 之前的版本这边还是用的Thread.sleep()很粗暴
        DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
    } else {
        doExport();//服务暴露
    }
}

doExport(); 方法是服务暴露,这里我们后面文章分析,checkAndUpdateSubConfigs();这个方法才是我们关心的

初始化

dubbo 在暴露之前还有一系列的初始化工作

//org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs
public void checkAndUpdateSubConfigs() {
    // Use default configs defined explicitly on global configs
    completeCompoundConfigs();
    // 启动配置中心
    startConfigCenter();
    checkDefault();
    checkProtocol(); //检查协议配置
    checkApplication();//检查应用配置 下面有说明
    // if protocol is not injvm checkRegistry
    if (!isOnlyInJvm()) { //下面有说明
        checkRegistry();
    }
    this.refresh();//从配置中心刷新配置
    checkMetadataReport();

    if (ref instanceof GenericService) {//下面有说明
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            generic = Boolean.TRUE.toString();
        }
    } else {
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    if (local != null) {
        if ("true".equals(local)) {
            local = interfaceName + "Local";
        }
        Class localClass;
        try {
            localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(localClass)) {
            throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
        }
    }
    if (stub != null) {
        if ("true".equals(stub)) {
            stub = interfaceName + "Stub";
        }
        Class stubClass;
        try {
            stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if (!interfaceClass.isAssignableFrom(stubClass)) {
            throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
        }
    }
    //存根验证
    checkStubAndLocal(interfaceClass);
    //mock验证 下面分析
    checkMock(interfaceClass);
}

应用配置检查

checkApplication();

//org.apache.dubbo.config.AbstractInterfaceConfig#checkApplication
protected void checkApplication() {
    // for backward compatibility
    createApplicationIfAbsent();
    if (!application.isValid()) {
        throw new IllegalStateException("No application config found or it's not a valid config! " +
                "Please add  to your spring config.");
    }
    ApplicationModel.setApplication(application.getName());

这边有对application 进行判空,那么application 是在哪里赋值的呢,我们回到ServiceBean 这里实现了InitializingBean,等bean初始化完成之后会调用它的 afterPropertiesSet() 钩子方法,方法大致逻辑是从spring 的context 获取对应的bean 然后赋值

image

isOnlyInJvm 检查

if (!isOnlyInJvm()) { //
        checkRegistry();
}

如果 代表只暴露在jvm,怎么理解呢,如果服务是消费者,但是生产者也是在同一个服务中启动,那么调用是否还需要经过socket,当然是不用的,那么问题又来了,那为啥不直接走bean调用,答案是bean调用不能走dubbo 的调用流程,dubbo有一系列的filter wrapper 等,在统计限流缓存上面都有扩展,只有在injvm上才会走到,单纯的bean调用不会走到。

泛化

if (ref instanceof GenericService) {
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            generic = Boolean.TRUE.toString();
        }
    }

dubbo 泛化用的可能比较少,但是多半都听过主要是为了“省事”吧,我这样理解,业内经常有这样用法,网关不依赖dubbo api 包,通过泛化来实现调用,这样也避免频繁更新网关导致的上线下,二者也不需要做兼容,这个只是dubbo 消费者做的泛化,在dubbo生产者中也可以使用泛化,比如这样

public class MyGenericService implements GenericService {
    public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
        if ("sayHello".equals(methodName)) {
            return "Welcome " + args[0];
        } elseif()//.....
    }}

这样服务端也不需要麻烦,而且对于暴露到zk的节点字节也会减少,因为这样没有了方法的记录。所以dubbo 泛化在生产者,消费者都是适用的。

MOCK

checkMock(interfaceClass);这个mock 并不是接口api没开发之前mock给前端的意思,mock主要是用在生产者调用失败的时候执行的逻辑,比如调用超时,然后希望返回一段自己的临时数据,不希望app白屏,优点熔断的味道。


//返回值

//抛异常


上面有三种mock形式,第一种 mock 的类名一定要Mock结尾也要同样实现api接口,真是有种感觉dubbo里面什么都有。

总结

水文终于写完了,后面 服务暴露才是重点。

你可能感兴趣的:(dubbo系列之-生产者初始化-2021-01-16)