Sebagai contoh, kini kami telah mereka bentuk rangka kerja pengelogan baharu: "super-logger" . Secara lalai, fail XML digunakan sebagai fail konfigurasi log kami, dan antara muka untuk penghuraian fail konfigurasi direka:
package com.github.kongwu.spisamples; public interface SuperLoggerConfiguration { void configure(String configFile); }
Kemudian terdapat pelaksanaan XML lalai:
package com.github.kongwu.spisamples; public class XMLConfiguration implements SuperLoggerConfiguration{ public void configure(String configFile){ ...... } }
Kemudian apabila kita memulakan dan menghuraikan konfigurasi, kita hanya perlu memanggil XMLConfiguration ini untuk menghuraikan fail konfigurasi XML
package com.github.kongwu.spisamples; public class LoggerFactory { static { SuperLoggerConfiguration configuration = new XMLConfiguration(); configuration.configure(configFile); } public static getLogger(Class clazz){ ...... } }
Dengan cara ini, model asas selesai, dan di sana nampaknya tiada masalah. Walau bagaimanapun, skalabiliti tidak begitu baik, kerana jika saya ingin menyesuaikan/melanjutkan/menulis semula fungsi penghuraian, saya perlu mentakrifkan semula kod masuk dan menulis semula LoggerFactory Ia tidak cukup fleksibel dan terlalu mengganggu.
Sebagai contoh, jika pengguna/pengguna kini ingin menambah fail yml sebagai fail konfigurasi log, maka dia hanya perlu mencipta YAMLConfiguration baharu dan melaksanakan SuperLoggerConfiguration. Tetapi...bagaimana untuk menyuntiknya, bagaimana untuk menggunakan YAMLConfiguration yang baru dibuat dalam LoggerFactory? Adakah mungkin LoggerFactory telah ditulis semula?
Jika anda menggunakan mekanisme SPI, perkara ini akan menjadi sangat mudah, dan fungsi pengembangan pintu masuk ini boleh diselesaikan dengan mudah.
Mari kita lihat dahulu cara menggunakan mekanisme SPI JDK untuk menyelesaikan masalah kebolehskalaan di atas.
JDK menyediakan fungsi SPI dan kelas teras ialah java.util.ServiceLoader. Fungsinya adalah untuk mendapatkan berbilang fail pelaksanaan konfigurasi di bawah "META-INF/services/" melalui nama kelas.
Untuk menyelesaikan masalah pengembangan di atas, kini kami mencipta fail META-INF/services/
(tanpa akhiran) di bawah com.github.kongwu.spisamples.SuperLoggerConfiguration
. Hanya terdapat satu baris kod dalam fail, yang merupakan lalai kami com.github.kongwu.spisamples.XMLConfiguration
(perhatikan bahawa berbilang pelaksanaan juga boleh ditulis dalam satu fail, dipisahkan oleh carriage returns)
META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration: com.github.kongwu.spisamples.XMLConfiguration
Kemudian dapatkan SPI kami konfigurasi mekanisme melalui kelas Pelaksanaan ServiceLoader:
ServiceLoader<SuperLoggerConfiguration> serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class); Iterator<SuperLoggerConfiguration> iterator = serviceLoader.iterator(); SuperLoggerConfiguration configuration; while(iterator.hasNext()) { //加载并初始化实现类 configuration = iterator.next(); } //对最后一个configuration类调用configure方法 configuration.configure(configFile);
Akhir sekali, laraskan konfigurasi permulaan dalam LoggerFactory kepada kaedah SPI semasa:
package com.github.kongwu.spisamples; public class LoggerFactory { static { ServiceLoader<SuperLoggerConfiguration> serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class); Iterator<SuperLoggerConfiguration> iterator = serviceLoader.iterator(); SuperLoggerConfiguration configuration; while(iterator.hasNext()) { configuration = iterator.next();//加载并初始化实现类 } configuration.configure(configFile); } public static getLogger(Class clazz){ ...... } }
"Tunggu, di sini Mengapa menggunakan iterator? Daripada kaedah seperti get yang hanya memperoleh satu contoh "
Bayangkan, jika ia adalah kaedah perolehan tetap, maka apa yang anda dapat ialah contoh tetap, SPI Apakah itu? titik?
Tujuan SPI adalah untuk meningkatkan kebolehskalaan. Ekstrak konfigurasi tetap dan konfigurasikannya melalui mekanisme SPI. Dalam kes itu, biasanya terdapat konfigurasi lalai, dan kemudian pelaksanaan yang berbeza dikonfigurasikan melalui fail SPI, jadi akan terdapat masalah berbilang pelaksanaan satu antara muka. Jika berbilang pelaksanaan ditemui, pelaksanaan yang manakah digunakan sebagai contoh terakhir?
Jadi iterator digunakan di sini untuk mendapatkan semua konfigurasi kelas pelaksanaan. Pelaksanaan SuperLoggerConfiguration lalai baru sahaja ditambahkan pada pakej "super-logger" kami.
Untuk menyokong konfigurasi YAML, sekarang tambahkan konfigurasi YAMLConfiguration SPI dalam kod pengguna/pengguna:
META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration: com.github.kongwu.spisamples.ext.YAMLConfiguration
Pada masa ini, ia akan diperolehi melalui iterator kaedah Terdapat dua kelas pelaksanaan konfigurasi: XMLConfiguration lalai dan YAMLConfiguration yang kami lanjutkan.
Dalam kod yang dimuatkan di atas, kami melintasi iterator, dan sehingga akhir, kami menggunakan konfigurasi pelaksanaan terakhir sebagai contoh terakhir.
"Tunggu sebentar? Yang terakhir? Bagaimana untuk mengira yang terakhir?"
Adakah YAMLConfiguration yang ditentukan pengguna ini semestinya yang terakhir?
Ini tidak semestinya benar Ia bergantung pada konfigurasi ClassPath apabila kita menjalankan balang yang dimuatkan di hadapan secara semula jadi di hadapan, dan balang dalam balang terakhir secara semula jadi di belakang. Jadi "Jika pakej pengguna berada kemudian dalam ClassPath daripada pakej super-logger, ia akan berada di kedudukan terakhir; jika pakej pengguna berada di hadapan, maka yang dipanggil terakhir masih merupakan XMLConfiguration lalai ." Konfigurasi YAMLConfiguration SPI ialah Fail berada dalam , maka elemen terakhir yang diperoleh oleh iterator mestilah YAMLConfiguration.
Tetapi bagaimana jika susunan laluan kelas diterbalikkan? main.jar di hadapan, super-logger.jar di belakang java -cp super-logger.jar:a.jar:b.jar:main.jar example.Main
Dengan cara ini, elemen terakhir yang diperoleh oleh iterator menjadi XMLConfiguration lalai. Tidak masuk akal untuk kita menggunakan JDK SPI, dan yang diperolehi Adakah ia yang pertama, atau XMLConfiguration lalai.
Memandangkan susunan pemuatan (laluan kelas) ditentukan oleh pengguna, sama ada kami memuatkan yang pertama atau yang terakhir, ada kemungkinan konfigurasi yang ditentukan pengguna tidak boleh dimuatkan. super-logger.jar
main.jar
Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。
Dubbo 中实现了一套新的 SPI 机制,功能更强大,也更复杂一些。相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下(以下demo来自dubbo官方文档)。
optimusPrime = org.apache.spi.OptimusPrime bumblebee = org.apache.spi.Bumblebee
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外在使用时还需要在接口上标注 @SPI 注解。
下面来演示 Dubbo SPI 的用法:
@SPI public interface Robot { void sayHello(); } public class OptimusPrime implements Robot { @Override public void sayHello(){ System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello(){ System.out.println("Hello, I am Bumblebee."); } } public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); } }
「Dubbo SPI 和 JDK SPI 最大的区别就在于支持“别名”」,可以通过某个扩展点的别名来获取固定的扩展点。就像上面的例子中,我可以获取 Robot 多个 SPI 实现中别名为“optimusPrime”的实现,也可以获取别名为“bumblebee”的实现,这个功能非常有用!
通过 @SPI 注解的 value 属性,还可以默认一个“别名”的实现。比如在Dubbo 中,默认的是Dubbo 私有协议:「dubbo protocol - dubbo://」**
来看看Dubbo中协议的接口:
@SPI("dubbo") public interface Protocol { ...... }
在 Protocol 接口上,增加了一个 @SPI 注解,而注解的 value 值为 Dubbo ,通过 SPI 获取实现时就会获取 Protocol SPI 配置中别名为dubbo的那个实现,com.alibaba.dubbo.rpc.Protocol
文件如下:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol registry=com.alibaba.dubbo.registry.integration.RegistryProtocol qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper
然后只需要通过getDefaultExtension,就可以获取到 @SPI 注解上value对应的那个扩展实现了
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension(); //protocol: DubboProtocol
还有一个 Adaptive 的机制,虽然非常灵活,但……用法并不是很“优雅”,这里就不介绍了
Dubbo 的 SPI 中还有一个“加载优先级”,优先加载内置(internal)的,然后加载外部的(external),按优先级顺序加载,「如果遇到重复就跳过不会加载」了。
所以如果想靠classpath加载顺序去覆盖内置的扩展,也是个不太理智的做法,原因同上 - 加载顺序不严谨
Spring 的 SPI 配置文件是一个固定的文件 - META-INF/spring.factories
,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单:
//获取所有factories文件中配置的LoggingSystemFactory List<LoggingSystemFactory>> factories = SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);
下面是一段 Spring Boot 中 spring.factories 的配置
# Logging Systems org.springframework.boot.logging.LoggingSystemFactory=\ org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\ org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\ org.springframework.boot.logging.java.JavaLoggingSystem.Factory # PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\ org.springframework.boot.context.config.StandardConfigDataLocationResolver ......
Spring SPI 中,将所有的配置放到一个固定的文件中,省去了配置一大堆文件的麻烦。至于多个接口的扩展配置,是用一个文件好,还是每个单独一个文件好这个,这个问题就见仁见智了(个人喜欢 Spring 这种,干净利落)。
Spring的SPI 虽然属于spring-framework(core),但是目前主要用在spring boot中……
和前面两种 SPI 机制一样,Spring 也是支持 ClassPath 中存在多个 spring.factories 文件的,加载时会按照 classpath 的顺序依次加载这些 spring.factories 文件,添加到一个 ArrayList 中。由于没有别名,所以也没有去重的概念,有多少就添加多少。
但由于 Spring 的 SPI 主要用在 Spring Boot 中,而 Spring Boot 中的 ClassLoader 会优先加载项目中的文件,而不是依赖包中的文件。所以如果在你的项目中定义个spring.factories文件,那么你项目中的文件会被第一个加载,得到的Factories中,项目中spring.factories里配置的那个实现类也会排在第一个
如果我们要扩展某个接口的话,只需要在你的项目(spring boot)里新建一个META-INF/spring.factories
文件,「只添加你要的那个配置,不要完整的复制一遍 Spring Boot 的 spring.factories 文件然后修改」**
比如我只想添加一个新的 LoggingSystemFactory 实现,那么我只需要新建一个META-INF/spring.factories
文件,而不是完整的复制+修改:
org.springframework.boot.logging.LoggingSystemFactory=\ com.example.log4j2demo.Log4J2LoggingSystem.Factory
JDK SPI
DUBBO SPI
Spring SPI
文件方式 |
每个扩展点单独一个文件 |
每个扩展点单独一个文件 |
所有的扩展点在一个文件 |
获取某个固定的实现 |
Tidak disokong, anda hanya boleh mendapatkan semua pelaksanaan mengikut urutan |
Terdapat konsep "alias", anda boleh mendapatkan pelaksanaan tetap sambungan titik mengikut nama, Sangat mudah untuk bekerjasama dengan anotasi Dubbo SPI |
tidak disokong, anda hanya boleh mendapatkan semua pelaksanaan dengan teratur. Walau bagaimanapun, memandangkan Spring Boot ClassLoader akan memberi keutamaan untuk memuatkan fail dalam kod pengguna, ia boleh memastikan bahawa fail spring.factoires yang ditakrifkan pengguna adalah yang pertama, dan sambungan tersuai boleh diperolehi dengan tetap dengan mendapatkan kilang pertama |
Lain-lain |
Tiada |
Menyokong suntikan pergantungan di dalam Dubbo melalui direktori Bezakan antara binaan Dubbo -dalam SPI dan SPI luaran, muatkan yang dalaman dahulu untuk memastikan yang dalaman mempunyai keutamaan tertinggi Kesempurnaan |
Artikel & maklumat pihak ketiga cukup kaya |
Dokumentasi tidak cukup kaya, tetapi disebabkan oleh beberapa fungsi, ia sangat mudah digunakan | Sokongan IDE |
Tiada |
|
Sokongan sempurna IDEA, dengan petua sintaks | Perbandingan tiga mekanisme SPI Antaranya, mekanisme terbina dalam JDK adalah yang paling lemah, tetapi kerana ia terbina dalam JDK, ia masih mempunyai senario aplikasi tertentu Lagipun, tidak ada keperluan untuk kebergantungan tambahan; Dubbo mempunyai fungsi terkaya, tetapi mekanismenya agak rumit, dan ia hanya boleh digunakan dengan Dubbo. , tidak boleh dianggap sepenuhnya sebagai modul bebas; fungsi Spring hampir sama dengan JDK Perbezaan terbesar ialah semua titik sambungan ditulis dalam fail spring.factories, yang juga merupakan peningkatan, dan IDEA menyokong dengan sempurna. gesaan sintaks. |
Atas ialah kandungan terperinci Apakah perbezaan antara tiga mekanisme SPI Java Spring Dubbo. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!