1.Dubbo中的SPI机制

1.JAVA SPI
java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类java.util.ServiceLoader
 
1.1接口
public interface UserService {
    public String getName(int id);
}

 

 
1.2具体实现
public class MyUserServiceImpl implements UserService{
    @Override
    public String getName(int id) {
        return "get -> name";
    }
}

 

 
1.3SPI目录文件
位置
文件内容
com.example.spi.service.MyUserServiceImpl
 
1.4演示spi实现
public static void main(String[] args) {
    Iterator<UserService> iterator = ServiceLoader.load(UserService.class).iterator();
    while (iterator.hasNext()){
        UserService service = iterator.next();
        System.out.println(service.getName(11));
    }
}

 

 
1.5 SPI原理
通过上面简单的demo,可以看到最关键的实现就是ServiceLoader这个类,可以看下这个类的源码,如下:
public final class ServiceLoader<S> implements Iterable<S> {




    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";


    // 被加载的类或接口
    private final Class<S> service;


    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;


    // 上下文对象
    private final AccessControlContext acc;


    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();


    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;


    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;


        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取目录下所有的类
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }


        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}

 

 
2.Dubbo SPI
dubbo作为一个高度可扩展的rpc框架,也依赖于java的spi,并且dubbo对java原生的spi机制作出了一定的扩展,使得其功能更加强大。
首先,从上面的java spi的原理中可以了解到,java的spi机制有着如下的弊端:
  • 只能遍历所有的实现,并全部实例化。
  • 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
  • 扩展如果依赖其他的扩展,做不到自动注入和装配。
  • 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持。
    dubbo的spi有如下几个概念:
    (1) 扩展点 :一个接口。
    (2) 扩展 :扩展(接口)的实现。
    (3) 扩展自适应实例: 其实就是一个Extension的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。dubbo会根据接口中的参数,自动地决定选择哪个实现。
    (4) @SPI :该注解作用于扩展点的接口上,表明该接口是一个扩展点。
    (5) @Adaptive: @Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。
    dubbo的spi也会从某些固定的路径下去加载配置文件,并且配置的格式与java原生的不一样,类似于property文件的格式:
firstFilter=com.example.dubbo.ProviderHelloFilter
 
下面将基于dubbo去实现一个简单的扩展实现。首先,要实现 org.apache.dubbo.rpc.Filter 这个接口,当然这个接口是被注解@SPI标注的可以扩展的:
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER})
public class ProviderHelloFilter implements Filter {


    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("hello ok====================================>>>>>");
        return invoker.invoke(invocation);
    }
}

 

然后,需要在duboo SPI的扫描目录下,添加配置文件,注意配置文件的名称要和扩展点的接口名称对应起来:
文件内容
firstFilter=com.example.dubbo.ProviderHelloFilter
还需要在dubbo的spring配置中显式的声明,使用上面自己实现的filter:
<dubbo:provider owner="xxx" timeout="6000" version="1.0.0" retries="0" executes="1000" filter="firstFilter"/>
最后,启动dubbo,调用service验证。 至此,dubbo的spi的demo也完成了。
 
dubbo spi的原理和jdk的实现稍有不同,具体实现类是 com.alibaba.dubbo.common.extension.ExtensionLoader
1.先调用loadExtensionClasses方法从下面三个目录来加载接口的实现类名
META-INF/dubbo/internal/
META-INF/dubbo/
META-INF/services/
2.调用createExtension方法反射实例化实现类对象并缓存起来
3.最后在调用filter的时候使用name找到对应的实现调用
 
3.总结
  关于spi的详解到此就结束了,总结下spi能带来的好处:
  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值