返回顶部
首页 > 资讯 > 精选 >Java如何实现简单SPI
  • 485
分享到

Java如何实现简单SPI

2023-07-05 08:07:18 485人浏览 薄情痞子
摘要

本文小编为大家详细介绍“Java如何实现简单SPI”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何实现简单SPI”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。SPI标注注解标注提供SPI能力接口的注解

本文小编为大家详细介绍“Java如何实现简单SPI”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何实现简单SPI”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

SPI标注注解

标注提供SPI能力接口的注解

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface SPI {        String value() default "";}

标准SPI实现的注解@Join

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Join {}

SPI核心实现

SPI的一些Class和扩展对象缓存

SPI实现是一个懒加载的过程,只有当通过get方法获取扩展的实例时才会加载扩展,并创建扩展实例,这里我们定义一个集合用于缓存扩展类,扩展对象等,代码如下:

@Slf4j@SuppressWarnings("all")public class ExtensionLoader<T> {        private static final String DEFAULT_DIRECTORY = "META-INF/log-helper/";        private final Class<T> tClass;        private static final Map<Class<?>, ExtensionLoader<?>> MAP = new ConcurrentHashMap<>();        private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();        private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();        private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();        private String cacheDefaultName;    // 省略代码后面介绍}

获取扩展器ExtensionLoader

    public static<T> ExtensionLoader<T> getExtensionLoader(final Class<T> tClass) {        // 参数非空校验        if (null == tClass) {            throw new NullPointerException("tClass is null !");        }        // 参数应该是接口        if (!tClass.isInterface()) {            throw new IllegalArgumentException("tClass :" + tClass + " is not interface !");        }        // 参数要包含@SPI注解        if (!tClass.isAnnotationPresent(SPI.class)) {            throw new IllegalArgumentException("tClass " + tClass + "without @" + SPI.class + " Annotation !");        }        // 从缓存中获取扩展加载器,如果存在直接返回,如果不存在就创建一个扩展加载器并放到缓存中        ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) MAP.get(tClass);        if (null != extensionLoader) {            return extensionLoader;        }        MAP.putIfAbsent(tClass, new ExtensionLoader<>(tClass));        return (ExtensionLoader<T>) MAP.get(tClass);    }

扩展加载器构造方法

    public ExtensionLoader(final Class<T> tClass) {        this.tClass = tClass;    }

获取SPI扩展对象

获取SPI扩展对象是懒加载过程,第一次去获取的时候是没有的,会触发从问家中加载资源,通过反射创建对象,并缓存起来。

    public T getJoin(String cacheDefaultName) {        // 扩展名 文件中的key        if (StringUtils.isBlank(cacheDefaultName)) {            throw new IllegalArgumentException("join name is null");        }        // 扩展对象存储缓存        Holder<Object> objectHolder = cachedInstances.get(cacheDefaultName);        // 如果扩展对象的存储是空的,创建一个扩展对象存储并缓存        if (null == objectHolder) {            cachedInstances.putIfAbsent(cacheDefaultName, new Holder<>());            objectHolder = cachedInstances.get(cacheDefaultName);        }        // 从扩展对象的存储中获取扩展对象        Object value = objectHolder.getT();        // 如果对象是空的,就触发创建扩展,否则直接返回扩展对象        if (null == value) {            synchronized (cacheDefaultName) {                value = objectHolder.getT();                if (null == value) {                    // 创建扩展对象                    value = createExtension(cacheDefaultName);                    objectHolder.setT(value);                }            }        }        return (T) value;    }

创建扩展对象

反射方式创建扩展对象的实例

    private Object createExtension(String cacheDefaultName) {        // 根据扩展名字获取扩展的Class,从Holder中获取 key-value缓存,然后根据名字从Map中获取扩展实现Class        Class<?> aClass = getExtensionClasses().get(cacheDefaultName);        if (null == aClass) {            throw new IllegalArgumentException("extension class is null");        }        Object o = joinInstances.get(aClass);        if (null == o) {            try {                // 创建扩展对象并放到缓存中                joinInstances.putIfAbsent(aClass, aClass.newInstance());                o = joinInstances.get(aClass);            } catch (InstantiationException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }        return o;    }

从Holder中获取获取扩展实现的Class集合

    public Map<String, Class<?>> getExtensionClasses() {        // 扩区SPI扩展实现的缓存,对应的就是扩展文件中的 key - value        Map<String, Class<?>> classes = cachedClasses.getT();        if (null == classes) {            synchronized (cachedClasses) {                classes = cachedClasses.getT();                if (null == classes) {                    // 加载扩展                    classes = loadExtensionClass();                    // 缓存扩展实现集合                    cachedClasses.setT(classes);                }            }        }        return classes;    }

加载扩展实现Class

加载扩展实现Class,就是从文件中获取扩展实现的Class,然后缓存起来

    public Map<String, Class<?>> loadExtensionClass() {        // 扩展接口tClass,必须包含SPI注解        SPI annotation = tClass.getAnnotation(SPI.class);        if (null != annotation) {            String v = annotation.value();            if (StringUtils.isNotBlank(v)) {                // 如果有默认的扩展实现名,用默认的                cacheDefaultName = v;            }        }        Map<String, Class<?>> classes = new HashMap<>(16);        // 从文件加载        loadDirectory(classes);        return classes;    }    private void loadDirectory(final Map<String, Class<?>> classes) {        // 文件名        String fileName = DEFAULT_DIRECTORY + tClass.getName();        try {            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();            // 读取配置文件            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName)                    : ClassLoader.getSystemResources(fileName);            if (urls != null) {                // 获取所有的配置文件                while (urls.hasMoreElements()) {                    URL url = urls.nextElement();                    // 加载资源                    loadResources(classes, url);                }            }        } catch (IOException e) {            log.error("load directory error {}", fileName, e);        }    }    private void loadResources(Map<String, Class<?>> classes, URL url) {        // 读取文件到Properties,遍历Properties,得到配置文件key(名字)和value(扩展实现的Class)        try (InputStream inputStream = url.openStream()) {            Properties properties = new Properties();            properties.load(inputStream);            properties.forEach((k, v) -> {                // 扩展实现的名字                String name = (String) k;                // 扩展实现的Class的全路径                String classPath = (String) v;                if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {                    try {                        // 加载扩展实现Class,就是想其缓存起来,缓存到集合中                        loadClass(classes, name, classPath);                    } catch (ClassNotFoundException e) {                        log.error("load class not found", e);                    }                }            });        } catch (IOException e) {            log.error("load resouces error", e);        }    }    private void loadClass(Map<String, Class<?>> classes, String name, String classPath) throws ClassNotFoundException {        // 反射创建扩展实现的Class        Class<?> subClass = Class.forName(classPath);        // 扩展实现的Class要是扩展接口的实现类        if (!tClass.isAssignableFrom(subClass)) {            throw new IllegalArgumentException("load extension class error " + subClass + " not sub type of " + tClass);        }        // 扩展实现要有Join注解        Join annotation = subClass.getAnnotation(Join.class);        if (null == annotation) {            throw new IllegalArgumentException("load extension class error " + subClass + " without @Join" +                    "Annotation");        }        // 缓存扩展实现Class        Class<?> oldClass = classes.get(name);        if (oldClass == null) {            classes.put(name, subClass);        } else if (oldClass != subClass) {            log.error("load extension class error, Duplicate class oldClass is " + oldClass + "subClass is" + subClass);        }    }

存储Holder

    public static class Holder<T> {        private volatile T t;        public T getT() {            return t;        }        public void setT(T t) {            this.t = t;        }    }

测试SPI

定义SPI接口

@SPIpublic interface TestSPI {    void test();}

扩展实现1和2

@Joinpublic class TestSPI1Impl implements TestSPI {    @Override    public void test() {        System.out.println("test1");    }}@Joinpublic class TestSPI2Impl implements TestSPI {    @Override    public void test() {        System.out.println("test2");    }}

在resources文件夹下创建META-INF/log-helper文件夹,并创建扩展文件

文件名称(接口全路径名):com.redick.spi.test.TestSPI

文件内容

testSPI1=com.redick.spi.test.TestSPI1Impl
testSPI2=com.redick.spi.test.TestSPI2Impl

动态使用测试

public class SpiExtensionFactoryTest {    @Test    public void getExtensionTest() {        TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI1");        testSPI.test();    }}

测试结果:

test1

public class SpiExtensionFactoryTest {    @Test    public void getExtensionTest() {        TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI2");        testSPI.test();    }}

测试结果:

test2

读到这里,这篇“Java如何实现简单SPI”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

--结束END--

本文标题: Java如何实现简单SPI

本文链接: https://lsjlt.com/news/350512.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java如何实现简单SPI
    本文小编为大家详细介绍“Java如何实现简单SPI”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何实现简单SPI”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。SPI标注注解标注提供SPI能力接口的注解...
    99+
    2023-07-05
  • 详解Java实现简单SPI流程
    目录SPI标注注解SPI核心实现SPI的一些Class和扩展对象缓存获取扩展器ExtensionLoader扩展加载器构造方法获取SPI扩展对象创建扩展对象从Holder中获取获取扩...
    99+
    2023-03-02
    Java实现SPI Java SPI Java实现简单SPI
  • Java SPI简单应用案例详解
    开篇 本文主要谈一下 Java SPI(Service Provider Interface) ,因为最近在看 Dubbo 的相关内容,其中涉及到了 一个概念- Dubbo SPI,...
    99+
    2024-04-02
  • Java如何实现简单画板
    本文小编为大家详细介绍“Java如何实现简单画板”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何实现简单画板”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一、代码先直接上代码吧,备注大部分都在代码中。...
    99+
    2023-07-02
  • Java如何实现简单的排序
    这篇文章主要介绍Java如何实现简单的排序,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!排序是数据处理中十分常见且核心的操作,虽说实际项目开发中很小几率会需要我们手动实现,毕竟每种语言的类库中都有n多种关于排序算法的...
    99+
    2023-06-25
  • Java如何实现简单小画板
    今天小编给大家分享一下Java如何实现简单小画板的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Java制作简单画板,包括两个...
    99+
    2023-07-02
  • 如何实现java简单的线程池
    这篇文章主要讲解了“如何实现java简单的线程池”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何实现java简单的线程池”吧!目录拆分实现流程实现方式拒绝策略阻塞队列线程池和工作线程策略模...
    99+
    2023-06-20
  • java如何实现简单快递系统
    这篇“java如何实现简单快递系统”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“java如何实现简单快递系统”文章吧。具体内...
    99+
    2023-06-29
  • java如何实现简单中国象棋
    这篇文章主要讲解了“java如何实现简单中国象棋”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java如何实现简单中国象棋”吧!运行效果代码import java.awt.Canv...
    99+
    2023-06-30
  • Java如何实现简单登陆界面
    这篇文章主要介绍了Java如何实现简单登陆界面的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java如何实现简单登陆界面文章都会有所收获,下面我们一起来看看吧。首先需要建立一个类,在这里,我命名为newLogi...
    99+
    2023-06-30
  • java如何实现简单登录界面
    本文小编为大家详细介绍“java如何实现简单登录界面”,内容详细,步骤清晰,细节处理妥当,希望这篇“java如何实现简单登录界面”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一、概要我们可以用java实现简单的登...
    99+
    2023-06-30
  • Java如何实现简单扫雷程序
    本篇内容介绍了“Java如何实现简单扫雷程序”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!页面设置:框架是borderlayout,在上中下...
    99+
    2023-06-30
  • Java如何实现一个简单计算器
    这篇文章主要介绍了Java如何实现一个简单计算器,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。先来看看界面效果:源码如下:package test1; i...
    99+
    2023-06-22
  • java如何实现简单三子棋游戏
    本篇内容主要讲解“java如何实现简单三子棋游戏”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java如何实现简单三子棋游戏”吧!JOptionPane类的使用编写程序,实现简单的三子棋游戏。在...
    99+
    2023-06-30
  • Java如何实现简单的日历界面
    本文小编为大家详细介绍“Java如何实现简单的日历界面”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java如何实现简单的日历界面”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。请使用JFrame、JPanel、...
    99+
    2023-07-02
  • java如何实现简单银行ATM系统
    这篇文章主要介绍了java如何实现简单银行ATM系统的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇java如何实现简单银行ATM系统文章都会有所收获,下面我们一起来看看吧。#ATM系统##功能模拟银行ATM机系...
    99+
    2023-06-30
  • Java如何实现简单的区块链程序
    本篇内容主要讲解“Java如何实现简单的区块链程序”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java如何实现简单的区块链程序”吧!什么是区块链?那么,让我们先来了解一下区块链到底是什么…好吧...
    99+
    2023-06-14
  • java如何实现简单贪吃蛇小游戏
    这篇文章给大家分享的是有关java如何实现简单贪吃蛇小游戏的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体内容如下有两个类,放在同一文件下下即可,背景图片,标题,蛇尾可自行更改import java....
    99+
    2023-06-15
  • Java如何实现简单图书借阅系统
    这篇文章主要介绍“Java如何实现简单图书借阅系统”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java如何实现简单图书借阅系统”文章能帮助大家解决问题。为图书阅览室开发一个图书借阅系统,最多可存5...
    99+
    2023-06-29
  • 如何使用Java实现简单点餐系统
    小编给大家分享一下如何使用Java实现简单点餐系统,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!程序控制分析:欢迎页循环:do-while2、Scanner 控制...
    99+
    2023-06-29
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作