Java SPI介绍

简言:今天在学习shardingsphere时候发现他们接口扩展使用SPI 模式来扩展接口具体实现内容,在很多框架中都有被广泛使用,特此进入记录学习。

定义

它是在JDK 1.6版本引入的,SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

SPI和API的使用场景

API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

来个简单例子

代码结构
在这里插入图片描述

定义一个接口

/**
 * @author zhaoyy
 * @version 1.0
 * @description TODO
 * @date 2022/10/10
 **/
public interface UserServer {
    String getName();
}

两个实现类

/**
 * @author zhaoyy
 * @version 1.0
 * @description TODO
 * @date 2022/10/10
 **/
public class UserServiceImpl01 implements UserServer {
    @Override
    public String getName() {
        return "is name UserServiceImpl01 ";
    }
}

```java
/**
 * @author zhaoyy
 * @version 1.0
 * @description TODO
 * @date 2022/10/10
 **/
public class UserServiceImpl02 implements UserServer {
    @Override
    public String getName() {
        return "is name UserServiceImpl02 ";
    }
}

测试类:


import java.util.ServiceLoader;

/**
 * @author zhaoyy
 * @version 1.0
 * @description TODO
 * @date 2022/10/10
 **/
public class Test {
    public static void main(String[] args) {
        ServiceLoader<UserServer> userServers = ServiceLoader.load(UserServer.class,
                Thread.currentThread().getContextClassLoader());
        for (UserServer userServer : userServers) {
            System.out.println(userServer.getName());
        }

    }
}

工程resoures/META-INFO/services
创建文件:com.zyy.java.server.loader.UserServer 没有的新建

文件内容:

com.zyy.java.server.loader.UserServiceImpl01
com.zyy.java.server.loader.UserServiceImpl02

在这里插入图片描述
输出结果:
在这里插入图片描述
这样一个简单的spi的demo就完成了。可以看到其中最为核心的就是通过ServiceLoader这个类来加载具体的实现类的。

SPI原理解析

我们来看下源码,源码比较多只复制重要的部分,感兴趣的小伙伴可以自己深入研究下,下面贴出比较直观的spi加载的主要流程供参考:
在这里插入图片描述

public final class ServiceLoader<S>
    implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
    
      public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    // Private inner class implementing fully-lazy provider lookup
    //
    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
}

来参考下sharding SIP结构

下面介绍的是表主键id设置参数可以是:UUID,SNOWFLAKE两种类型
简单介绍下uuid实现过程:

application.properties 文件中我们需要指定分表主键id生成类型

spring.shardingsphere.sharding.tables.bill.key-generator.type=UUID

我们来看下他们具体源码
servers 配置文件下
在这里插入图片描述
对应接口ShardingKeyGenerator 实现类UUIDShardingKeyGenerator

package org.apache.shardingsphere.core.strategy.keygen;

import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;

import java.util.Properties;
import java.util.UUID;

/**
 * UUID key generator.
 *
 * @author panjuan
 */
@Getter
@Setter
public final class UUIDShardingKeyGenerator implements ShardingKeyGenerator {
    
    private Properties properties = new Properties();
    
    @Override
    public String getType() {
        return "UUID";
    }
    
    @Override
    public synchronized Comparable<?> generateKey() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}

sharding 实现uid 很简单就是 UUID.randomUUID()

serviceLoader 如何实现的:
1 代码入口:
org.apache.shardingsphere.core.rule.ShardingRule# createDefaultKeyGenerator 默认初始化keyGenerator方法,初始加载接口实现类方法:serviceLoader.newService
源码方法:
在这里插入图片描述
回过来我们看上图对象: ShardingKeyGeneratorServiceLoader当前类完成 UUID,SNOWFLAKE 具体实现接口加载类到注册对象SERVICE_MAP 具体源码请看下面:

public final class ShardingKeyGeneratorServiceLoader extends TypeBasedSPIServiceLoader<ShardingKeyGenerator> {
    
    // 静态方法完成 初始化对象是完成接口注册到NewInstanceServiceLoader#SERVICE_MAP 下面会介绍当前类
    static {
        NewInstanceServiceLoader.register(ShardingKeyGenerator.class);
    }
    
    public ShardingKeyGeneratorServiceLoader() {
        super(ShardingKeyGenerator.class);
    }
}

2 看下newService 是如何实现的,方法会调用loadTypeBasedServices 方法,结果 会返回集合泛类对象

NewInstanceServiceLoader.newServiceInstances(classType),该方法先从共享变量SERVER_MAP中查找,如果有直接返回,重新加载一次,下图片2, each.newInstance()会根据classtype类型去加载对应类,最终到了jdk Class方法中完成实现类具体加载过程

在这里插入图片描述
在这里插入图片描述

总结下

  1. 关于spi的详解到此就结束了,总结下spi能带来的好处:
  2. 不需要改动源码就可以实现扩展,解耦。
  3. 实现扩展对原来的代码几乎没有侵入性。
  4. 只需要添加配置就可以实现扩展,符合开闭原则。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟-要努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值