SPI核心机制主要依赖于ExtensionLoader类,在3.0版本中获取扩展点的方式跟2版本是有区别的,已经废弃了原本的方式,而是采用以下方式
ExtensionLoader<Protocol> extensionLoader =
ApplicationModel.defaultModel().getExtensionLoader(Protocol.class);
Protocol http = extensionLoader.getExtension("http");
http.sendRequest("http hello");
ExtensionLoader的核心功能点:
- 扩展点的加载
- 扩展点的依赖注入
- 扩展点的自适应
- 以及扩展点的激活
1.extensionLoader.getExtension()方法流程
public T getExtension(String name) {
//调用重载方法,跟进去
T extension = getExtension(name, true);
if (extension == null) {
throw new IllegalArgumentException("Not find extension: " + name);
}
return extension;
}
public T getExtension(String name, boolean wrap) {
final Holder<Object> holder = getOrCreateHolder(cacheKey);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//双重锁创建对象
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name, boolean wrap) {
//获取类对象
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
//先从实例map中取一下看是否存在实例
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
//创建实例并放入map中
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
//再从实例map中获取
instance = (T) extensionInstances.get(clazz);
//前置增强
instance = postProcessBeforeInitialization(instance, name);
//判断是否需要setter注入
injectExtension(instance);
//后置增强
instance = postProcessAfterInitialization(instance, name);
}
//是否存在包装
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
boolean match = (wrapper == null) ||
((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
!ArrayUtils.contains(wrapper.mismatches(), name));
if (match) {
//装饰增强
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
//执行后置通知
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
2.Dubbo 依赖注入
依赖注入,实现类依赖的属性自动注入,类似spring的依赖注入。如果字段不想不注入,可以在set方法上使用DisableInject注解。被依赖注入的属性类的方法必须有@Adaptive注解,即这个类是一个自适应SPI。
3.扩展点原理
案例代码如下
@SPI
public interface Animal {
@Adaptive
String eat(URL url);
}
@Adaptive
public class Cat implements Animal{
@Override
public String eat(URL url) {
return "Cat eat";
}
}
public class Dog implements Animal{
@Override
public String eat(URL url) {
return "Dog eat";
}
}
public class Pig implements Animal{
@Override
public String eat(URL url) {
return "Pig eat";
}
}
public class Demo {
public static void main(String[] args) {
ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal animal = extensionLoader.getAdaptiveExtension();
String cat = animal.eat(URL.valueOf("http://localhost?aa=pig"));
System.out.println(cat);
}
}
输出结果为Cat eat
一个SPI下扩展点只能有一个,原理代码如下
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException();
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
//缓存扩展点
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
} else {
}
}
private void cacheAdaptiveClass(Class<?> clazz) {
//只能有一个扩展点
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getName()
+ ", " + clazz.getName());
}
}
注释掉Cat的@Adaptive,默认的实现code如下:
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Animal\$Adaptive implements com.doukill.dubbo.adaptive.Animal {
public java.lang.String eat(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("animal", "pig");
if(extName == null) throw new IllegalStateException("Failed to get extension (com.doukill.dubbo.adaptive.Animal) name from url (" + url.toString() + ") use keys(\[animal])");
com.doukill.dubbo.adaptive.Animal extension = (com.doukill.dubbo.adaptive.Animal)ExtensionLoader.getExtensionLoader(com.doukill.dubbo.adaptive.Animal.class).getExtension(extName);
return extension.eat(arg0);
}
}
```
类URL中代码:
public String getParameter(String key) {
String value = parameters.get(key);
return StringUtils.isEmpty(value) ? parameters.get(DEFAULT_KEY_PREFIX + key) : value;
}
public String getParameter(String key, String defaultValue) {
String value = getParameter(key);
return StringUtils.isEmpty(value) ? defaultValue : value;
}
ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal animal = extensionLoader.getAdaptiveExtension();
String cat = animal.eat(URL.valueOf("http://localhost?animal=cat"));
System.out.println(cat);
String pig = animal.eat(URL.valueOf("http://localhost?animal=pig"));
System.out.println(pig);
String dog = animal.eat(URL.valueOf("http://localhost?animal=dog"));
System.out.println(dog);
//报错 没有默认值,除非在SPI注解上加一个默认值
String def = animal.eat(URL.valueOf("http://localhost"));
System.out.println(def);
```
作用其实比较简单,就是想动态地指定 URL 中的参数,来动态切换实现类去执行业务逻辑,把一堆根据参数获取实现类的重复代码,全部封装到了代理类中,以达到充分灵活扩展的效果。
@Adaptive 注解其实是生成了一个自适应的代理类,每个 SPI 接口都有且仅有一个自适应扩展点。
然后跟踪了加载 SPI 资源文件的 loadDirectory 方法的源码,发现 @Adaptive 注解不仅可以
写在 SPI 接口的方法上,还可以写在 SPI 接口实现类上,并且在使用自适应扩展的时候,若实
现类有 @Adaptive 注解,则优先使用该实现类作为自适应扩展点。
要实现SPI的setter注入,必须要有@Adaptive注解,源码如下:
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
//这里必须要有注解方法
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
return code.toString();
}
public class Bb$Adaptive implements com.doukill.dubbo.demo.Bb {
public java.lang.String eat(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("bb");
if (extName == null)
throw new IllegalStateException("Failed to get extension (com.doukill.dubbo.demo.Bb) " +
"name from url (" + url.toString() + ") use keys([bb])");
com.doukill.dubbo.demo.Bb extension = (com.doukill.dubbo.demo.Bb)
ExtensionLoader.getExtensionLoader(com.doukill.dubbo.demo.Bb.class).getExtension(extName);
return extension.eat(arg0);
}
}