Dubbo扩展点SPI简单实例
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: