目录
前言:
在前面有关dubbo源码解析的博文中我们分析了api编码的形式配置服务消费者进行了服务的发布,下面我们继续使用api的形式进行暴露服务的消费。
1、dubbo服务提供者api
/**
* dubbo 服务消费者api形式进行服务消费
*/
@Test
public void consumerDubboService(){
//声明应用 dubbo生态质检服务调用是基于应用的
ApplicationConfig application = new ApplicationConfig("dubbo-refrence");
//涉及注册中心
RegistryConfig registryCenter = new RegistryConfig();
registryCenter.setAddress("zookeeper://182.92.189.235:2181");
//消费者消费
//设置消费者全局配置
ConsumerConfig consumerConfig = new ConsumerConfig();
//设置默认的超时时间
consumerConfig.setTimeout(1000*5);
ReferenceConfig<UserService> userConfigReference = new ReferenceConfig<>();
userConfigReference.setApplication(application);
// List<RegistryConfig> registryConfigs = new ArrayList<>();
// registryConfigs.add(registryCenter);
userConfigReference.setRegistry(registryCenter);
userConfigReference.setInterface(UserService.class);
//设置methodConfig 方法级别的dubbo参数包配置 比如方法名必填、重试次数、超时时间、负载均衡策略
MethodConfig methodConfig = new MethodConfig();
//方法名必填
methodConfig.setName("queryUserInfo");
//超时时间
methodConfig.setTimeout(1000 * 5);
//重试次数
methodConfig.setRetries(3);
//获取服务(并非真实的对象而是代理对象)
UserService userService = userConfigReference.get();
//调用对象方法
String info = userService.queryUserInfo();
System.out.println(info);
}
上述代码核心是构造了一个ReferenceConfig对象 该对应设置了应用信息配置(applicationConfig)、注册中心(RegistryConfig)、全局消费者配置(ConsumerConfig)、方法级别配置(MethodConfig)最终调用ReferenceConfig的get方法获取duubo服务对象进行服务消费。我们核心需要关注其get方法
2、获取服务对象
ReferenceConfig的get方法最终会调用其对应的init()方法进行dubbo服务代理对象的创建,最终业务代码调用代理对象的方法进行服务消费下面来关注一下init()方法
private void init() {
//如果该实例已经初始化 则返回
if (initialized) {
return;
}
//标记该实例初始化(先占坑) 防止重复实例化
initialized = true;
//消费接口非空校验
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
//获取全局服务消费者配置信息(ConsumerConfig) 逻辑:获取该对象配置的ConsumerConfig对象 并将系统属性中
//涉及到服务消费者dubbo.consumer.xxx 相关的配置属性通过setXxx方法填充到ConsumerConfig对象中
checkDefault();
//服务消费者dubbo.reference.xxx 相关的配置属性通过setXxx方法填充到ReferenceConfig对象中
appendProperties(this);
//设置是否泛化接口标识
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
//如果是泛化接口 class为GenericService
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
//普通接口 反射获取class
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//检查接口对应的class和method级别的参数配置
//所谓方法级别的配置可以针对接口中的的某个方法进行 超时、重试次数、负载均衡策略等的设置
checkInterfaceAndMethods(interfaceClass, methods);
}
//获取直连dubbo对应的url
//1、项目运行的时候jdk 命令启动 带参数java -D com.xxx.dubbo.DemoService=dubbo://localhost:28090
//2、直连配置多的时候 用-Ddubbo.resolve.file指定映射文件路径 java -Ddubbo.resolve.file=xxx.properties 在xxx.properties中(key为服务名,value为服务提供者url)
//3、xml配置直连 <dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService"url="dubbo://localhost:20890" />
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
resolveFile = userResolveFile.getAbsolutePath();
}
}
if (resolveFile != null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
resolve = properties.getProperty(interfaceName);
}
}
if (resolve != null && resolve.length() > 0) {
url = resolve;
if (logger.isWarnEnabled()) {
if (resolveFile != null && resolveFile.length() > 0) {
logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
} else {
logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
}
}
}
//根据优先级从ConsumerConfig、ModuleConfig、ApplicationConfig获取相应的配置信息
//比如注册中心、监控信息等
if (consumer != null) {
if (application == null) {
application = consumer.getApplication();
}
if (module == null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) {
monitor = consumer.getMonitor();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
//获取application(应用)相关的信息填充application对象中
//还会添加两个 有关dubbo(优雅)停机操作属性
checkApplication();
//添加mock、local、stub相关配置class
checkStubAndMock(interfaceClass);
//获取side、version、时间戳、pid、revision、methods、
//application应用信息、module模块、consumer消费者全局配置、method级别的配置等信息添加到map中
// (用与生成服务消费相关的Dubbo URL)
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (!isGeneric()) {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
map.put(Constants.INTERFACE_KEY, interfaceName);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
String prefix = StringUtils.getServiceKey(map);
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
appendAttributes(attributes, method, prefix + "." + method.getName());
checkAndConvertImplicitConfig(method, map, attributes);
}
}
//DUBBO_IP_TO_REGISTRY 属性可以显示设置服务消费者需要注册到注册中心的地址
// (docker服务部署服务消费者可能会出现注册不是真实消费者的注册地址)
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
//没有配置显示注册地址则获取宿主机真实地址
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
//添加MethodConfig相关的属性信息到系统上下文中
StaticContext.getSystemContext().putAll(attributes);
//创建代理对象
ref = createProxy(map);
//将对应代理对象包装成ConsumerModel 存放到ApplicationModel对应的集合中
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}上述代码比较长,但是逻辑和duubo服务发布者相似比较简单
1、该实例重复和接口为空校验
2、从系统变量获取属性设置到consumerConfig全局配置和ReferenceConfig中
3、获取接口class属性(1、普通接口:需要检查接口对应的class和method级别的参数配置 2泛化接口:GenericService)
4、获取直连dubbo 对应的服务接口
获取方式又分成
1、项目运行的时候jdk 命令启动 带参数
java -D com.xxx.dubbo.DemoService=dubbo://localhost:28090
2、服务多的时候 直连配置多的时候 用-Ddubbo.resolve.file指定映射文件路径
java -Ddubbo.resolve.file=xxx.properties
在xxx.properties中(key为服务名,value为服务提供者url)
3、xml配置直连 <dubbo:reference id="xxxService"
interface="com.alibaba.xxx.XxxService"url="dubbo://localhost:20890" />
api编码:userConfigReference.setUrl("dubbo://xxx.xxx.xx:22890")
注解: @Reference(url = "dubbo;//xxxxxx.xx:22200")
5、以consumerConfig -> moduleConfig -> applicationConfig的优先级顺序获取注册中心、监控等信息
6、获取side、version、时间戳、pid、revision、methods、application应用信息、
module模块、consumer消费者全局配置、method级别的配置等信息添加到map中
7、applicationConfig 配置信息填充
8、local、stub、mock相关class配置
9、获取宿主地址信息(可以显示配置ip)
10、添加MethodConfig相关的属性信息到系统上下文中
11、创建代理对象
12、将对应代理对象包装成ConsumerModel 存放到ApplicationModel对应的集合中上述代码中我们需要重点关注的是createProxy()方法
3、获取代理对象
private T createProxy(Map<String, String> map) {
//判断是不是dubbo的本地调用 如果是本地调用则不发起远程服务,只进行本地关联 但执行 Dubbo 的 Filter 链
//从 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务
//<dubbo:protocol name="injvm" />
// <dubbo:provider protocol="injvm" />
//<dubbo:service protocol="injvm" />
//<dubbo:consumer injvm="true" .../>
//<dubbo:provider injvm="true" .../>
//<dubbo:reference injvm="true" .../>
//<dubbo:service injvm="true" .../>
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
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();
}
//如果是本地调用获取本地调用的invoker JVM内部调用
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 {
//直连调用逻辑
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
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)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
//走注册中心
//获取多个注册中心以及注册中心对应的监控中心 遍历将注册中心和监控中心添加到Dubbo URL 集合中 后面创建Invoker对象用
//获取多个注册中心
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
//获取注册中心对应的监控中心
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
//添加到DubboURL集合中 该Dubbo URL 是注册中心转换的url
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls == null || urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
//注册中心单个则使用该注册中心创建对应的Invoker
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
//多个注册中心遍历处理 每一个注册中心创建一个Invoker添加到invokers集合中
//最终选则最新的注册地址
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
//省略日志打印
//使用ProxyFacrory创建dubbo服务对应的代理对象
return (T) proxyFactory.getProxy(invoker);
}创建代理对象的逻辑分成三个模块
1、本地调用Injvm
2、直连URL调用
3、注册中心注册
这三种方式最终都会生成对应的com.alibaba.dubbo.rpc.Invoker 通过com.alibaba.dubbo.rpc.ProxyFactory创建代理对象。
版权声明:本文为liushangzaibeijing原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。