SPI机制(Java、Spring和Dubbo)

SPI,在dubbo的官网上是这么介绍的,SPI全称为Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。
dubbo官网

JAVA的SPI机制

使用
  1. 首先需要一个目录META-INF/services
  2. 建一个文件,文件名字是接口的名字
  3. 文件内容是某个接口实现类的全限定类名
  • 我们现在通过模拟一个数据库驱动Driver的spi
  • 创建一个MyDriver,实现java.sql.Driver
    在这里插入图片描述
  • 在resources目录下新建META-INF/services/java.sql.Driver文件在这里插入图片描述
  • java.sql.Driver中的内容是MyDriver的全限定类名在这里插入图片描述
  • 编写测试类进行测试
public class JavaSpi {
    public static void main(String[] args) {
        ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = load.iterator();
        while (iterator.hasNext()){
            Driver driver = iterator.next();
            System.out.println(driver.getClass().getName());
            //输出内容:com.test.spi.java.MyDriver
        }
    }
}
源码分析
  • 我们从ServiceLoader.load开始看起,一路看下去可以看到创建了一个ServiceLoader实例,并且在构造器中执行了reload()方法,初始化了一个LazyIterator,这个LazyIterator中的方法很关键
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(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();
    }
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
  • 然后我们在遍历的时候,会得到一个迭代器,这个迭代器是在ServiceLoader内部new出来的
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;
                    //最终我们会走到在reload()方法中创建的LazyIterator类中
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                    //同理,这里也会进入到LazyIterator中
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }
  • LazyIterator的hasNext()->hasNextService()->parse()
public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                	//这里就是去读取文件 
                	//private static final String PREFIX = "META-INF/services/"
                	//service就是我们调用load方法传进来的Driver,最终得到一个路径名
                    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());
            }
            //我们调用next()获取到的类名
            nextName = pending.next();
            return true;
        }        
  • LazyIterator的next()->nextService()
public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
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
        }

spring的SPI机制

使用
  1. 新建一个META-INF/spring.factories文件
  2. spring.factories的内容是key-value的形式,key是某个类的全限定类名,value也是某个类的全限定类名,如果只获得value的全限定类名,是不需要key和value有父子关系的,即调用loadFactoryNames方法,下面会说明
  • 模仿springboot的EnableAutoConfiguration的创建一个注解在这里插入图片描述
  • 随便写一个类在这里插入图片描述
  • 写spring.factories文件
    在这里插入图片描述
    在这里插入图片描述
  • 编写测试类
public class SpringSpi {
    public static void main(String[] args) {
    	//这里loadFactoryNames只是获得MyConfiguration为key的所有value的名称,并没有创建实例
        List<String> strings = SpringFactoriesLoader.
                loadFactoryNames(MyConfiguration.class, SpringTestClass.class.getClassLoader());
        System.out.println(strings);
        //输出内容:[com.test.spi.spring.SpringTestClass]
    }
}
源码
  • 我们从loadFactoryNames方法看起,调用loadSpringFactories
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	//缓存中有,就直接返回
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
		//这里和java的spi是一样的,都是去读取文件,
		//FACTORIES_RESOURCE_LOCATION= "META-INF/spring.factories"
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				//得到我们的key和value的形式 比如我们上面的例子的 key=com.test.spi.spring.MyConfiguration 
			//value=com.test.spi.spring.SpringTestClass
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					//我们的value可以是以逗号分割的字符串,这里变成一个数据
					for (String factoryImplementationName : 
					StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					//加入Map中
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			//加入缓存,返回
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

Dubbo的SPI机制

dubbo的SPI可以分为静态扩展点,自适应扩展点和激活扩展点。

使用
  • 自定义一个Container在这里插入图片描述
  • 在resources下新建META-INF/services/org.apache.dubbo.container.Container
    在这里插入图片描述
  • 在org.apache.dubbo.container.Container文件中写入myContainer=com.test.spi.dubbo.MyContainer在这里插入图片描述
  • 写测试类,只有静态扩展点我们需要新建类,其余的都是dubbo自身有的类
public class DubboSpi {
    public static void main(String[] args) {
        //静态扩展点myContainer 就是我们在文件中的key
        Container myContainer = ExtensionLoader.getExtensionLoader(Container.class).getExtension("myContainer");
        System.out.println(myContainer);
        //输出:com.test.spi.dubbo.MyContainer@6aa8ceb6

        //自适应扩展点,加在类上和加载方法上
        Compiler adaptiveExtension = ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
        System.out.println(adaptiveExtension);

        //输出:org.apache.dubbo.common.compiler.support.AdaptiveCompiler@759ebb3d
        Protocol adaptiveExtension1 = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
        System.out.println(adaptiveExtension1);
        //输出:org.apache.dubbo.rpc.Protocol$Adaptive@61a485d2


        //激活扩展点
        URL url = new URL("","localhost",8080).addParameters(Constants.DEPRECATED_KEY,Constants.DEPRECATED_KEY);
        List<Filter> activateExtension = ExtensionLoader.getExtensionLoader(Filter.class)
                .getActivateExtension(url, Constants.DEPRECATED_KEY);
        System.out.println(activateExtension);
        //输出:[org.apache.dubbo.rpc.filter.EchoFilter@69ea3742, org.apache.dubbo.rpc.filter.ClassLoaderFilter@4b952a2d, org.apache.dubbo.rpc.filter.GenericFilter@3159c4b8, org.apache.dubbo.rpc.filter.ConsumerContextFilter@73846619, org.apache.dubbo.rpc.filter.ContextFilter@4bec1f0c, org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter@29ca901e, org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter@5649fd9b, org.apache.dubbo.rpc.filter.TimeoutFilter@6adede5, org.apache.dubbo.monitor.support.MonitorFilter@2d928643, org.apache.dubbo.rpc.filter.ExceptionFilter@5025a98f, org.apache.dubbo.cache.filter.CacheFilter@49993335]
    }
}
源码
  • 我们从ExtensionLoader.getExtensionLoader().getExtension()方法进入源码
public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
       	//检查是否被加载过,如果有就直接返回,没有就进入createExtension方法,进行创建
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                	//创建扩展点
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    
    private T createExtension(String name) {
    	//会进入到getExtensionClasses()去加载所有的扩展点
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
      	.... 这里省略依赖注入和包装类的方法
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                	//缓存中没有会进入到这里
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();
		//从这里我们可以看到,dubbo支持在很多目录下读文件
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        //下面会进入到loadDirectory方法
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
        	//这段代码是不是看着很熟悉,又是去读取文件
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    //这里就是去解析文件,获得所有的类了
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
  • 分析到上面,我们的静态扩展点就搞定了,剩下的自适应扩展点和激活扩展点在这里
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值