当前位置: 首页 > 科技观察

Dubbo的SPI实现和JDK实现的区别

时间:2023-03-15 14:55:46 科技观察

在Java中,为了规范开发,制定了大量的“规范”和“标准”。这些上层内容大多以接口的形式提供。那么这些接口最终由谁、在哪里实现呢?规范不关心这个。所谓规范就是规定了一系列的内容来指导我们的开发和实施。例如,Servlet规范解释了Servlet的行为。实现的时候,可以是Tomcat、Jetty等。再比如Java的JDBC规范,规定了驱动提供者需要实现什么,但具体来说,Oracle或者MySQL都可以支持。可以看看之前关于JDBC的文章(没想到你和JDBC一样)。之前,我们可以使用Class.forName来加载Driver的具体实现类。从JDK1.6开始,官方提供了一种叫做“SPI”的机制,可以更加方便快捷的加载相应的实现类,无需我们的关注。我们需要做的就是将包含实现类的JAR文件放在类路径中。最近刚看了一些Dubbo的源码,里面有Dubbo的另一种SPI实现,区别于JDK。那么在这篇文章中,我们就来看看Dubbo的“SPI”实现,以及它和JDK实现的区别。首先,什么是SPI?SPI(ServiceProviderInterfaces)可以理解为第三方实现的API。JDK文档描述服务是一组众所周知的接口和(通常是抽象的)类。服务提供者是服务的具体实现。Java中用到SPI的这些地方:JDBCJNDIJavaXMLProcessingAPILocaelNIOChannelProvider...通过这个SPI的实现,我们的应用好像有了可插拔的能力。在我们之前的文章《Tomcat中的可插拔与SCI的实现原理》中,我们也分析了如何在容器中实现可插拔。SPI在JDK中是如何实现的?JDK中包含了一个SPI的核心类:ServiceLoader。当我们需要加载Provider类时,我们需要做的是:ServiceLoader.load(Provider.class);JDK中的规范AllProviders必须在JAR文件的META-INF/services目录下包含一个文件,文件名是我们要实现的Service名称的全路径。比如我们熟悉的JDBC的MySQL实现,在mysql-connector中,有这样一个文件META-INF/services/java.sql.Driver,这些provider是什么时候加载的?因为Provider的加载和初始化是Lazy的实现,所以需要的时候,可以遍历Provider的Iterator,按需加载,加载的会存在缓存中。但是有些实现不想偷懒,所以在ServiceLoader的load执行完后直接加载并初始化所有的实现,比如这次的JDBC,所以Tomcat内存泄漏,可以查看之前的文章(Tomcat和内存泄漏处理)继续说具体的加载时机。我们一般会在Spring配置中添加一个数据源。这个数据源通常在启动时被初始化为一个Bean,此时会设置数据源中配置的驱动程序。当这些内容传入Bean时,会调用DriverManager的初始化static{loadInitialDrivers();println("JDBCDriverManagerManagerinitialized");}loadInitialDrivers。除了ServiceLoader.load,还初始化了ServiceLoaderloadedDrivers=ServiceLoader。load(Driver.class);IteratordriversIterator=loadedDrivers.iterator();try{while(driversIterator.hasNext()){driversIterator.next();}}catch(Throwablet){//Donothing}returnnull;我们来看一下Dubbo的SPI实现。如果你能看一下Dubbo的源码,你会发现在实现上并没有用到JDK的SPI,而是自己设计了一个。下面先从Main类开始,看看具体实现。从我们使用的入口来看,第一步是传入一个接口,然后传入期望实现的名称SpringContainercontainer=(SpringContainer)ExtensionLoader.getExtensionLoader(Container.class).getExtension("spring");这里通过入口是Container.class,期望的实现是spring。//synchronizedetExtensionClassesprivateMap>loadExtensionClasses(){finalSPIdefaultAnnotation=type.getAnnotation(SPI.class);if(defaultAnnotation!=null){Stringvalue=defaultAnnotation.value();if((valuevalue=value.trim()).length()>0){String[]names=NAME_SEPARATOR.split(value);if(names.length>1){thrownewIllegalStateException("morethan1defaultextensionnameonextension"+type.getName()+":"+Arrays.toString(names));}if(names.length==1)cachedDefaultName=names[0];}}Map>extensionClasses=newHashMap>();loadDirectory(ExtensionClasses,dubbo_internal_directory);loadDirectory(ExtensionClasses,dubbo_directory);loadDirectory(ExtensionClasses,services_directory);returnExtensionClasses;}INF/services/privatevoidloadDirectory(地图>extensionClasses,Stringdir){StringfileName=dir+type.getName();try{Enumerationurls;ClassLoaderclassLoader=findClassLoader();if(classLoader!=null){urls=classLoader.getResources(fileName);}else{urls=ClassLoader.getSystemResources(fileName);}if(urls!=null){while(urls.hasMoreElements()){java.net.URLresourceURL=urls.nextElement();loadResource(extensionClasses,classLoader,resourceURL);}}}catch(Throwablet){logger.error("Exceptionwhenloadextensionclass(interface:"+type+",descriptionfile:"+fileName+").",t);}}这里通过classLoader,看for匹配传入的特定名称的文件,java.net.URLresourceURL=urls.nextElement();这时候会获取到一个包含该文件的URLPath,然后通过loadResource加载资源。此时获取的文件内容为spring=com.alibaba.dubbo.container.spring.SpringContainer更进一步,加载等号后面的类完成loadClass时,并不是直接通过类似Class.forName等形式加载,而是下面这个类型:privatevoidloadClass(Map>extensionClasses,java.net.URLresourceURL,Classclazz,Stringname)throwsNoSuchMethodException{if(!type.isAssignableFrom(clazz)){thrownewIllegalStateException("Errorwhenloadextensionclass(interface:"+type+",classline:"+clazz.getName()+"),class"+clazz.getName()+"isnotsubtypeofinterface.");}if(clazz.isAnnotationPresent(Adaptive.class)){if(cachedAdaptiveClass==null){cachedAdaptiveClass=clazz;}elseif(!cachedAdaptiveClass.equals(clazz)){thrownewIllegalStateException("Morethan1adaptiveclassfound:"+cachedAdaptiveClass.getClass().getName()+","+clazz.getClass().getName());}}elseif(isWrapperClass(clazz)){Set>wrappers=cachedWrapperClasses;if(wrappers==null){cachedWrapperClasses=newConcurrentHashSet>();wrappers=cachedWrapperClasses;}wrappers.add(clazz);}else{clazz.getConstructor();if(name==null||name.length()==0){name=findAnnotationName(clazz);if(name.length()==0){thrownewIllegalStateException("Nosuchextensionnamefortheclass"+clazz.getName()+"intheconfig"+resourceURL);}}String[]names=NAME_SEPARATOR.split(name);if(names!=null&&names.length>0){Activateactivate=clazz.getAnnotation(Activate.class);if(activate!=null){cachedActivates.put(names[0],activate);}for(Stringn:names){if(!cachedNames.containsKey(clazz)){cachedNames.put(clazz,n);}类c=extensionClasses.get(n);if(c==null){extensionClasses.put(n,clazz);}elseif(c!=clazz){thrownewIllegalStateException("Duplicateextension"+type.getName()+"name"+n+"on"+c.getName()+"and"+clazz.getName());}}}}}加载完成后,需要对类进行初始化。这时候直接创建一个newInstance,然后通过reflect注入方法将相应的属性设置进去privateTcreateExtension(Stringname){Classclazz=getExtensionClasses().get(name);if(clazz==null){throwfindException(name);}try{Tinstance=(T)EXTENSION_INSTANCES.get(clazz);if(instance==null){EXTENSION_INSTANCES.putIfAbsent(clazz,clazz.newInstance());instance=(T)EXTENSION_INSTANCES.get(clazz);}injectExtension(instance);Set>wrapperClasses=cachedWrapperClasses;if(wrapperClasses!=null&&!wrapperClasses.isEmpty()){for(ClasswrapperClass:wrapperClasses){instance=injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance));}}returninstance;}catch(Throwablet){thrownewIllegalStateException("Extensioninstance(name:"+name+",class:"+type+")couldnotbeinstantiated:"+t.getMessage(),t);}}privateTinjectExtension(Tinstance){try{if(objectFactory!=null){for(Methodmethod:instance.getClass().getMethods()){if(method.getName().startsWith("set")&&method.getParameterTypes().length==1&&Modifier.isPublic(method.getModifiers())){Classpt=method.getParameterTypes()[0];try{Stringproperty=method.getName().length()>3?method.getName().substring(3,4).toLowerCase()+method.getName().substring(4):"";Objectobject=objectFactory.getExtension(pt,property);if(object!=null){method.invoke(instance,object);}}catch(Exceptione){logger.error("failtoinjectviamethod"+method.getName()+"ofinterface"+type.getName()+":"+e.getMessage(),e);}}}}}catch(Exceptione){logger.error(e.getMessage(),e);}returninstance;}通过上面的描述,我们可以看出JDK和Dubbo的SPI实现,虽然都是从JAR中加载了相应的扩展,但还是有一些明显的区别。例如:Dubbo支持更多的加载路径。同时,它没有使用Iterator的形式,而是直接通过名字定位到具体的Provider,按需加载,效率更高。高,同时支持Provider以类似IOC的形式提供【本文为专栏作家“侯树成”原创稿件,转载请通过作者微信获取授权公众号“Tomcat那些事”】点击在这里可以看到更多关于作者的好文