文章目录
- JAVA的SPI
- SPI是什么
- 为什么要使用
- 使用场景
- 如何使用
- 原理
- 存在问题
- DUBBO的SPI
- DUBBO的SPI用法
- @SPI
- @Active 条件激活
- 接口及实现类
- 配置文件
- 测试案例
- @Adaptive 自适配
- @Adaptive在SPI接口实现类上
- @Adaptive在SPI接口方法上
- DUBBO中动态编译的Class
JAVA的SPI
SPI是什么
SPI全称service provider interface,其作用是寻找接口的实现类,将接口与实现类完全分离,是面向接口编程的最后一步
为什么要使用
面向的对象的设计里,推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。解决了接口与实现类的解耦。
使用场景
提供功能的扩展点,解耦接口与实现类,提高框架的透明化。常见的使用案例:日志的门面包slf4j、jdbc4.0以上的驱动包、spring boot的自动装配。
如何使用
在JDK中的SPI使用步骤如下:
1、在资源目录resource下的MEAT-INF/services中创建配置文件(以接口完整class名为配置文件名,文件内容为接口实现类完整class名且分行符分隔,文件使用UTF-8编码格式)
2、在JAVA代码中使用ServiceLoader.load获取对应接口的加载器对象,遍历其实现了迭代器的加载器对象获取对应的实例调用即可
原理
指定读取MEAT-INF/services目录下的所有文件获取实现类的完整class名,并(使用线程上下文加载器)加载实例化
存在问题
会将配置文件的中实现类全部实例化,不管是否会使用到,造成资源浪费,不能按需加载
DUBBO的SPI
DUBBO的SPI用法
SPI机制解决了实例的加载问题,DUBBO的SPI实现基于健值对的形式进行管理配置文件,为了显示懒加载或运行期选择调用,实现了一套更灵活的过滤选择方式,其使用体现在几个注解的用法中。
@SPI
所有DUBBO的SPI接口必须使用@SPI注解标记,@SPI注解的value值为默认的接口实现类的别名(在配置文件中配置的那些key)
第一步:
这里创建一个api模块,创建SPI接口
pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-common</artifactId>
<version>2.6.5</version>
<scope>provided</scope>
</dependency>
接口
package priv.dev.service;
import com.alibaba.dubbo.common.extension.SPI;
@SPI("a")
public interface ProductService {
String getProduct(String productId);
}
第二步:
package priv.dev.service.impl;
import priv.dev.service.ProductService;
public class ProductServiceAImpl implements ProductService {
@Override
public String getProduct(String productId) {
System.out.println("A,调用了ProductService的A实现类");
return productId;
}
}
package priv.dev.service.impl;
import priv.dev.service.ProductService;
public class ProductServiceBImpl implements ProductService {
@Override
public String getProduct(String productId) {
System.out.println("B,调用了ProductService的B实现类");
return productId;
}
}
package priv.dev.service.impl;
import priv.dev.service.ProductService;
public class ProductServiceCImpl implements ProductService {
@Override
public String getProduct(String productId) {
System.out.println("C,调用了ProductService的C实现类");
return productId;
}
}
在资源目录下的META-INF/dubbo/创建配置文件(以接口完整class名为配置文件名,文件内容为key=value形式,value为接口实现类完整class名,key为别名,且分行符分隔,文件使用UTF-8编码格式)
这里创建一个spi模块,引入api模块和dubbo依赖,在该项目下创建配置文件。没有在配置文件中接口实现类不会被读取。
第三步:
这里创建spi-test模块,引入spi模块和dubbo和junit依赖
测试getDefaultExtension方法获取@SPI标记的SPI接口默认实现类
package priv.dev.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;
import priv.dev.service.ProductService;
public class SpiTest {
@Test
public void testGetDefaultExtension(){
ExtensionLoader<ProductService> loader = ExtensionLoader.getExtensionLoader(ProductService.class);
//loader.getDefaultExtension:获取接口上标记@SPI注解的value值对应的实现类
ProductService extension = loader.getDefaultExtension();
extension.getProduct("xxxx");
}
}
运行效果:
当去掉@SPI接口的value值再次测试getDefaultExtension
运行效果:
由于getDefaultExtension无法获取SPI接口的默认实现类进而调用其方法空指针异常,使用getDefaultExtension在@SPI的value必须指定正确的别名
测试getExtension方法获取指定别名的SPI接口实现类
@Active 条件激活
@Active条件激活,@Active注解表示的是需要满足的条件
在上述第二步的基础上另行分支案例
接口及实现类
package priv.dev.service;
import com.alibaba.dubbo.common.extension.SPI;
@SPI
public interface PaymentService {
String payment(String productId);
}
package priv.dev.service.impl;
import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;
@Activate(group = "dev")
public class PaymentServiceAImpl implements PaymentService {
@Override
public String payment(String productId) {
System.out.println("调用了PaymentService的A实现类");
return productId;
}
}
package priv.dev.service.impl;
import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;
@Activate(group = {"dev", "test"},order = 3)
public class PaymentServiceBImpl implements PaymentService {
@Override
public String payment(String productId) {
System.out.println("调用了PaymentService的B实现类");
return productId;
}
}
package priv.dev.service.impl;
import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;
@Activate(group = "test",order = 2)
public class PaymentServiceCImpl implements PaymentService {
@Override
public String payment(String productId) {
System.out.println("调用了PaymentService的C实现类");
return productId;
}
}
package priv.dev.service.impl;
import com.alibaba.dubbo.common.extension.Activate;
import priv.dev.service.PaymentService;
@Activate(group = "test",value = "ddd",order = 1)
public class PaymentServiceDImpl implements PaymentService {
@Override
public String payment(String productId) {
System.out.println("调用了PaymentService的D实现类");
return productId;
}
}
配置文件
a=priv.dev.service.impl.PaymentServiceAImpl
b=priv.dev.service.impl.PaymentServiceBImpl
c=priv.dev.service.impl.PaymentServiceCImpl
d=priv.dev.service.impl.PaymentServiceDImpl
测试案例
@Active的group属性,表示分组
/**
* 实际情况的group为dev
* 满足匹配条件激活的实现类为PaymentService的A、B实现类
*/
@Test
public void testGetActiveExtension() {
ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
URL url = URL.valueOf("/");
List<PaymentService> extensions = loader.getActivateExtension(url, "", "dev");
extensions.forEach(extension -> extension.payment("xxxx"));
}
@Active的order属性,表示实现类在getActiveExtension返回的实现类集合中的顺序。(dubbo内部使用责任链模式,按顺序调用一批SPI接口的实现类)
/**
* 实际情况的group为test
* 满足匹配条件激活的实现类为PaymentService的B、C实现类
* 且调用顺序为C-》B,由于C实现类的order小于B的order,排在前面
*/
@Test
public void testGetActiveExtension2() {
ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
URL url = URL.valueOf("/");
List<PaymentService> extensions = loader.getActivateExtension(url, "", "test");
for (PaymentService service : extensions) {
service.payment("123");
}
}
@Active的value属性,表示需要在url参数中有key为该值才符合条件
/**
* 实际情况的group为test,且url参数中有key为ddd
* 满足匹配条件激活的实现类为PaymentService的B、D、C实现类
*/
@Test
public void testGetActiveExtension3() {
ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
URL url = URL.valueOf("/");
url = url.addParameter("ddd", "666");
List<PaymentService> extensions = loader.getActivateExtension(url, "", "test");
for (PaymentService service : extensions) {
service.payment("123");
}
}
/**
* 实际情况的group为test,且url参数中有key为fff
* 满足匹配条件激活的实现类为PaymentService的C、B实现类
*/
@Test
public void testGetActiveExtension4() {
ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
URL url = URL.valueOf("/");
url = url.addParameter("fff", "666");
List<PaymentService> extensions = loader.getActivateExtension(url, "", "test");
for (PaymentService service : extensions) {
service.payment("123");
}
}
getActiveExtension的动态选择实现类
/**
* 实际情况的group为test,且url参数中有key为fff
* 当前匹配条件激活的实现类为PaymentService的C、B实现类
* 但由于getActivateExtension方法中指定了key进行动态选择实现类
* 在url上新增key同名的参数,值为a,-c 即为 (+)a:加入a实现 -c:去掉c实现
* 最终匹配条件激活的实现类为PaymentService的B、A实现类
*/
@Test
public void testGetActiveExtension5() {
ExtensionLoader<PaymentService> loader = ExtensionLoader.getExtensionLoader(PaymentService.class);
URL url = URL.valueOf("/");
url = url.addParameter("fff", "666");
url = url.addParameter("dynamic", "a,-c");
List<PaymentService> extensions = loader.getActivateExtension(url, "dynamic", "test");
for (PaymentService service : extensions) {
service.payment("123");
}
}
@Adaptive 自适配
@Adaptive可以使用在SPI接口的实现类上或者在SPI接口方法上
新增一个SPI接口
package priv.dev.service;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.SPI;
@SPI
public interface RecordService {
String addRecord(String message, URL url);
}
package priv.dev.service.impl;
import com.alibaba.dubbo.common.URL;
import priv.dev.service.RecordService;
public class RecordServiceAImpl implements RecordService {
@Override
public String addRecord(String message, URL url) {
System.out.println("调用了RecordService的A实现类");
return message;
}
}
package priv.dev.service.impl;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import priv.dev.service.RecordService;
@Adaptive
public class RecordServiceBImpl implements RecordService {
@Override
public String addRecord(String message, URL url) {
System.out.println("调用了RecordService的B实现类");
return message;
}
}
配置文件
a=priv.dev.service.impl.RecordServiceAImpl
b=priv.dev.service.impl.RecordServiceBImpl
@Adaptive在SPI接口实现类上
在RecordServiceBImpl类上新增@Adaptive注解
/**
* 在RecordService的B实现类上有@Adaptive注解
* 调用B实现类
*/
@Test
public void testAdaptive() {
ExtensionLoader<RecordService> loader = ExtensionLoader.getExtensionLoader(RecordService.class);
RecordService service = loader.getAdaptiveExtension();
URL url = URL.valueOf("/");
service.addRecord("xxxx", url);
}
@Adaptive在SPI接口方法上
注释掉实现类上的@Adaptive注解,在RecordServiceAImpl的方法上标记
@SPI
public interface RecordService {
@Adaptive
String addRecord(String message, URL url);
}
/**
*
* 在接口方法上标记@Adaptive,且接口方法参数必须包含URL或者参数class类中存在get方法返回URL
* 通过在url追加参数,key默认值使用 点 分隔接口的驼峰名,如RecordService的默认key为record.service
* value为实现类的别名
* 由于在A实现类的方法上标记@Adaptive,所以最后调用了A实现类
*/
@Test
public void testAdaptive2(){
ExtensionLoader<RecordService> loader = ExtensionLoader.getExtensionLoader(RecordService.class);
RecordService service = loader.getAdaptiveExtension();
URL url = URL.valueOf("/?record.service=a");
service.addRecord("xxxx", url);
}
@Adaptive自定义vlaue
@SPI
public interface RecordService {
// @Adaptive
@Adaptive("rec")
String addRecord(String message, URL url);
}
/**
* 在接口方法上标记@Adaptive,且接口方法参数必须包含URL或者参数class类中存在get方法返回URL
* 通过在url追加参数,key为rec,value为b
* 所以最后调用了B实现类
*/
@Test
public void testAdaptive3(){
ExtensionLoader<RecordService> loader = ExtensionLoader.getExtensionLoader(RecordService.class);
RecordService service = loader.getAdaptiveExtension();
URL url = URL.valueOf("/?rec=b");
service.addRecord("xxxx", url);
}
DUBBO中动态编译的Class
当在SPI接口的方法中标记@Adaptive注解,在ExtensionLoader的createAdaptiveExtensionClassCode方法会返回拼接的class字符串,通过javassist动态编译生成class,class中静态代理调用SPI接口实现类
以ProxyFactory这个SPI接口为例
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
打印createAdaptiveExtensionClassCode方法返回拼接的class字符串
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0, boolean arg1) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
}
通过分析createAdaptiveExtensionClassCode方法和凭借的class字符串可得知,在调用被@Adaptive标记注解的方法的时候会从方法参数获取URL或者参数class类中存在get方法返回URL获得URL,并通过DUBBO的URL类的url.getParameter获取key为@Adaptive的value的值,且默认值为@SPI的value