SPI-自由扩展的入口

本文基于jdk1.8

一.SPI是何物?

SPI是Service Provider Interface的缩写,用于解耦服务接口与其具体实现。
java编程中最常见的SPI实现是解耦数据库驱动。
想想平时引入mysql或者postgresql的jar就能连上数据库,是不是很神奇。

二. 快速实现SPI接口

来动手写一个demo,熟悉下。

1. 定义一个用户服务,及其实现
public interface UserService {
    String getUserName(long userId);
}
public class UserServiceImpl implements UserService {
    @Override
    public String getUserName(long userId) {
        return String.valueOf(userId).concat("用户名");
    }
}
2. 创建资源文件

文件名称为用户服务接口全路径名,内容为其实现类全路径名,大体如下图
在这里插入图片描述

3. 测试下demo
@SpringBootTest
class JdkSpiApplicationTests {

    @Test
    void contextLoads() {
        ServiceLoader<UserService> load = ServiceLoader.load(UserService.class);
        Iterator<UserService> iterator = load.iterator();
        while (iterator.hasNext()) {
            UserService userService = iterator.next();
            System.out.println("调用服务获取用户名");
            System.out.println(userService.getUserName(1L));
        }
    }

}

结果如下:
在这里插入图片描述

三.服务加载器-ServiceLoader

上文简单实现了一个SPI,接下来看下,具体是如何实现上述效果。

1.获取ServiceLoader

进入load方法

	ServiceLoader.load(UserService.class);

load方法会构建一个ServiceLoader对象返回,这是一个静态工厂的实现案例。

	public static <S> ServiceLoader<S> load(Class<S> service) {
		// 获取上下文的类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
	
	 public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

ServiceLoader的关键属性如下,主要做了三件事:

  1. 保存类加载器,接口类等属性
  2. 清空实例缓存
  3. 生成惰性加载器

	// 缓存提供接口实例化的
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 懒惰迭代器
    private LazyIterator lookupIterator;
	
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
    	// 设置接口类
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        // 设置类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        // 这里设置安全管理器,一般是null
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
	public void reload() {
		// 清空缓存
        providers.clear();
        // 创建惰性迭代器
        lookupIterator = new LazyIterator(service, loader);
    }
2.获取迭代器
Iterator<UserService> iterator = load.iterator();

第二步获取迭代对象,这里获取的ServiceLoader的内部类。

  public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
            	// 先从缓存中取
                if (knownProviders.hasNext())
                    return true;
                // 再从惰性迭代器查找    
                return lookupIterator.hasNext();
            }

            public S next() {
            	// 先从缓存中取
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                // 再从惰性迭代器查找    
                return lookupIterator.next();
            }
        };
    }
3.判断是否有迭代对象
	  while (iterator.hasNext()) {
	  	...
      }

hasNext方法最终调用hasNextService方法,首次进入流程如下:

  1. 加载接口文件
  2. 逐行解析,校验接口名
  3. 将解析出来的接口实现类名存入names接口
	// 下一个迭代的实现类名称
	 String nextName = null;
	 // 资源路径集合
	  Enumeration<URL> configs = null;
	// 文件查找路径
	private static final String PREFIX = "META-INF/services/";
	
	private boolean hasNextService() {
			// 首次进入这里为null
            if (nextName != null) {
                return true;
            }
            // 首次进入这里为null
            if (configs == null) {
                try {
                	// 这里就是resources目录创建方式的原因
                    String fullName = PREFIX + service.getName();
                    // 这里前文已设置过,一般不为null
                    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 Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            // 注意这里的格式是utf-8
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            // 逐行解析数据
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
          ....
        }
        // 返回实现类名集合迭代器
        return names.iterator();
    }
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        // 解析结束
        if (ln == null) {
            return -1;
        }
        // 这里说明文件可以添加# 这注释
        int ci = ln.indexOf('#');
        // 截取到#之前的字符串
        if (ci >= 0) ln = ln.substring(0, ci);
        // 去除首尾空格
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
        	// 校验字符串内部是否包含空格或者换行符
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            // 校验字符是否符合java字符标准
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            // 依次校验每个字符    
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            // 添加解析的实现类名
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }
4.获取迭代对象
	UserService userService = iterator.next();

next()最终调用nextService方法

	private S nextService() {
			// 之前没有调用过hasNextService,进行解析数据,这里会尝试解析文件流
            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();
        }

四.总结

jdk的SPI虽然实现了解耦接口的目的,但还是有一个小缺陷:
面对多个实现时,不能指定具体的实现。
最后附上项目地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值