JDK SPI学习笔记

1 篇文章 0 订阅

  SPI(Service Provider Interface),服务提供者接口,或称服务提供者模式,是一种被广泛使用的用于解耦的技术。常见的xml、jdbc、nio selector,甚至是dubbo都能看到它的身影。当然,我们知道SPI主要目的是解耦,但是它又是怎样去实现解耦这个目的的呢?

  就拿nio的java.nio.channels.spi.SelectorProvider来说,java.nio.channels.spi.SelectorProvider本身是一个抽象类,它必然要有一个具体的类才能让程序跑起来。首先我们看到java.nio.channels.spi.SelectorProvider的一个叫provider的方法:

    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty()) //(1)先从系统参数获取SelectorProvider的实现类
                                return provider;
                            if (loadProviderAsService()) //(2)找不到的话,尝试从META-INF/services/目录下查找对应的java.nio.channels.spi.SelectorProvider文件
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();// (3)使用默认的实现sun.nio.ch.DefaultSelectorProvider
                            return provider;
                        }
                    });
        }
    }

  windows下打开对应的sun.nio.ch.DefaultSelectorProvider源码:

public class DefaultSelectorProvider {
	public static SelectorProvider create() {
		return new WindowsSelectorProvider();
	}
}

  所以,很容易看出,SPI机制就是要想办法让接口定义和对应实现做的完全解耦,甚至可插拔化。不管使用的系统参数,还是META-INF/service/目录定义的方式,目的都是为了尽量避免在代码里面直接对着接口new出具体的实现类,这样也方便后续维护以及替换对应的实现类,做到对上层代码无感知。当然,java.nio.channels.spi.SelectorProvider这里组合了三种方式来实现SPI,其实还是稍微复杂的,大部分时候,其实我们只需单独使用第二种就可以了。

    private static boolean loadProviderAsService() {

        ServiceLoader<SelectorProvider> sl =
            ServiceLoader.load(SelectorProvider.class,
                               ClassLoader.getSystemClassLoader());
        Iterator<SelectorProvider> i = sl.iterator();
        for (;;) {
            try {
                if (!i.hasNext())
                    return false;
                provider = i.next();
                return true;
            } catch (ServiceConfigurationError sce) {
                if (sce.getCause() instanceof SecurityException) {
                    // Ignore the security exception, try the next provider
                    continue;
                }
                throw sce;
            }
        }
    }

  这里可以看到,最最核心的一句代码是:

        ServiceLoader<SelectorProvider> sl =
            ServiceLoader.load(SelectorProvider.class,
                               ClassLoader.getSystemClassLoader());

  没错,这里的ServiceLoader.load方法就是来帮助我们找到一个接口对应的实现类的一种机制。通过这种方式,我们也可以很方便地在我们的业务代码里面用上SPI。下面,我们也自己动手来试试:

  首先,新建一个Java interface,这里我起名MyInterface,然后又一个action的方法:

public interface MyInterface {

	void action();
}

  然后,新建两个MyInterface的实现类,分别为MyInterface1和MyInterface2:

public class MyInterface1 implements MyInterface {

	@Override
	public void action() {
		System.out.println("hehe1");
	}

}
public class MyInterface2 implements MyInterface {

	@Override
	public void action() {
		System.out.println("hehe2");
	}

}

  最后,编写一段测试代码:

public class TestSpi {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ServiceLoader<MyInterface> load = ServiceLoader.load(MyInterface.class);
		Iterator<MyInterface> iterator = load.iterator();
		while (iterator.hasNext()) {
			MyInterface myInterface = iterator.next();
			myInterface.action();
		}
	}

}

  直接运行,你会发现啥也没输出。正常的,因为我们还没给MyInterface的接口配上具体的实现类。配置方式是:在项目工程下新建一个META-INF/services/[interface全类目]的目录结构,其中[interface全类目]部分则替换成我们的接口全名,比如我自己的如下:


  然后,这个cn.seedeed.test.spi.MyInterface的文件的内容修改为具体实现类的全类目,就好了。比如,我们修改为cn.seedeed.test.spi.MyInterface1,就表示具体实现类是cn.seedeed.test.spi.MyInterface1,运行就会输出hehe1。如果我们把内容修改为cn.seedeed.test.spi.MyInterface2,就表示接口的实现类是cn.seedeed.test.spi.MyInterface2,运行则会输出hehe2了。

  这样一来,你就会发现,我们的测试代码没有给cn.seedeed.test.spi.MyInterface这个接口耦合任何具体类的信息,具体类完全是配置出来的,这样就达到了SPI接口与实现解耦的目的了。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值