聊聊dubbo的EchoService

496 阅读3分钟

首先来看一段代码来引入今天的话题

// 强制转换成EchoService
EchoService echoService = (EchoService) userReadService;
// 回声测试
Object status = echoService.$echo("Ok");
Assert.equals(status, "Ok");

以上代码正常情况下返回值status=Ok。可是很明显,我们写的Dubbo服务接口并没有与EchoService接口有任何继承关系,这是如何实现的呢?

这里涉及到另外一个知识点,Dubbo接口远程调用时实际上都是通过生成代理对象实现的。拿调用方举例,spring xml配置方式如下:

<dubbo:reference id="userReadService" interface="com.test.service.UserReadService" check="false" version="1.0.0" timeout="3000" />

那么xml又是如何转换为对象的呢?实际上是依赖spring.handlers机制初始化的。我们在dubbo的源码中可以看到以下配置,意思是使用com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler解析dubbo的xml配置文件。

好了,我们来看dubbo是如何解析reference配置的。在DubboNamespaceHandler类中我们可以找到以下代码:

public class DubboNamespaceHandler extends NamespaceHandlerSupport {


	public void init() {
	    
	      ......
	      
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        
        ......
        
    }

}

也就是说通过以上代码将reference配置内容转换成ReferenceBean。我们再来看ReferenceBean的定义:

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

ReferenceBean实现了FactoryBean,也就是说ReferenceBean本质上就是个spring的FactoryBean。到这里,熟悉spring的小伙伴一下子就明白了,xml配置最终会转化为ReferenceBean.getObject()类。

继续看:

public Object getObject() throws Exception {
        return get();
    }
    
public synchronized T get() {
        if (destroyed){
            throw new IllegalStateException("Already destroyed!");
        }
    	if (ref == null) {
    		init();
    	}
    	return ref;
    }
    

这里可以看到最终返回的是成员变量ref。继续往下看:

private void init() {
	    ......
        //attributes通过系统context进行存储.
        StaticContext.getSystemContext().putAll(attributes);
        ref = createProxy(map);
    }

终于看到了想要的信息ref = createProxy(map)。

private T createProxy(Map<String, String> map) {

......
   // 创建服务代理
   return (T) proxyFactory.getProxy(invoker);
}

这里关键信息来了:

public abstract class AbstractProxyFactory implements ProxyFactory {

    public <T> T getProxy(Invoker<T> invoker) throws RpcException {
        Class<?>[] interfaces = null;
        String config = invoker.getUrl().getParameter("interfaces");
        if (config != null && config.length() > 0) {
            String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
            if (types != null && types.length > 0) {
                interfaces = new Class<?>[types.length + 2];
                interfaces[0] = invoker.getInterface();
                interfaces[1] = EchoService.class;
                for (int i = 0; i < types.length; i ++) {
                    interfaces[i + 1] = ReflectUtils.forName(types[i]);
                }
            }
        }
        if (interfaces == null) {
            interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};
        }
        return getProxy(invoker, interfaces);
    }
    
    public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);

}

这里出现了预期的EchoService.class。

再来看getProxy(Invoker invoker, Class<?>[] types)的默认实现:

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
    
    ......

}

这里就是最终生成代理类的地方了。Proxy.getProxy方法核心逻辑是通过字符串拼接的方式生成一个代理类描述,并将EchoService设置为该代理的implement接口,然后再对生成的类进行动态加载。我们通过程序debug可以代码,生成的类其实是一个InvokerInvocationHandler对象。

我们来简单回顾下上文内容:

DubboNamespaceHandler#init
	--> registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
		--> ReferenceBean#getObject
			--> ReferenceConfig#init
				--> ReferenceConfig#createProxy
					--> AbstractProxyFactory#getProxy
						--> JavassistProxyFactory#getProxy
							--> Proxy#getProxy

通过以上过程最终生成了InvokerInvocationHandler代理对象,而InvokerInvocationHandler对象中的Invoker成员变量正式我们文章开头强制类型转换为EchoService的子类。

至此,我们就已经解读了为什么dubbo中的任何ReferenceBean都可以强制类型转换为EchoService了。

今天我们主要介绍的是xml形式的实现,通过springboot注解形式的实现过程类似,只是上层解析入口换成了springboot的SPI机制实现类。