SPI,Service Provider Interface,服务提供者接口,是一种服务发现机制。
JDK 的 SPI 规范
JDK 的 SPI 规范规定:
接口名:可随意定义
实现类名:可随意定义
提供者配置文件路径:其查找的目录为 META-INF/services
提供者配置文件名称:接口的全限定性类名,没有扩展名。
提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名占一行
代码示列:
package com.sqz.spi.jdk;
public interface Human {
public void say();
}
package com.sqz.spi.jdk;
public class Huang implements Human{
@Override
public void say() {
System.out.println("黄种人");
}
}
package com.sqz.spi.jdk;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Run {
public static void main(String[] args) {
ServiceLoader<Human> load = ServiceLoader.load(Human.class);
Iterator<Human> iterator = load.iterator();
while(iterator.hasNext()) {
Human next = iterator.next();
next.say();
}
}
}
配置文件在META_INFO下新建services目录,目录下新增文件名称为SPI接口全类名:
文件内容为实现类全类名:
com.sqz.spi.jdk.Huang
Dubbo 的 SPI
Dubbo 并没有直接使用 JDK 的 SPI,而是在其基础之上对其进行了改进。
规范说明
Dubbo 的 SPI 规范是:
接口名:可以随意定义 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串 提供者配置文件路径:在依次查找的目录为
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
提供者配置文件名称:接口的全限定性类名,无需扩展名
提供者配置文件内容:文件的内容为 key=value 形式,
value为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占一行。
提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader
类,用于加载提供者配置文件中指定的实现类,并创建相应的实例。
代码示列:
项目加入maven依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>3.0.2</version>
</dependency>
测试代码:
package com.sqz.spi.dubbo;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Order {
String way();
}
package com.sqz.spi.dubbo;
public class AlipayOrder implements Order{
@Override
public String way() {
return "支付宝支付";
}
}
package com.sqz.spi.dubbo;
public class WeChatOrder implements Order{
@Override
public String way() {
return "微信支付方式";
}
}
package com.sqz.spi.dubbo;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Run {
public static void main(String[] args) {
ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class);
Order alipay = extensionLoader.getExtension("alipay");
System.out.println(alipay.way());
Order wechat = extensionLoader.getExtension("wechat");
System.out.println(wechat.way());
}
}
配置文件可以在以下任意目录
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
这里在META_INFO下新建services目录,目录下新增文件名称为SPI接口全类名:
内容为键值对:
alipay=com.sqz.spi.dubbo.AlipayOrder
wechat=com.sqz.spi.dubbo.WeChatOrder
Adaptive 机制
Adaptive 机制,即扩展类的自适应机制。即其可以指定想要加载的扩展名,也可以不指定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive注解实现的。
@Adaptive 注解可以修饰类与方法,其作用不一样。
@Adaptive 修饰类
代码测试:
package com.sqz.adaptive.clazz;
import org.apache.dubbo.common.extension.SPI;
@SPI("alipay")
public interface Order {
String way();
}
package com.sqz.adaptive.clazz;
public class WechatOrder implements Order {
@Override
public String way() {
return "微信支付";
}
}
package com.sqz.adaptive.clazz;
public class AlipayOrder implements Order {
@Override
public String way() {
return "支付宝支付";
}
}
package com.sqz.adaptive.clazz;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.common.utils.StringUtils;
@Adaptive
public class AdativeOrder implements Order {
private String orderName;
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public String way(){
ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
String name = orderName;
Order order;
if(StringUtils.isEmpty(name)) {
order = loader.getDefaultExtension();
} else {
order = loader.getExtension(name);
}
return order.way();
}
}
package com.sqz.adaptive.clazz;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Run {
public static void main(String[] args) {
// extracted1();
extracted2();
}
private static void extracted1() {
ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class);
Order order = extensionLoader.getAdaptiveExtension();
((AdativeOrder)order).setOrderName("alipay");
System.out.println(order.way());
}
private static void extracted2() {
ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class);
Order order = extensionLoader.getAdaptiveExtension();
System.out.println(order.way());
}
}
配置文件:
配置文件内容:
adative=com.sqz.adaptive.clazz.AdativeOrder
alipay=com.sqz.adaptive.clazz.AlipayOrder
weichat=com.sqz.adaptive.clazz.WechatOrder
Adaptive方法
Adaptive 方法的定义规范仅一条:其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。方法调用者是通过URL 传递要加载的扩展名的。
注意该URL类,是dubbo自己的:org.apache.dubbo.common.URL
看案例:
package com.sqz.adaptive.method;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Order {
String way();
@Adaptive
String pay(URL url);
}
package com.sqz.adaptive.method;
import org.apache.dubbo.common.URL;
public class AlipayOrder implements Order{
@Override
public String way() {
System.out.println("--way-使用支付宝支付");
return "支付宝支付";
}
@Override
public String pay(URL url) {
System.out.println("--pay-使用支付宝支付");
return "pay 支付宝支付";
}
}
package com.sqz.adaptive.method;
import org.apache.dubbo.common.URL;
public class WechatOrder implements Order{
@Override
public String way() {
System.out.println("--wechat-使用微信支付");
return "微信支付";
}
@Override
public String pay(URL url) {
System.out.println("--pay-使用微信支付");
return "pay 微信支付";
}
}
package com.sqz.adaptive.method;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Run {
public static void main(String[] args) {
extracted();
}
private static void extracted() {
ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class);
Order order = extensionLoader.getAdaptiveExtension();
URL url = URL.valueOf("http://localhost:8080?order=alipay");
System.out.println(order.pay(url));
// System.out.println(order.way()); //报错UnsupportedOperationException,非@Adaptive方法不能自适应
}
}
配置文件:
内容:
alipay=com.sqz.adaptive.method.AlipayOrder
wechat=com.sqz.adaptive.method.WechatOrder
需要注意的是:非@Adaptive方法不能自适应,报错UnsupportedOperationException
Wrapper 机制
Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是 Wrapper 设计模式的应用。一个 SPI 可以包含多个 Wrapper。
Wrapper 类规范
Wrapper 机制不是通过注解实现的,而是通过一套 Wrapper 规范实现的。
Wrapper 类在定义时需要遵循如下规范。(其实就是装饰设计模式的规范)
该类要实现 SPI 接口
该类中要有 SPI 接口的引用
该类中 SPI 接口实例是通过仅包含一个 SPI 接口参数的带参构造器传的(源码中判断是否是Wrapper类是以这个为标准的)
在接口实现方法中要调用 SPI 接口引用对象的相应方法
该类名称以 Wrapper 结尾
代码实例
package com.sqz.wrapper;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Order {
String way();
}
package com.sqz.wrapper;
public class AlipayOrder implements Order {
@Override
public String way() {
System.out.println("支付宝支付");
return "支付宝支付成功";
}
}
package com.sqz.wrapper;
public class WeChatOrder implements Order {
@Override
public String way() {
System.out.println("微信支付");
return "微信支付成功";
}
}
package com.sqz.wrapper;
public class OrderWrapper1 implements Order{
private Order order;
public OrderWrapper1(Order order) {
this.order = order;
}
public String way() {
System.out.println("w1 way before");
String way = order.way();
System.out.println("w1 way after");
return way;
}
}
package com.sqz.wrapper;
public class OrderWrapper2 implements Order{
private Order order;
public OrderWrapper2(Order order) {
this.order = order;
}
public String way() {
System.out.println("w2 way before");
String way = order.way();
System.out.println("w2 way after");
return way;
}
}
package com.sqz.wrapper;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Run {
public static void main(String[] args) {
ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class);
Order wrapper = extensionLoader.getExtension("alipay");
System.out.println(wrapper.way());
}
}
配置文件:
配置文件内容:
alipay=com.sqz.wrapper.AlipayOrder
wechat=com.sqz.wrapper.WeChatOrder
wrapper1=com.sqz.wrapper.OrderWrapper1
wrapper2=com.sqz.wrapper.OrderWrapper2
运行结果:
w2 way before
w1 way before
支付宝支付
w1 way after
w2 way after
支付宝支付成功
Activate 机制
用于激活扩展类的。
Activate 机制,即扩展类的激活机制。通过指定的条件来激活当前的扩展类。其是通过@Activate 注解实现的。
@Activate 注解
在@Activate 注解中共有五个属性,其中 before、after 两个属性已经过时,剩余有效属性还有三个。它们的意义为:
group:为扩展类指定所属的组别,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以属于多个组。
value:为当前扩展类指定的 key(必须是META-INF/dubbo/internal/com.abc.spi.Order中配置的前缀),是当前扩展类的一个标识。String[]类型,表示一个扩展类可以有多个指定的 key。
order:指定筛选条件相同的扩展类的加载顺序。序号越小,优先级越高。默认值为 0。
案例:
package com.sqz.activate;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Order {
String way();
}
package com.sqz.activate;
import org.apache.dubbo.common.extension.Activate;
@Activate(group = {"online"})
public class AlipayOrder implements Order {
@Override
public String way() {
System.out.println("支付宝支付");
return "支付宝支付成功";
}
}
package com.sqz.activate;
import org.apache.dubbo.common.extension.Activate;
@Activate(group = {"online","offline"})
public class CardOrder implements Order{
@Override
public String way() {
System.out.println("card支付");
return "card支付成功";
}
}
package com.sqz.activate;
import org.apache.dubbo.common.extension.Activate;
@Activate(group = {"offline"})
public class CashOrder implements Order{
@Override
public String way() {
System.out.println("Cash支付");
return "Cash支付成功";
}
}
package com.sqz.activate;
import org.apache.dubbo.common.extension.Activate;
@Activate(group = {"offline"},order = -1)
public class CouponOrder implements Order{
@Override
public String way() {
System.out.println("Coupon支付");
return "Coupon支付成功";
}
}
package com.sqz.activate;
import org.apache.dubbo.common.extension.Activate;
@Activate(group = {"online"})
public class WeChatOrder implements Order {
@Override
public String way() {
System.out.println("微信支付");
return "微信支付成功";
}
}
package com.sqz.activate;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.List;
public class Run {
public static void main(String[] args) {
ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class);
List<Order> activateExtensions = extensionLoader.getActivateExtension(URL.valueOf(""), "", "offline");
for (Order order : activateExtensions) {
System.out.println(order.way());
}
}
}
配置文件:
配置文件内容:
alipay=com.sqz.activate.AlipayOrder
card=com.sqz.activate.CardOrder
cash=com.sqz.activate.CashOrder
coupon=com.sqz.activate.CouponOrder
wechat=com.sqz.activate.WeChatOrder
运行结果:
Coupon支付
Coupon支付成功
card支付
card支付成功
Cash支付
Cash支付成功