`
brandNewUser
  • 浏览: 456034 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java和dubbo中的SPI机制学习

阅读更多
 
关于java的SPI机制,可以参考:https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
 
为了实现在模块装配时的时候不在程序中动态指明,需要提供一种服务发现机制,为某个接口寻找服务实现的机制,就是将装配的控制权转移到程序之外,在模块化设计中这个机制尤其重要。
 
Java SPI(Service Provider Interface)的具体约定如下:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录中同时创建一个以服务接口命名的文件,该文件中的内容就是实现该服务接口的具体实现类。
 
Java中提供了一个用于服务实现查找的工具类:java.util.ServiceLoader。
 


 
 
该实例用于演示在maven环境下的service声明定义,居然在idea下无法调试启动...
 
resources下指定目录 META-INF/services目录(被写死在代码中):
 
public final class ServiceLoader<S
    implements Iterable<S
{
 
    private static final String PREFIX = "META-INF/services/";
 
 
 
注意需要将服务声明的文件名称定义为: example.spi.service.Service,与接口名称一致,其中的内容包括:
 
example.spi.service.PrintServiceImpl
example.spi.service.EchoServiceImpl
  
 
不同的实现类需要以换行符分隔,在SpiMain的主方法中,使用ServiceLoader进行加载操作:
 
public static void main(String[] args) {
    ServiceLoader<Service> serviceLoader = ServiceLoader.load(Service.class);
 
    for (Service service : serviceLoader) {
        service.printInfo();
    }
}
 
 
在ServiceLoader的load方法中,会初始化ServiceLoader.LazyIterator,实现了标准的迭代器接口Iterator(以及hasNext, next方法),其中hasNextService()方法中会从当前的ClassLoader加载 PREFIX("META-INF/services/") + service名称(class名称),
 
 
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}
 
 
 
在parse方法中会根据文件进行逐行解析,如果下一步存在对应的实现,该方法返回true,接着就可以访问nextService方法来获得下一个服务实现:
 
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
            "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
            "Provider " + cn  + " not a subtype");
    }
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
            "Provider " + cn + " could not be instantiated",
            x);
    }
    throw new Error();          // This cannot happen
}
 
 
 
将nextName(下一行实现的类名称)进行实例化,注意需要实现对应接口并有无参构造函数,并将实现放到providers这个Map中,以便下次直接使用(除非进行reload操作,否则不会更新该表,而reload操作是在ServiceLoader启动时初始化的)。
 
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}
 
 
虽然ServiceLoader也算是使用了延迟加载,但只能通过遍历所有获取,将接口的实现类全部加载并实例化一遍,而且只能通过Iterator形式获取,不能根据某个参数来获取。
 
参考:https://my.oschina.net/pingpangkuangmo/blog/508963 来分析dubbo的服务体系。
 
在dubbo中大量使用了该方式进行服务发现和服务注册,并进行了一定的扩展,实现了 com.alibaba.dubbo.common.extension.ExtensionLoader 类,内部注册服务的目录迁移为 META-INF/dubbo/internal类型:
 
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
 
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
  
 
在ExtensionLoader中,不仅会对其中的服务进行初始化,还可以像IOC一样,对引用的其他服务进行set操作,这部分参考ExtensionLoader.injectExtension(T instance)类,进行注入的类型必须为set开头方法,参数只有一个,方法为public,而且参数必须为接口(只有接口ExtensionFactory才能根据该接口进行查找服务实现类)。
 
private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}
 
 
如果接口的实现由多个,则此时采取的策略是,并不去注入一个具体的实现者,而是注入一个动态生成的实现者,这个动态生成的实现者的逻辑是确定的,能够根据不同的参数来使用不同的实现者实现相应的方法。这个动态生成的实现者的class就是ExtensionLoader的Class<?> cachedAdaptiveClass
 
在查找SPI Annotation,使用dubbo配置的扩展方式进行注册,例如在获取AdaptiveExtensionFactory时,使用的构造函数用于加载扩展点:
 
public AdaptiveExtensionFactory() {
    ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
    List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
    for (String name : loader.getSupportedExtensions()) {
        list.add(loader.getExtension(name));
    }
    factories = Collections.unmodifiableList(list);
}
  
而getExtension()方法能够查找具体的objectFactory(SPI,Spring)可以从对应的文件声明以及spring容器中查找具体的bean。
 
对于ExtensionFactory会从三个地方加载extensionClass:
 
private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }
 
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}
 
 
分别为 /META-INF/dubbo/internal, /META-INF/dubbo/,META-INF/services。
 
可以看出dubbo的扩展机制虽然与SPI比较类似,但额外增加了其他功能,例如可以根据接口名称来获取服务,服务声明文件支持A=B的方式,此时A为名称B为实现类;支持扩展IOC依赖注入功能,可以为Service之间的依赖关系注入相关的服务并保证单例。
 
 
  • 大小: 18.6 KB
分享到:
评论

相关推荐

    Dubbo源码分析之SPI

    SPI,全称Service Provider Interface,是Java提供的...在深入研究`dubbo-spi`和`java-spi`这两个文件夹中的源码,我们可以更深入地了解这两套SPI机制的实现细节和差异,这对于提升我们的Java和Dubbo开发技能大有裨益。

    36_SPI是啥思想?dubbo的SPI机制是怎么玩儿的?.zip

    Dubbo作为一款高性能的Java RPC框架,也引入了类似的SPI机制,但相对于Java内置的SPI,Dubbo的SPI机制更为强大和灵活。Dubbo的SPI机制主要由`dubbo-common`模块中的`ExtensionLoader`类实现,它支持以下特性: 1. ...

    dubbo-spi_mouse1oc_dubbospi_

    通过SPI机制,Dubbo可以灵活地扩展其功能,使得开发者能够按照自己的需求定制服务行为。在本文中,我们将深入探讨Dubbo SPI的工作原理及其相关知识点。 首先,我们需要理解什么是SPI。SPI(Service Provider ...

    dubbo spi可扩展机制源码解析

    总结来说,Dubbo SPI机制通过元数据配置、服务加载、服务发现、扩展点和动态代理等手段,实现了服务的高可扩展性和灵活性。开发者可以轻松地添加新的服务实现或扩展已有功能,而无需修改原有代码,符合开放封闭原则...

    Java SPI 机制(SPI实战+ServiceLoader源码分析+SPI 应用场景+破坏双亲委派)

    SPI 机制广泛应用于许多框架和库中,如 Dubbo、Spring、SpringBoot 等。 SPI 机制的核心组件是 ServiceLoader,负责加载和实例化提供者的实现类。ServiceLoader 的实现非常简单,可以分为三个大块:加载配置文件、...

    Java类加载及SPI机制.pdf

    总的来说,Java类加载机制和SPI机制是Java实现模块化编程和框架开发的重要手段。通过类加载机制,可以保证Java应用的类型安全和动态加载的需求;而SPI机制则提供了一种通用的方式来扩展Java平台功能,使得Java开发者...

    dubbo-thought.7z

    通过这个项目,你可以深入理解Java SPI和Dubbo SPI的区别,学习如何在实际项目中应用Dubbo的扩展机制。这对于提升Java开发者,特别是从事微服务开发的人员来说,是非常有价值的参考资料。同时,了解和掌握Dubbo SPI...

    深度解析Dubbo的可扩展机制SPI源码:从理论到实践,打造高效、稳定的分布式服务框架.rar

    Dubbo的SPI机制不仅提高了代码的可维护性和可扩展性,而且降低了服务治理的复杂性。通过深入理解和熟练运用这个机制,开发者能够更好地定制和优化自己的分布式服务框架,以应对各种业务场景和性能挑战。在实际项目中...

    服务治理中间件dubbo原理解析

    通过以上对Dubbo中间件和服务治理机制的解析,我们可以理解到SPI机制在Dubbo中的重要性。它不仅是Java标准的服务发现方式,也是Dubbo框架能够灵活扩展和强大功能的重要技术支撑。掌握Dubbo的SPI扩展机制,对于深入...

    DUBBO 中文帮助文档

    Dubbo 基于 Java SPI(Service Provider Interface)提供了一套扩展机制,允许开发者自定义实现 Dubbo 的各个组件,如协议、序列化、注册中心等。 综上,Dubbo 中文 API 提供了丰富的配置选项,使得开发者能够灵活...

    1.为什么dubbo要自己设计一套SPI.txt

    综上所述,Dubbo通过自定义SPI机制极大地提升了服务发现和管理的灵活性与效率。这些改进不仅解决了原始JDK SPI的一些问题,还为开发者提供了更为强大和易用的功能。通过这些增强特性,Dubbo能够在分布式系统中更好地...

    dubbo源码分析系列

    - SPI(Service Provider Interface)机制:Dubbo如何利用Java的SPI实现插件化设计,允许开发者灵活扩展和替换组件。 - 过滤器链:了解过滤器在Dubbo中的作用,以及如何编写自定义过滤器实现特定功能。 通过对...

    dubbo-spiDemo:精神病

    SPI机制是基于Java的ServiceLoader类实现的,是Java标准扩展的一种方式,用于解耦框架核心与扩展模块之间的依赖。在本项目"Dubbo-spiDemo"中,我们将深入探讨这个机制及其在实际应用中的演示。 首先,我们需要理解...

    dubbo-2.5.4官网源码下载

    - Dubbo采用SPI机制来实现组件的可插拔,开发者可以根据需求自定义实现。源码中的`META-INF/services`目录下可以看到各种服务接口的具体实现。 9. **API与配置解析**: - 分析源码可以理解Dubbo的API设计和配置...

    【JAVA分布式系列】dubbo

    【JAVA分布式系列】dubbo 在Java开发领域,分布式服务框架是实现大型系统高可用、可扩展的关键技术。...通过学习和掌握Dubbo,开发者可以更好地应对复杂的企业级应用场景,提升团队的开发效率和系统质量。

    dubbo资源 dubbo-admin dubbo demo

    【压缩包子文件的文件名称列表】"dubbo-master"通常表示这是Dubbo项目的主分支或者完整版本,可能包含了Dubbo的所有模块,包括核心库、服务治理模块、协议支持、SPI扩展机制、以及相关的示例和文档。 深入讲解这些...

    dubbo源码解析

    dubbo采用JavaSPI机制来实现扩展点的注册和加载。了解扩展点配置、加载流程及扩展点装饰的具体实现对于深入理解dubbo的动态扩展能力非常重要。 8. 代理机制在dubbo中扮演着重要角色,包括JDK代理和Javaassist代理两...

    dubbo学习后自己的源代码

    5. **SPI机制**:Dubbo的Service Provider Interface(SPI)机制允许我们动态扩展服务的实现,增强了系统的可扩展性。 通过这个项目,你不仅可以学习到Dubbo的基本使用,还可以深入理解分布式服务架构的设计和实现...

Global site tag (gtag.js) - Google Analytics