首页 > 其他分享 >再聊SPI机制

再聊SPI机制

时间:2024-03-26 09:11:59浏览次数:33  
标签:return name class clazz SPI classes 机制 再聊 final

前言

去年更新了一系列和SPI相关的内容,最近因为业务需要,我又基于业务场景,实现了一版。对于什么是spi,很久之前有写过一篇文章,java之spi机制简介感兴趣的朋友可以蛮看一下

需求分析

用过原生jdk提供的spi的朋友,应该会知道原生jdk的spi有个缺陷,就是没法实现按需加载,因此本文的实现就是来解决这个问题。

自定义SPI

核心代码

@Slf4j
public final class SpiLoader<T> {
    
    
    private static final String SPI_DIRECTORY = "META-INF/spi/";
    
    private static final Map<Class<?>, SpiLoader<?>> LOADERS = new ConcurrentHashMap<>();
    
    private final Class<T> clazz;
    
    private final ClassLoader classLoader;
    
    private final Holder<Map<String, ClassEntity>> cachedClasses = new Holder<>();
    
    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    
    private final Map<Class<?>, Object> targetInstances = new ConcurrentHashMap<>();
    

    
    /**
     * Instantiates a new Extension loader.
     *
     * @param clazz the clazz.
     */
    private SpiLoader(final Class<T> clazz, final ClassLoader cl) {
        this.clazz = clazz;
        this.classLoader = cl;
        if (!Objects.equals(clazz, SpiFactory.class)) {
            SpiLoader.getExtensionLoader(SpiFactory.class).getExtensionClassesEntity();
        }
    }
    
    /**
     * Gets extension loader.
     *
     * @param <T>   the type parameter
     * @param clazz the clazz
     * @param cl    the cl
     * @return the extension loader.
     */
    public static <T> SpiLoader<T> getExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
        
        Objects.requireNonNull(clazz, "extension clazz is null");
        
        if (!clazz.isInterface()) {
            throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
        }

        SpiLoader<T> extensionLoader = (SpiLoader<T>) LOADERS.get(clazz);
        if (Objects.nonNull(extensionLoader)) {
            return extensionLoader;
        }
        LOADERS.putIfAbsent(clazz, new SpiLoader<>(clazz, cl));
        return (SpiLoader<T>) LOADERS.get(clazz);
    }
    
    /**
     * Gets extension loader.
     *
     * @param <T>   the type parameter
     * @param clazz the clazz
     * @return the extension loader
     */
    public static <T> SpiLoader<T> getExtensionLoader(final Class<T> clazz) {
        return getExtensionLoader(clazz, SpiLoader.class.getClassLoader());
    }
    

    
    /**
     * Gets target.
     *
     * @param name the name
     * @return the target.
     */
    public T getTarget(final String name) {
        if (StringUtils.isBlank(name)) {
            throw new NullPointerException("get target name is null");
        }
        Holder<Object> objectHolder = cachedInstances.get(name);
        if (Objects.isNull(objectHolder)) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            objectHolder = cachedInstances.get(name);
        }
        Object value = objectHolder.getValue();
        if (Objects.isNull(value)) {
            synchronized (cachedInstances) {
                value = objectHolder.getValue();
                if (Objects.isNull(value)) {
                    createExtension(name, objectHolder);
                    value = objectHolder.getValue();
                }
            }
        }
        return (T) value;
    }
    
    /**
     * get all target spi.
     *
     * @return list. target
     */
    public List<T> getTargets() {
        Map<String, ClassEntity> extensionClassesEntity = this.getExtensionClassesEntity();
        if (extensionClassesEntity.isEmpty()) {
            return Collections.emptyList();
        }
        if (Objects.equals(extensionClassesEntity.size(), cachedInstances.size())) {
            return (List<T>) this.cachedInstances.values().stream()
                    .map(Holder::getValue).collect(Collectors.toList());
        }
        List<T> targets = new ArrayList<>();
        List<ClassEntity> classEntities = new ArrayList<>(extensionClassesEntity.values());
        classEntities.forEach(v -> {
            T target = this.getTarget(v.getName());
            targets.add(target);
        });
        return targets;
    }
    
    @SuppressWarnings("unchecked")
    private void createExtension(final String name, final Holder<Object> holder) {
        ClassEntity classEntity = getExtensionClassesEntity().get(name);
        if (Objects.isNull(classEntity)) {
            throw new IllegalArgumentException(name + " name is error");
        }
        Class<?> aClass = classEntity.getClazz();
        Object o = targetInstances.get(aClass);
        if (Objects.isNull(o)) {
            try {
                targetInstances.putIfAbsent(aClass, aClass.newInstance());
                o = targetInstances.get(aClass);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException("Extension instance(name: " + name + ", class: "
                        + aClass + ")  could not be instantiated: " + e.getMessage(), e);
                
            }
        }
        holder.setValue(o);
    }
    
    /**
     * Gets extension classes.
     *
     * @return the extension classes
     */
    public Map<String, Class<?>> getTargetClassesMap() {
        Map<String, ClassEntity> classes = this.getExtensionClassesEntity();
        return classes.values().stream().collect(Collectors.toMap(ClassEntity::getName, ClassEntity::getClazz, (a, b) -> a));
    }
    
    private Map<String, ClassEntity> getExtensionClassesEntity() {
        Map<String, ClassEntity> classes = cachedClasses.getValue();
        if (Objects.isNull(classes)) {
            synchronized (cachedClasses) {
                classes = cachedClasses.getValue();
                if (Objects.isNull(classes)) {
                    classes = loadExtensionClass();
                    cachedClasses.setValue(classes);
                }
            }
        }
        return classes;
    }
    
    private Map<String, ClassEntity> loadExtensionClass() {
        Map<String, ClassEntity> classes = new HashMap<>(16);
        loadDirectory(classes);
        return classes;
    }
    
    /**
     * Load files under SPI_DIRECTORY.
     */
    private void loadDirectory(final Map<String, ClassEntity> classes) {
        String fileName = SPI_DIRECTORY + clazz.getName();
        try {
            Enumeration<URL> urls = Objects.nonNull(this.classLoader) ? classLoader.getResources(fileName)
                    : ClassLoader.getSystemResources(fileName);
            if (Objects.nonNull(urls)) {
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    loadResources(classes, url);
                }
            }
        } catch (IOException t) {
            log.error("load extension class error {}", fileName, t);
        }
    }
    
    private void loadResources(final Map<String, ClassEntity> classes, final URL url) throws IOException {
        try (InputStream inputStream = url.openStream()) {
            Properties properties = new Properties();
            properties.load(inputStream);
            properties.forEach((k, v) -> {
                String name = (String) k;
                String classPath = (String) v;
                if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
                    try {
                        loadClass(classes, name, classPath);
                    } catch (ClassNotFoundException e) {
                        throw new IllegalStateException("load extension resources error", e);
                    }
                }
            });
        } catch (IOException e) {
            throw new IllegalStateException("load extension resources error", e);
        }
    }
    
    private void loadClass(final Map<String, ClassEntity> classes,
                           final String name, final String classPath) throws ClassNotFoundException {
        Class<?> subClass = Objects.nonNull(this.classLoader) ? Class.forName(classPath, true, this.classLoader) : Class.forName(classPath);
        if (!clazz.isAssignableFrom(subClass)) {
            throw new IllegalStateException("load extension resources error," + subClass + " subtype is not of " + clazz);
        }

        ClassEntity oldClassEntity = classes.get(name);
        if (Objects.isNull(oldClassEntity)) {
            ClassEntity classEntity = new ClassEntity(name,  subClass);
            classes.put(name, classEntity);
        } else if (!Objects.equals(oldClassEntity.getClazz(), subClass)) {
            throw new IllegalStateException("load extension resources error,Duplicate class " + clazz.getName() + " name "
                    + name + " on " + oldClassEntity.getClazz().getName() + " or " + subClass.getName());
        }
    }
    
    /**
     * The type Holder.
     *
     * @param <T> the type parameter.
     */
    private static final class Holder<T> {
        
        private volatile T value;

        
        /**
         * Gets value.
         *
         * @return the value
         */
        public T getValue() {
            return value;
        }
        
        /**
         * Sets value.
         *
         * @param value the value
         */
        public void setValue(final T value) {
            this.value = value;
        }
    }
    
    private static final class ClassEntity {
        
        /**
         * name.
         */
        private final String name;
        

        /**
         * class.
         */
        private Class<?> clazz;
        
        private ClassEntity(final String name, final Class<?> clazz) {
            this.name = name;
            this.clazz = clazz;
        }
        
        /**
         * get class.
         *
         * @return class.
         */
        public Class<?> getClazz() {
            return clazz;
        }
        
        /**
         * set class.
         *
         * @param clazz class.
         */
        public void setClazz(final Class<?> clazz) {
            this.clazz = clazz;
        }
        
        /**
         * get name.
         *
         * @return name.
         */
        public String getName() {
            return name;
        }
        

    }
}

代码解读:

从classpath类路径下查找/META-INF/spi/接口文件,并解析相关文件,将解析后的key和class类名放入本地缓存,最后根据业务实际需要,按需将class实例化为对象

示例

以mock一个不同日志门面打印为例子

1、创建日志接口

public interface LogService {

    void info(String msg);
}

2、创建日志实现

public class Log4jService implements LogService {
    @Override
    public void info(String msg) {
        System.out.println(Log4jService.class.getName() + " info: " + msg);
    }
}

3、在具体实现的classpath目录下创建

/META-INF/spi/com.github.lybgeek.log.LogService文件,并填入如下内容

log4j=com.github.lybgeek.log.Log4jService

4、测试

public class LogMainTest {
    public static void main(String[] args) {
        LogService logService = SpiLoader.getExtensionLoader(LogService.class).getTarget("log4j2");
        logService.info("log4j2-hello");

        logService = SpiFactoriesLoader.loadFactories().getTarget("log4j",LogService.class);
        logService.info("log4j-hello");

    }
}

可以看到控制台输出如下内容

com.github.lybgeek.log.Log4j2Service info: log4j2-hello
com.github.lybgeek.log.Log4jService info: log4j-hello

总结

本文主要是实现原生SPI不支持按需加载的能力,其次本文的核心实现其实是搬dubbo的spi能力,因为我们业务场景比较简单,并不需要dubbo那么灵活的spi能力,因此在实现时,就仅仅搬了dubbo的一部分能力扩展

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-custom-spi

标签:return,name,class,clazz,SPI,classes,机制,再聊,final
From: https://www.cnblogs.com/linyb-geek/p/17952399

相关文章

  • sping 事务失效的7中情况
    1、抛出检查异常导致事务不能正确回滚原因:Spring默认只会回滚非检查异常解决:配置rollbackFor属性@Transactional(rollbackFor=Exception.class)2、业务方法内自己try-catch异常导致事务不能正确回滚原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目......
  • spinlock和mutex选用方法
    资料参考:《宋宝华Linux设备驱动开发详解》 spinlock和mutex选用方法如下:1、当锁不能被获取到时,使用互斥体的开销仅仅是上下文切换,使用自旋锁的开销是等待获取自旋锁(由临界区的执行时间决定)。若临界区比较小,适合使用自旋锁。若临界区较大,适合使用互斥锁2、互斥锁所保护的临......
  • 自旋锁spinlock
    参考资料:《正点原子Linux驱动教程》《宋宝华Linux设备驱动开发详解》 原子操作只能对整型变量或者bit位进行保护,但是实际使用中,不可能只有整型变量或者bit位等临界区 自旋锁spinlock也是一种典型的对临界资源进行互斥访问的手段,其名称来源自它的工作方式。当一个线程要......
  • MySQL中的MVCC实现机制
     MySQL中的MVCC实现机制   一、什么是MVCC?  MVCC,全称Multi-VersionConcurrencyControl,即多版本并发控制。MVCC是一种无锁的并发控制方法,一般在数据库管理系统中,用于实现对数据库的并发访问。  我们知道,在数据库中,对数据的操作主要有2中,分别是读和写,而在并发场景......
  • 深度学习(18)--注意力机制详解
    目录一.什么是注意力机制(AttentionMechanism)二.什么是注意力(Attention)三.自注意力机制(Self-AttentionMechanism)3.1.对输入数据进行Embedding操作3.2.q,k操作3.3.v操作 3.4.代码实现四.多头自注意力机制(Multi-headSelf-AttentionMachanism) 4.1.q,k操作4.2.v......
  • Nature communications︱东北师范大学王岭团队在放牧草地生态系统功能维持机制的研究中
    草地作为陆地上最大的被管理的生态系统,其强烈地受到人类活动的频繁干扰和利用,家畜放牧是该系统最主要的干扰和利用方式,对草地生物多样性及生态系统功能起重要调控作用。过度放牧等不科学的放牧方式是造成草地退化、生物多样性降低的主要原因,特别是在我国北方,90%的草地因过度放......
  • 用生动的语言讲mysql索引机制与B+树形象化理解
    索引,index,是什么呢,假如说,没有索引,比如你要点名,你就得挨个问,你是不是某某某,效率奇低,但是,当他们有了独一无二的号数或者名字,就可以免于追寻,一觅即中,这就是索引存在的意义但是,凡事有利有弊,索引增加了查询的效率,但是却降低了增删改的效率,比如说,班级加入一名新同学,你还要给他一个号数,......
  • 【微前端】微前端的零信任(Zero-trust)机制应用
    【微前端】微前端的零信任(Zero-trust)机制应用目录【微前端】微前端的零信任(Zero-trust)机制应用零信任如何应用于前端将零信任扩展到微前端1\.独立的构建过程2\.微前端隔离3\.用于微前端组件的身份验证结论推荐超级课程:Docker快速入门到精通Kub......
  • 内核睡眠机制和等待队列
    内核睡眠机制:进程通过睡眠机制释放处理器,使其能够处理其他线程。处理器睡眠的原因可能在于感知数据可用性,或等待资源释放内核调度器管理要运行的任务列表,这被称为运行队列。睡眠进程不再被调度,因为已将它们从运行队列中移除了。除非改变状态(唤醒),否则睡眠进程将永远不会被执行。......
  • Spark重温笔记(三):Spark在企业中为什么能这么强?——持久化、Checkpoint机制、共享变量与
    Spark学习笔记前言:今天是温习Spark的第3天啦!主要梳理了Spark核心数据结构:RDD(弹性分布式数据集),包括RDD持久化,checkpoint机制,spark两种共享变量以及spark内核调度原理,希望对大家有帮助!Tips:"分享是快乐的源泉......