写在前面
dubbo提供了SPI机制可以通过外部配置文件来动态加载扩展类,有时我们可能需要配置这些扩展类挨个执行来满足业务场景,进行一些数据的处理等,此时这些扩展类我们都是需要的,还有一些其他的场景,需要根据外部环境的不同(如某参数的值),来动态的选择使用哪个扩展类,针对这种需求,dubbo提供了@Adaptive
注解来完成该功能,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD}) // 在类和方法上使用
public @interface Adaptive {
/**
设置注入哪个扩展类。目标扩展类名称通过在URL中传递的参数值确定,而URL中参数的key是什么就是通过该方法的返回值确定的,可以有多个,默认值是接口简单名称转
点分形式,如MyInterface就是my.interface。如果是在URL上没有设置目标扩展类名称,则会读取在@SPI注解上配置的值。
比如这里配置的值是String[] {"key1", "key2"},首先通过key1从URL上寻找目标值作为扩展类的名字,找不到则使用key2继续寻找,没有找到则使用默认值,即@SPI注解配置的值(没有配置则转换为点分形式作为名称),如果是也没有则会抛出java.lang.IllegalStateException
*/
String[] value() default {};
}
下面看一个简单的例子。
1:简单例子
源码 。
1.1:定义接口和实现类
@SPI("apple")
public interface FruitGranter {
Fruit grant();
@Adaptive
String watering(URL url);
}
我们定义了一个水果种植类的接口FruitGranter,并通过设置@SPI("apple")
来指定该接口为dubbo SPI接口,并设置要使用的默认的扩展类的名称是apple。另外在方法watering
中配置了@Adaptive
注解,代表需要根据URL中的参数来动态调用哪个扩展类方法,然后定义如下两个实现类:
// 苹果种植者
public class AppleGranter implements FruitGranter {
@Override
public Fruit grant() {
return new Apple();
}
@Override
public String watering(URL url) {
System.out.println("watering apple");
return "watering finished";
}
}
// 香蕉种植者
public class BananaGranter implements FruitGranter {
@Override
public Fruit grant() {
return new Banana();
}
@Override
public String watering(URL url) {
System.out.println("watering banana");
return "watering success";
}
}
定义了香蕉种植者和苹果种植者两个类,然后我们在配置文件中进行配置。
1.2:配置文件
首先在classpath下创建目录META-INF/dubbo
,然后创建以FruitGranter全限定名称为文件名的文件,本文文件名称是dongshi.daddy.adaptive.FruitGranter
,然后添加苹果扩展类和香蕉扩展类:
apple=dongshi.daddy.adaptive.AppleGranter
banana=dongshi.daddy.adaptive.BananaGranter
扩展类的名字分别是apple
和banana
。
1.3:测试
如下测试代码是使用名称为banana扩展类:
public class ExtensionLoaderTest {
@Test
public void testGetExtensionLoader() {
// 首先创建一个模拟用的URL对象
URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");
// 通过ExtensionLoader获取一个FruitGranter对象
FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class)
.getAdaptiveExtension();
// 使用该FruitGranter调用其"自适应标注的"方法,获取调用结果
String result = granter.watering(url);
System.out.println(result);
}
}
运行输出如下:
watering banana
watering success
如果是修改URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");
为URL url = URL.valueOf("dubbo://192.168.0.101:20880");
则会使用@SPI("apple")
中设置的apple
作为默认的扩展类名称,此时输出如下:
watering apple
watering finished
此时从URL上寻找扩展名称的key是FruitGranter接口简单名称的点分形式,即fruit.granter
,我们也可以通过在FruitGranter#watering
的@Adaptive
注解中设置key,比如设置为find.fruit.extenstion
,如下:
@SPI("apple")
public interface FruitGranter {
Fruit grant();
// @Adaptive
@Adaptive({ "find.fruit.extenstion" })
String watering(URL url);
}
此时测试代码修改为如下也可以获取到名称为banana
的扩展类:
public class ExtensionLoaderTest {
@Test
public void testGetExtensionLoader() {
// 首先创建一个模拟用的URL对象
// URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");
URL url = URL.valueOf("dubbo://192.168.0.101:20880?find.fruit.extenstion=banana");
// URL url = URL.valueOf("dubbo://192.168.0.101:20880");
// 通过ExtensionLoader获取一个FruitGranter对象
FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class)
.getAdaptiveExtension();
// 使用该FruitGranter调用其"自适应标注的"方法,获取调用结果
String result = granter.watering(url);
System.out.println(result);
}
}
测试输出如下:
watering apple
watering finished
Process finished with exit code 0
以上的例子,我们是将@Adaptive
注解写到接口的方法上,此时dubbo会通过动态代理方式生成接口一个子类,并按照一定的逻辑实现该方法,逻辑其实也很简单,即根据最终获取到的参数值来获取对应的扩展类,并调用其方法,没有添加@Adaptive
注解的方法会默认抛出异常信息,如下是本例生成的动态代码:
public class FruitGranter$Adaptive implements org.apache.dubbo.demo.example.eg19.FruitGranter {
// 没有标注@Adaptive注解的方法,默认抛出java.lang.UnsupportedOperationException异常
public org.apache.dubbo.demo.example.eg19.Fruit grant() {
throw new UnsupportedOperationException(
"The method public abstract org.apache.dubbo.demo.example.eg19.Fruit "
+ "org.apache.dubbo.demo.example.eg19.FruitGranter.grant() of interface "
+ "org.apache.dubbo.demo.example.eg19.FruitGranter is not adaptive method!");
}
// 该方法使用了@Adaptive注解,自动生成动态调用的逻辑
public java.lang.String watering(org.apache.dubbo.common.URL arg0) {
// URL参数必须有值
if (arg0 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg0;
// 以fruit.granter作为key从url上获取参数值,默认值是apple,就是在注解@SPI("apple")配置的值
String extName = url.getParameter("fruit.granter", "apple");
if (extName == null) {
throw new IllegalStateException(
"Failed to get extension (org.apache.dubbo.demo.example.eg19.FruitGranter) name "
+ "from url (" + url.toString() + ") use keys([fruit.granter])");
}
// 调用ExtensionLoader的方法根据扩展类名称获取对应的扩展实现类
org.apache.dubbo.demo.example.eg19.FruitGranter extension =
(org.apache.dubbo.demo.example.eg19.FruitGranter) ExtensionLoader
.getExtensionLoader(org.apache.dubbo.demo.example.eg19.FruitGranter.class)
.getExtension(extName);
// 调用目标扩展实现类的watering方法
return extension.watering(arg0);
}
}
另外,@Adaptive
注解还可以使用在接口的子类上,此时会直接执行该子类逻辑,在dubbo中目前只有ExtensionFactory的子类AdaptiveExtensionFactory是这种使用方法,用来分别从SPI和spring容器获取对象。下面我们再看一个将@Adptive
注解使用在子类上的例子。
2:@Adaptive
注解使用在子类上
源码 。
2.1:定义接口和实现类
2.1.1:接口
@SPI
public interface UseInSubClsInterface {
void sayHi(String word);
}
2.1.2:非Adaptive实现类
public class ConcreteUseInSubClsInterface1 implements UseInSubClsInterface {
@Override
public void sayHi(String word) {
System.out.println("ConcreteUseInSubClsInterface1 say hi: " + word);
}
}
public class ConcreteUseInSubClsInterface2 implements UseInSubClsInterface {
@Override
public void sayHi(String word) {
System.out.println("ConcreteUseInSubClsInterface2 say hi: " + word);
}
}
2.1.3:Adaptive的实现类
@Adaptive
public class AdaptiveUseInSubClsInterface implements UseInSubClsInterface {
// 封装所有的扩展类集合(@Adaptive注解的扩展类除外!!!)
private final List<UseInSubClsInterface> factories;
public AdaptiveUseInSubClsInterface() {
// 获取接口UseInSubClsInterface所有的扩展类
ExtensionLoader<UseInSubClsInterface> loader
= ExtensionLoader.getExtensionLoader(UseInSubClsInterface.class);
List<UseInSubClsInterface> list = new ArrayList<UseInSubClsInterface>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public void sayHi(String word) {
// 任选一个扩展类来调用其方法
int size = factories.size();
Random random = new Random();
factories.get(random.nextInt(size)).sayHi(word);
}
}
为了测试目的,只是获取任意一个扩展类来调用。
2.2:定义SPI文件
文件名称dongshi.daddy.adaptive.useinsubcls.UseInSubClsInterface
,内容如下:
adaptive=dongshi.daddy.adaptive.useinsubcls.AdaptiveUseInSubClsInterface
concreteUseInSubClsInterface1=dongshi.daddy.adaptive.useinsubcls.ConcreteUseInSubClsInterface1
concreteUseInSubClsInterface2=dongshi.daddy.adaptive.useinsubcls.ConcreteUseInSubClsInterface2
2.3:测试
public class AdaptiveInSubClsTest {
@Test
public void testGetExtensionLoader() {
UseInSubClsInterface useInSubClsInterface
= ExtensionLoader
.getExtensionLoader(UseInSubClsInterface.class)
// 获取Aaptive的子类
.getAdaptiveExtension();
useInSubClsInterface.sayHi("good night,mongo need to drink milk!!!");
}
}
多次运行可以看到会随机调用,多次运行输出如下:
ConcreteUseInSubClsInterface2 say hi: good night,mongo need to drink milk!!!
ConcreteUseInSubClsInterface1 say hi: good night,mongo need to drink milk!!!
写在后面
参考文章列表: