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:

 

posted on 2020-07-12 06:30  不想下火车的人  阅读(742)  评论(0编辑  收藏  举报

导航