【ShenYu系列】ShenYu的SPI实现源码分析

前言

前面我已经介绍【面试系列】详细拆解Java、Spring、Dubbo三者SPI机制的原理,当已经有了合适的实现,shenyu自身的SPI和上面的有啥区别,值得玩味。

什么是SPI

SPI就是Service Provider Interface,直译"服务提供方接口",是一种动态的服务发现机制,可以基于接口运行时动态加载接口的实现类(也就是接口编程 + 策略模式 + 配置文件的一种开发模式)。最常见的就是JDK内置的数据库驱动接口java.sql.Driver,不同的厂商可以对该接口完成不同的实现,例如MySQLMySQL驱动包中的com.mysql.jdbc.Driver)、PostgreSQLPostgreSQL驱动包中的org.postgresql.Driver)等等。

ShenYu的SPI

Demo

JdbcSPI jdbcSPI = ExtensionLoader.getExtensionLoader(JdbcSPI.class).getJoin("mysql");

源码

ExtensionLoader#getExtensionLoader(java.lang.Class<T>),判断类是否是接口,是否存在@SPI注解。

    public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
        return getExtensionLoader(clazz, ExtensionLoader.class.getClassLoader());
    }

    public static <T> ExtensionLoader<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!");
        }
        if (!clazz.isAnnotationPresent(SPI.class)) {
            throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
        }
        ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
        if (Objects.nonNull(extensionLoader)) {
            return extensionLoader;
        }
        LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz, cl));
        return (ExtensionLoader<T>) LOADERS.get(clazz);
    }

ExtensionLoader,构造方法。获取ExtensionFactory

    private ExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
        this.clazz = clazz;
        this.classLoader = cl;
        if (!Objects.equals(clazz, ExtensionFactory.class)) {
            ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getExtensionClassesEntity();
        }
    }

ExtensionLoader#getExtensionClassesEntity,获取本地缓存,本地缓存不存在,加载类信息。

    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);
                    cachedClasses.setOrder(0);
                }
            }
        }
        return classes;
    }

ExtensionLoader#loadExtensionClass,加载类信息主要是加载指定目录下面的文件

    private Map<String, ClassEntity> loadExtensionClass() {
        SPI annotation = clazz.getAnnotation(SPI.class);
        if (Objects.nonNull(annotation)) {
            String value = annotation.value();
            if (StringUtils.isNotBlank(value)) {
                cachedDefaultName = value;
            }
        }
        Map<String, ClassEntity> classes = new HashMap<>(16);
        loadDirectory(classes);
        return classes;
    }

ExtensionLoader#loadDirectory,加载目录下的文件

    private void loadDirectory(final Map<String, ClassEntity> classes) {
        String fileName = SHENYU_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);
        }
    }

ExtensionLoader#loadResources,加载资源文件

    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);
        }
    }

ExtensionLoader#loadClass,判断是否是实现类,是否存在Join注解,满足条件,封装成ClassEntity,添加到classes中。

    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);
        }
        if (!subClass.isAnnotationPresent(Join.class)) {
            throw new IllegalStateException("load extension resources error," + subClass + " without @" + Join.class + " annotation");
        }
        ClassEntity oldClassEntity = classes.get(name);
        if (Objects.isNull(oldClassEntity)) {
            Join joinAnnotation = subClass.getAnnotation(Join.class);
            ClassEntity classEntity = new ClassEntity(name, joinAnnotation.order(), subClass, joinAnnotation.isSingleton());
            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());
        }
    }

ExtensionLoader#getJoin,创建对应的实例。

    public T getJoin(final String name) {
        if (StringUtils.isBlank(name)) {
            throw new NullPointerException("get join 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();
                    if (!objectHolder.isSingleton()) {
                        Holder<Object> removeObj = cachedInstances.remove(name);
                        removeObj = null;
                    }
                }
            }
        }
        return (T) value;
    }

ExtensionLoader#createExtension,根据接口信息去找到配置文件,从配置文件中读取实现类。

    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 = joinInstances.get(aClass);
        if (Objects.isNull(o)) {
            try {
                if (classEntity.isSingleton()) {
                    joinInstances.putIfAbsent(aClass, aClass.newInstance());
                    o = joinInstances.get(aClass);
                } else {
                    o = aClass.newInstance();
                }
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException("Extension instance(name: " + name + ", class: "
                        + aClass + ")  could not be instantiated: " + e.getMessage(), e);
                
            }
        }
        holder.setOrder(classEntity.getOrder());
        holder.setValue(o);
        holder.setSingleton(classEntity.isSingleton());
    }

配置文件如下

mysql=org.apache.shenyu.spi.fixture.MysqlSPI
oracle=org.apache.shenyu.spi.fixture.OracleSPI
canNotInstantiated=org.apache.shenyu.spi.fixture.CanNotInstantiatedSPI

总结一下

Shenyu的spi,更多像是dubbo的spi简化版。springboot是key=a,b,c,不涉及对应关系。dubbo是类名作为文件名,key获取实现类的主键。相比于dubbo,简化了adaptive等功能。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shenyu网关是一款开源的API网关软件,它可以帮助开发者实现对接口的统一管理和流量控制。shenyu网关提供了多种功能,包括请求转发、请求过滤、流量限制、负载均衡等,帮助用户实现高可用和高稳定性的系统架构。 shenyu网关基于Java开发,使用简单方便,支持多种开源框架和中间件,如Spring Boot、Dubbo、Motan等。通过使用shenyu网关,开发者可以快速搭建起一个完善的微服务架构,可以轻松地管理和监控各个微服务之间的通信和流量。 另外,shenyu网关还提供了可视化的管理界面,方便用户进行参数配置、资源管理、性能监控等操作。用户可以通过该界面对各个服务进行细粒度的管理,实现功能的灵活配置和动态调整。 码云是中国最大的代码托管平台,类似于国外的GitHub。用户可以通过码云托管自己的代码仓库,并利用这个平台进行团队协作和项目管理。码云提供了Git版本控制、代码托管、代码审查等功能,支持多种开发语言和开发工具。 与GitHub不同的是,码云在国内具有更好的访问速度和稳定性,同时还提供了国内常用的Git代码托管服务,支持国内的开发者更方便地使用和访问。用户可以在码云上创建自己的项目仓库,与其他开发者进行交流学习,并共同进行开发和维护。 综上所述,shenyu网关和码云都是开发者常用的工具。shenyu网关可以帮助开发者实现API网关的功能,提供统一的接口管理和流量控制;码云则是代码托管平台,提供代码的版本控制和团队协作功能。两者的结合可以帮助开发者更好地进行微服务架构的开发和管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值