前言
在前面的章节中,我们已经完成Dubbo服务暴露的流程分析。今天我们一起来看Dubbo怎么引用这些服务的。
关于服务引用,Dubbo有两种方式。一种是基于注册中心进行服务引用,一种是服务直连进行引用。服务直连主要用于测试联调阶段,生产环境不推荐使用。它的配置也比较简单,在消费者端指定服务url即可。
<dubbo:reference id="infoUserService"
interface="com.viewscenes.netsupervisor.service.InfoUserService"
url="dubbo://192.168.139.129:20880"/>
本文将重点分析通过注册中心方式,服务引用的过程。开始之前,我们再回顾一下消费者端项目整体的XML文件配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/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-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消费者方应用信息,用于计算依赖关系 -->
<dubbo:application name="dubbo_consumer"/>
<!-- 用于配置连接注册中心相关信息 -->
<dubbo:registry protocol="zookeeper" address="192.168.139.131:2181" client="zkclient" />
<!-- 引用配置 用于创建一个远程接口服务代理 -->
<dubbo:reference id="infoUserService"
interface="com.viewscenes.netsupervisor.service.InfoUserService"/>
</beans>
一、引用的是什么?
在上述配置文件中,infoUserService
只是Spring中的一个Bean。在代码中,我们通过getBean()
来获取它,然后调用它的方法。
我们在RPC基本原理以及如何用Netty来实现RPC这篇文章中已经分析过,这里的bean其实是一个FactoryBean
,通过它可以返回一个接口的代理对象,完成调用逻辑的处理。
在代码中,我们通过这样来获取这个Bean。
InfoUserService infoUserService = (InfoUserService) context.getBean("infoUserService");
这里的infoUserService
就是一个代理对象,比如像proxy@2903
这种。当然了,它必然会包含一个InvocationHandler
,如下所示:
服务引用就是其实通过动态代理给接口创建代理对象并返回。当我们调用接口方法时,则调用到InvocationHandler
相关方法,处理相关请求。
二、从哪里开始?
在上面的配置文件中,dubbo:reference
对应的是ReferenceBean
处理类。那么Spring在实例化这个Bean的时候,就调用到里面方法。我们先看看它的类结构
package com.alibaba.dubbo.config.spring;
public class ReferenceBean<T> extends ReferenceConfig<T> implements
FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
private transient ApplicationContext applicationContext;
public ReferenceBean() {
super();
}
public ReferenceBean(Reference reference) {
super(reference);
}
//设置applicationContext
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
//返回代理对象
public Object getObject() throws Exception {
return get();
}
//返回代理对象接口类型
public Class<?> getObjectType() {
return getInterfaceClass();
}
//是否为单例
@Parameter(excluded = true)
public boolean isSingleton() {
return true;
}
//Bean初始化方法
public void afterPropertiesSet() throws Exception {
if(isInit()){
getObject();
}
}
}
首先,映入眼帘的是FactoryBean
接口。有了它,则证明当前bean是一个工厂Bean,所以我们重点关注它的getObject
方法,当我们的服务被注入到其他类中时,就会调用到此方法。
其次,是InitializingBean
接口。它是Bean的初始化方法,当Bean完成实例化之后被调用。在上述代码中,当init()
条件判断成立后调用getObject
。我们可以在配置文件中以这种方式来激活它:init="true"
以上就是服务引用的两个不同时机。再专业点来讲,一个是懒汉式,一个是饿汉式。Dubbo默认是懒汉式引用,需要时才会调用。
上述代码中,getObject
方法会调用到父类的init
方法。这个方法内容比较多,前面的部分是各种配置检查、赋值,然后就是创建代理对象返回。
private void init() {
//避免重复初始化
if (initialized) {
return;
}
//省略相关配置检查、赋值等代码...
//创建代理
ref = createProxy(map);
}
上述代码中,重点是createProxy
方法。它负责创建Invoker实例和创建代理对象。
也许我们还记得,在服务暴露的时候,会分为本地暴露和远程暴露。在这里,服务引用也是这样。Dubbo首先判断服务引用是本地引用还是远程引用,默认是远程引用。然后判断是否为直连服务,根据协议调用refer
方法创建invoker对象 。最后创建服务代理对象并返回。
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
//获取配置判断是否为本地引用
if (isInjvm() == null) {
if (url != null && url.length() > 0) {
isJvmRefer = false;
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
// by default, reference local service if there is
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
//本地引用
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
//远程引用
} else {
//url不为空则代表是服务直连
if (url != null && url.length() > 0) {
//切割多个url
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map