Spi扩展加载机制

 

 一、概述

       我们都知道 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 {

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

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

    private Class type;

    private ClassLoader classLoader;

    private volatile transient boolean init;

    private ConcurrentHashMap> extensions = new ConcurrentHashMap>();
    private ConcurrentHashMap singletons;


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


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

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


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

        checkInterface(clz);
        ExtensionLoader extensionLoader = extensionLoaders.get(clz);
        if (extensionLoader != null) return (ExtensionLoader) extensionLoader;

        return newExtensionLoader(clz);

    }


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

        ExtensionLoader extensionLoader = extensionLoaders.get(clz);
        if (extensionLoader != null) return (ExtensionLoader) extensionLoader;

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

    }

    /**
     * 检查接口是否为扩展加载的接口
     *
     * @param clz
     * @param 
     */
    private static  void checkInterface(Class 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 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 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();
        this.init = true;


    }

    private ConcurrentHashMap> loadExtensions() {

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

        List classNames = new ArrayList();

        try {

            Enumeration 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> loadAllClasses(List classNames) {
        ConcurrentHashMap> classes = new ConcurrentHashMap>();
        if (classNames == null || classNames.isEmpty()) return classes;

        for (String className : classNames) {


            try {

                Class clz = null;

                if (this.classLoader == null) {
                    clz = (Class) Class.forName(className);
                } else {
                    clz = (Class) 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 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 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 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 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);

 

 

你可能感兴趣的:(java)