`
qq466862016
  • 浏览: 128424 次
  • 来自: 杭州
社区版块
存档分类
最新评论

Spi扩展加载机制

    博客分类:
  • java
阅读更多

 

 一、概述

       我们都知道 Java的SPI机制:(service provider interface ) 对于该机制的详情概述请自行百度。其实Spi简单的是提供给服务提供商的开发者使用和扩展的(其实是接口编程+策略模式+配置文件的一种方式)。

       场景:假如一个一个jar包中的一个接口A 分别有三个A接口的实现:B、C、D,我们在其他地方使用到了接口A的实现的时候那么我们不得不进行硬编码来指定对应的实现类,为了解耦我们就可以使用Spi机制来解决这个问题。

 

    此Spi扩展加载机制的约定:

           1.只扫描META-INF/services目录下的配置的spi服务接口文件。

           2.META-INF/services目录下的spi服务接口文件名称必须与指定的spi接口名称相同。

           3.META-INF/services目录下的spi服务接口文件内容包有对应接口的实现的类名称。

           4.spi服务接口必须包含有@Spi注解。

           5.spi服务接口实现类上包含有@SpiMeta 注解 可有可无。

          6.@Spi注解可有指定实现类是单例模式还是原型模式(默认为原型模式)。

          7.getExtension(String name)方法来获取对应服务实例(如果服务实现类中包含有@SpiMeta注解并且name不为空则以此name值来获取,否则按照此服务实现的类名来获取)。

        8.通过@Spi注解可以指定是原型模式还是单例模式。

        

 

二、目录结构示意图

     src

      --- com.shotdog.panda.core.registry.RegistryFactory

     ----com.shotdog.panda.core.registry.support.ZookeeperRegistryFactory

    META-INF

    ------------- services

   -------------------------com.shotdog.panda.core.registry.RegistryFactory(内容:com.shotdog.panda.core.registry.support.ZookeeperRegistryFactory) 

                              

 

package com.shotdog.panda.core.extension;

import com.shotdog.panda.core.common.annotation.Spi;
import com.shotdog.panda.core.common.annotation.SpiMeta;
import com.shotdog.panda.core.common.enums.Scope;
import com.shotdog.panda.core.exception.PandaFrameworkException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/***
 * Spi 扩展加载器
 * jdk  spi 增强
 * @date 2017-10-31 21:42:04
 */
public class ExtensionLoader<T> {

    private final static Logger log = LoggerFactory.getLogger(ExtensionLoader.class);

    private final static String PREFIX = "META-INF/services/";

    private Class<T> type;

    private ClassLoader classLoader;

    private volatile transient boolean init;

    private ConcurrentHashMap<String, Class<T>> extensions = new ConcurrentHashMap<String, Class<T>>();
    private ConcurrentHashMap<String, T> singletons;


    private static ConcurrentHashMap<Class<?>, ExtensionLoader<?>> extensionLoaders = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();


    public ExtensionLoader(Class<T> type) {
        this(type, Thread.currentThread().getContextClassLoader());
    }

    public ExtensionLoader(Class<T> type, ClassLoader classLoader) {
        this.type = type;
        this.classLoader = classLoader;
    }


    /**
     * 获取扩展加载器
     *
     * @param clz
     * @param <T>
     * @return
     */
    public synchronized static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clz) {

        checkInterface(clz);
        ExtensionLoader<?> extensionLoader = extensionLoaders.get(clz);
        if (extensionLoader != null) return (ExtensionLoader<T>) extensionLoader;

        return newExtensionLoader(clz);

    }


    /**
     * 创建一个新的扩展加载器
     *
     * @param clz
     * @param <T>
     * @return
     */
    private static <T> ExtensionLoader<T> newExtensionLoader(Class<T> clz) {

        ExtensionLoader<?> extensionLoader = extensionLoaders.get(clz);
        if (extensionLoader != null) return (ExtensionLoader<T>) extensionLoader;

        ExtensionLoader<T> loader = new ExtensionLoader<T>(clz);
        extensionLoaders.putIfAbsent(clz, loader);
        return loader;

    }

    /**
     * 检查接口是否为扩展加载的接口
     *
     * @param clz
     * @param <T>
     */
    private static <T> void checkInterface(Class<T> clz) {

        if (clz == null) {

            throw new PandaFrameworkException("extension loader service interface is not allow null");
        }

        if (!clz.isAnnotationPresent(Spi.class)) {

            throw new PandaFrameworkException(String.format("extension loader service interface has no (%s) annotation", Spi.class.getName()));
        }

    }


    /***
     * 获取扩展加载服务对象
     * @param name
     * @return
     */
    public T getExtension(String name) {

        if (!init) {
            this.initExtensionLoader();
        }

        Class<T> clz = this.extensions.get(name);
        if (clz == null) return null;

        try {


            Spi spi = type.getAnnotation(Spi.class);
            if (spi.scope() == Scope.SINGLETON) {

                return this.newInstanceToSingleton(clz, name);

            } else {

                return clz.newInstance();

            }
        } catch (Exception e) {
            throw new PandaFrameworkException(String.format("class:(%) newInstance fail", clz.getName()));

        }


    }

    /**
     * 创建一个新的单例对象
     *
     * @param clz
     * @param name
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private T newInstanceToSingleton(Class<T> clz, String name) throws IllegalAccessException, InstantiationException {

        synchronized (singletons) {

            T t = this.singletons.get(name);
            if (t != null) return t;

            t = clz.newInstance();

            this.singletons.putIfAbsent(name, t);
            return t;
        }
    }

    private void initExtensionLoader() {


        this.extensions = this.loadExtensions();
        this.singletons = new ConcurrentHashMap<String, T>();
        this.init = true;


    }

    private ConcurrentHashMap<String, Class<T>> loadExtensions() {

        String configFiles = PREFIX + this.type.getName();

        List<String> classNames = new ArrayList<String>();

        try {

            Enumeration<URL> url = null;
            if (this.classLoader == null) {
                url = ClassLoader.getSystemResources(configFiles);
            } else {
                url = this.classLoader.getResources(configFiles);
            }


            while (url.hasMoreElements()) {

                URL u = url.nextElement();

                this.parseUrl(u, classNames);

            }


        } catch (Exception e) {

            throw new PandaFrameworkException(String.format("extension loader loadExtensions :(%s) fail", configFiles), e);
        }


        return this.loadAllClasses(classNames);
    }

    /***
     * 加载所有的类
     * @param classNames
     * @return
     */
    private ConcurrentHashMap<String, Class<T>> loadAllClasses(List<String> classNames) {
        ConcurrentHashMap<String, Class<T>> classes = new ConcurrentHashMap<String, Class<T>>();
        if (classNames == null || classNames.isEmpty()) return classes;

        for (String className : classNames) {


            try {

                Class<T> clz = null;

                if (this.classLoader == null) {
                    clz = (Class<T>) Class.forName(className);
                } else {
                    clz = (Class<T>) Class.forName(className, false, this.classLoader);
                }

                this.checkExtensionType(clz);

                String name = this.getSpiName(clz);

                if (!classes.containsKey(name)) {

                    classes.putIfAbsent(name, clz);
                }
            } catch (Exception e) {

                throw new PandaFrameworkException(String.format("extension loader load class:(%s)  fail", className));
            }

        }


        return classes;
    }

    /**
     * 获取spi服务名称
     *
     * @param clz
     * @return
     */
    private String getSpiName(Class<T> clz) {

        SpiMeta spiMeta = clz.getAnnotation(SpiMeta.class);
        if (spiMeta != null && StringUtils.isNotBlank(spiMeta.name())) {
            return spiMeta.name();
        }

        return clz.getSimpleName();
    }

    /***
     *  检查扩展服务类类型
     * @param clz
     */
    private void checkExtensionType(Class<T> clz) {


        // 检查是否为 public 类型
        if (!Modifier.isPublic(clz.getModifiers())) {
            throw new PandaFrameworkException(String.format("class :(%s) is not public type class", clz.getName()));
        }

        // 检查是否为spi服务接口实现类

        if (!type.isAssignableFrom(clz)) {
            throw new PandaFrameworkException(String.format("class :(%s) is not assignable from interface ", clz.getName(), type.getName()));
        }
        // 检查是否有无参数默认构造方法

        Constructor<?>[] constructors = clz.getConstructors();

        for (Constructor constructor : constructors) {

            if (constructor.getParameterTypes().length == 0) {
                return;
            }
        }


        throw new PandaFrameworkException(String.format("class :(%s) is has not default constructor method ", clz.getName()));


    }

    /**
     * 解析每个 url
     *
     * @param url
     * @param classNames
     */
    private void parseUrl(URL url, List<String> classNames) throws IOException {

        InputStream inputStream = null;
        BufferedReader reader = null;
        try {

            inputStream = url.openStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            while (this.parseReadLine(reader, classNames) > 0) ;

        } catch (Exception e) {

            throw new PandaFrameworkException("extension loader parseUrl fail");
        } finally {


            try {

                if (reader != null) reader.close();
                if (inputStream != null) inputStream.close();

            } catch (Exception e) {
                // ignore
            }
        }

    }

    /**
     * 读取每一行
     *
     * @param reader
     * @param classNames
     * @return
     * @throws IOException
     */
    private int parseReadLine(BufferedReader reader, List<String> classNames) throws IOException {

        String line = reader.readLine();
        if (line == null) return -1;

        int index = line.indexOf("#");
        if (index >= 0) line = line.substring(0, index);

        line = line.trim();

        int length = line.length();
        if (length > 0) {

            if (line.indexOf(' ') >= 0 || line.indexOf('\t') > 0) {

                throw new PandaFrameworkException(String.format("Syntax error:(%s)", line));
            }

            // 校验首字母
            int codePoint = line.codePointAt(0);
            if (!Character.isJavaIdentifierStart(codePoint)) {

                throw new PandaFrameworkException(String.format("Syntax error:(%s)", line));
            }

            // 循环检查内容是否合法

            for (int i = Character.charCount(codePoint); i < length; i += Character.charCount(codePoint)) {

                codePoint = line.codePointAt(i);
                if (!Character.isJavaIdentifierPart(codePoint) && codePoint != '.') {

                    throw new PandaFrameworkException(String.format("Syntax error:(%s)", line));
                }

                if (!classNames.contains(line)) {
                    classNames.add(line);
                }


            }


        }


        return length + 1;
    }


}

 二、测试代码

 

 spi接口

   

@Spi
public interface EchoService {

    String echo(String name);
}

 

spi服务接口实现类

@SpiMeta(name = "hello")
public class HelloEchoService implements EchoService {
    public String echo(String name) {
        return "hello " + name;
    }
}

   

   在 META-INF/services/目录下 创建一个名称为:com.shotdog.panda.test.extension.EchoService 文件 

内容为:com.shotdog.panda.test.extension.HelloEchoService

 

测试代码如下:

 

 EchoService hello = ExtensionLoader.getExtensionLoader(EchoService.class).getExtension("hello");
        String shotdog = hello.echo("shotdog");

        log.info(shotdog);

 

 

1
0
分享到:
评论

相关推荐

    dubbo spi可扩展机制源码解析

    Dubbo SPI(Service Provider Interface)是阿里巴巴开源的Dubbo框架中的一个重要特性,它提供了一种动态发现服务提供者和加载实现类的机制,使得服务消费者无需关心服务提供者的具体实现,增强了系统的可扩展性和...

    Java类加载及SPI机制.pdf

    Java类加载机制和SPI(ServiceProviderInterface)机制是Java平台中极其重要的两个概念,它们对于Java程序的灵活性和可扩展性起到关键作用。下面详细解释这两个机制的概念、原理和应用场景。 类加载机制是Java...

    Java类加载及SPI机制.zip

    Java类加载机制是Java运行时环境的核心组成部分,它负责将.class文件从磁盘加载到内存中,转换为可执行的Java对象。理解类加载机制对于优化应用性能、深入理解JVM工作原理至关重要。SPI(Service Provider Interface...

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

    SPI 机制也可以用于框架的扩展,例如 Dubbo 的 SPI 实现。 Java SPI 机制的优点是: 1. 解耦:SPI 机制将接口和实现分离,使得程序更加灵活和可扩展。 2. 可扩展性:SPI 机制允许第三方提供实现类,扩展程序的功能...

    99-Apache InLong 的 SPI 扩展实践.pdf

    SPI(Service Provider Interface)是一种 Java 提供的标准机制,用于实现框架的扩展性和组件的可替换性。其基本思想是允许第三方实现特定接口,并通过特定的配置文件指定这些实现,从而达到扩展和替换的目的。 SPI...

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

    Dubbo的可扩展机制SPI(Service Provider Interface)是其核心特性之一,允许开发者根据需要动态扩展服务。SPI机制使得Dubbo作为一个高度可定制化的RPC框架,可以轻松地添加新的服务协议、序列化方式等组件。下面...

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

    在Dubbo中,Service Provider Interface (SPI) 是其核心的可扩展机制,它使得用户可以根据自己的需求自定义实现特定的功能,如协议、序列化、注册中心等。本文将深入探讨Dubbo的SPI源码,从理论到实践,揭示其设计...

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

    SPI(Service Provider Interface)是Java提供的一种服务发现和服务使用的机制,它允许开发者通过定义接口并在配置文件中指定实现类的方式,动态地加载服务实现。SPI的思想核心在于解耦,使得服务使用者无需关注服务...

    Dubbo源码分析之SPI

    SPI,全称Service Provider Interface,是Java提供的一种服务发现和服务加载机制,允许应用程序在运行时动态地查找和加载服务实现。这一机制使得开发者能够轻松地扩展应用,而无需修改核心代码。Dubbo作为一款高性能...

    Java 基础(8-8)-Java常用机制 - SPI机制详解.pdf

    Java SPI(Service Provider Interface)机制是一种服务提供发现机制,能够实现框架的扩展和替换组件,主要被框架的开发人员使用。SPI 机制的核心思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要...

    jdk spi机制

    SPI,全称Service Provider Interface,是Java平台提供的一种服务发现和动态加载的机制。它允许JVM在运行时动态地查找并加载实现特定接口的服务提供商。这种机制在Java生态系统中广泛应用,比如数据库驱动、日志框架...

    java对于MP3的spi

    Java平台扩展机制(Service Provider Interface, SPI)是一种允许第三方开发者扩展Java应用程序或库的功能的方式。在Java Sound API中,SPI扮演着关键角色,使得开发者能够添加对不同音频格式的支持,如MP3。MP3 SPI...

    SPI的简单示例

    SPI(Service Provider Interface)是Java平台提供的一种服务发现与加载机制,它允许应用程序在运行时动态查找并加载实现特定接口的服务提供商。SPI的核心概念在于,服务提供者通过实现指定的接口来表明他们能够提供...

    spi,Android组件化的SPI。.zip

    SPI(Service Provider Interface)是Java平台提供的一种服务发现机制,允许第三方开发者为已存在的接口提供实现。在Android系统中,虽然没有直接使用Java SPI,但Android的组件化思想与SPI有相似之处,尤其是在构建...

    通过SPI实现动态加载外部jar的实现类

    SPI(Service Provider Interface)是Java平台提供的一种服务发现机制,允许第...总之,SPI是Java中一种强大的机制,用于实现组件的动态加载和插件扩展。通过合理利用SPI,开发者可以构建更加灵活、可扩展的应用程序。

    携程apollo配置中心spi实现代码.zip

    SPI(Service Provider Interface)是Java的一种服务发现机制,允许第三方扩展已存在的程序功能。在Apollo中,SPI机制被用来加载和管理不同的配置源和服务提供者。 Apollo的核心组件包括ConfigService和Config...

    Java 动态加载jar文件示例

    首先,我们需要理解Java的类加载机制。Java中的类是由类加载器(ClassLoader)负责加载的。默认情况下,Java虚拟机(JVM)提供了三个内置的类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器...

    java spi实现工程

    Java SPI(Service Provider Interface)是Java平台提供的一种服务发现机制,允许JVM在运行时动态加载服务提供商。这个机制使得开发者可以扩展应用的功能,而无需修改原有代码。SPI的核心概念在于,服务接口由主程序...

    基于SPI机制实现外部插件热插拔的SpringBoot设计源码

    该项目为基于SPI机制设计的SpringBoot框架样例,旨在实现外部插件的热插拔功能,共计包含49个文件,其中...该样例项目通过SPI机制,提供了灵活的外部插件扩展和热更新能力,适用于需要动态加载和卸载插件的应用场景。

Global site tag (gtag.js) - Google Analytics