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

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
分享到:
评论

相关推荐

    java基础编程必须知道的:SPI、反射、位运算

    SPI(Service Provider Interface)是 Java 中一种基于接口的服务发现机制,旨在实现代码解耦和可扩展性。通过 SPI,开发者可以定义一组接口,而具体的实现则由不同的提供者来提供,实现了解耦的目的。 SPI 机制使得...

    pacebox-netty 是一个基于netty和pacebox-core封装的便捷工具包.rar

    Hermes是一款基于Netty的可以支持百万级别的并发连接的高性能、高度可扩展的的网络通讯框架,它参考了dubbo和sofa-bolt的网络通讯模块的设计,hemers可以使用在IM、长连接等领域,...使用SPI扩展点加载,扩展性强 鉴权

    如何在一个全新模块中注册通用上传功能.pdf

    SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载...这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。

    SpringBoot内置生命周期事件详解 SpringBoot源码(十)

    SpringBoot中文注释项目Github地址: ...本篇接 SpringBoot事件监听机制源码分析(上) SpringBoot源码(九) 1 温故而知新 温故而知新,我们来简单回顾一下上篇的内容,...2)其次加载SPI扩展类EventPublishingRunListener。

    JAVA_API1.6文档(中文)

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    java jdk-api-1.6 中文 chmd

    java.awt.dnd Drag 和 Drop 是一种直接操作动作,在许多图形用户界面系统中都会遇到它,它提供了一种机制,能够在两个与 GUI 中显示元素逻辑相关的实体之间传输信息。 java.awt.event 提供处理由 AWT 组件所激发的...

    java api最新7.0

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    JDK_1_6 API

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    JavaAPI1.6中文chm文档 part1

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    [Java参考文档].JDK_API 1.6

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    JavaAPI中文chm文档 part2

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    [Java参考文档]

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    Java 1.6 API 中文 New

    javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...

    冰刃 IceSword

    1.16中进程栏只纳入基本功能,欲使用一些扩展的隐藏进程功能,请使用系统检查。 右键菜单: 1、刷新列表:请再次点击“进程”按钮,或点击右键,选择“刷新列表”。 2、结束进程:点击左键选中一项,或按住Ctrl键...

Global site tag (gtag.js) - Google Analytics