1.JDK SPI
JDK 中 提供了一个 SPI 的功能,核心类是 java.util.ServiceLoader。其作用就是,可以通过类名获取在META-INF/services/下的多个配置实现文件。
META-INF/services/下创建一个 com.atguigu.SuperLoggerConfiguration
通过 ServiceLoader 获取我们的 SPI 机制配置的实现类
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);
SPI的目的是为了增强扩展性,将固定的配置提取出来,然后通过SPI机制配置。那么既然如此就会有一个默认的配置类。 这样就会出现一个接口多个实现,那么如何找到是哪个实现类呢。
所以使用Iterator获取实现配置。而不是get只获取一个配置类。
所以JDK SPI的一个缺点就是无法确定哪个类的实现,而且没有去重的概念。
2.Dubbo SPI
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外在使用时还需要在接口上标注 @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的区别在于Dubbo spi可以利用别名,可以通过某个扩展点的别名来获取固定的扩展点。 这样就避免了JDK spi中的无法确定是哪一个类实现的配置。
@SPI("dubbo")
public interface Protocol {
......
}
通过spi可以确实是别名为dubbo的实现了配置。
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();
Dubbo 的 SPI 中还有一个“加载优先级”,优先加载内置(internal)的,然后加载外部的(external),按优先级顺序加载,如果遇到重复就跳过不会加载了。
由于有别名的存在所以有去重的概念。
3.Spring spi
META-INF/spring.factories
主要是在配置文件中将全部的配置类都写在一个配置文件中。和jdk spi不一样的地方。省去了配置一大堆文件的麻烦。
Spring 也是支持 ClassPath 中存在多个 spring.factories 文件的,加载时会按照 classpath 的顺序依次加载这些 spring.factories 文件,添加到一个 ArrayList 中。由于没有别名,所以也没有去重的概念,有多少就添加多少。
但是Spring Boot 中的 ClassLoader 会优先加载项目中的文件,而不是依赖包中的文件。所以如果项目中定义了spring.factories文件,那么项目中的文件会被第一个加载。
如果想扩展某个接口的话,只需要新建META-INF/spring.factories文件,然后添加配置即可,不用复制以前的再添加新的。
4.总结
三种 SPI 机制对比之下,JDK 内置的机制是最弱鸡的,但是由于是 JDK 内置,所以还是有一定应用场景,毕竟不用额外的依赖;Dubbo 的功能最丰富,但机制有点复杂了,而且只能配合 Dubbo 使用,不能完全算是一个独立的模块;Spring 的功能和JDK的相差无几,最大的区别是所有扩展点写在一个 spring.factories 文件中,也算是一个改进,并且 IDEA 完美支持语法提示。
|