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
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端口不能相同:
瀏覽器輸入消費者hello接口跑一次:
從消費者日志可以看到進入我們自定義的LoadBalance: