dubbo采用微内核+插件机制方便框架使用者自行扩展,这个插件机制的实现就是JDK的SPI(参见Java的SPI简单实例)。dubbo扩展了JDK的SPI,加入了注解和Spring容器的支持,给配置文件中的全限定实现类添加了自定义名称映射,支持按不同的映射参数加载不同的实现类等。按dubbo官方说法,进化后的SPI叫扩展点SPI,ServiceLoader变成了ExtensionLoader。接下来看一个例子,使用扩展点SPI来自定义一个LoadBalance。
先看dubbo源码中的LoadBalance接口:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance;
import java.util.List;
/**
* LoadBalance. (SPI, Singleton, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load-Balancing</a>
*
* @see org.apache.dubbo.rpc.cluster.Cluster#join(Directory)
*/
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
我们看到该接口使用了@SPI和@Adaptive注解,而且它们都有参数。@SPI用来标记扩展接口,@Adaptive使ExtesionLoader知道应该加载哪个扩展实现类。同ServiceLoader一样,ExtesionLoader也是主要的实现类:
/**
* Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
* will be thrown.
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
下面自定义一个LoadBalance实现,扩展点实现起来非常简单。项目原代码参见一个spring boot集成dubbo的小例子,只需修改消费者,服务提供者不用动。下面重点把修改点列出来:
1、消费者项目新增配置文件:resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
demo=com.example.dubbo.democonsumer.loadbalance.DemoLoadBalance
![](https://i-blog.csdnimg.cn/blog_migrate/2c18de62ebf01822b23d9c1ad10d92c6.png)
2、消费者项目新增自定义LoadBalance实现:
package com.example.dubbo.democonsumer.loadbalance;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import java.util.List;
public class DemoLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> list, URL url, Invocation invocation) throws RpcException {
System.out.printf("DemoLoadBalance coming, url %s", url.toFullString());
return list.get(0);
}
}
3、消费者项目的Controller类新增LoadBalance注解:
package com.example.dubbo.democonsumer.controller;
import com.example.dubbo.demo.domain.DemoBean;
import com.example.dubbo.demo.service.DemoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.*;
/**
* 服务消费者
*/
@RestController
public class ConsumerController {
// 引入API
@Reference(check = false, loadbalance = "demo")
DemoService demoService;
@ResponseBody
@RequestMapping("/hello")
public String sayHelo(@RequestParam(value = "msg") String msg) {
return demoService.sayHelo(msg);
}
@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(DemoBean demoBean) {
return demoService.login(demoBean);
}
}
完事了。启动消费者和两个服务提供者(IDEA启动多实例参见IDEA同一项目启动多个实例),注意两个provider配置的rpc端口不能相同:
![](https://i-blog.csdnimg.cn/blog_migrate/8382306b49fef7b946a2c1f12c295799.png)
浏览器输入消费者hello接口跑一次:
![](https://i-blog.csdnimg.cn/blog_migrate/b096ae28a0a78d2cfb7470353373831c.png)
从消费者日志可以看到进入我们自定义的LoadBalance:
![](https://i-blog.csdnimg.cn/blog_migrate/10b5e01205075df83f269047ed13d2a6.png)