SPI全称为Service Provider Interface,是一种服务提供机制,相对于接口调用而言,可以让调用方知道服务提供方具体提供的是什么服务。
jdk与dubbo SPI区别
- 存储路径
jdk主要是在项目的META-INF/services
目录下以接口的全路径名为文件名,然后在该文件中声明该接口的具体实现类有哪些.
dubbo 主要包含META-INF/dubbo/external/
、META-INF/dubbo/internal/
、META-INF/dubbo/
、META-INF/services/
四种文件路径全路径名为文件夹,然后在该文件中声明该接口的具体实现类有哪些. - 接口提供服务数量
jdk的SPI默认会将所有目标文件中定义的所有子类都读取到返回使用,所以尽量只有每个接口提供一个服务。
dubbo对此没有限制,通过多种方式获取对应的服务。 - 接口提供服务
当提供多个服务时,jdk的SPI无法动态的根据配置来使用不同的配置,而dubbo可以。
dubbo的SPI实现
dubbo对于SPI的实现主要是在ExtensionLoader
这个类中,这个类主要有三个方法:
public T getExtension(String name);
public T getAdaptiveExtension();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type);
1.getExtension(String name)
主要用于获取名称为name的对应的子类的对象
public T getExtension(String name) {
return getExtension(name, true);
}
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
//缓存中获取拓展类
final Holder<Object> holder = getOrCreateHolder(name);
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;
}
第一段代码可以看出,如果传入的name为true,则获取默认配置的扩展实现,
反之,当缓存不存在时,则进行初始化创建,这里都会调用方法createExtension(String name, boolean wrap)
public T getDefaultExtension() {
getExtensionClasses();
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
......
}
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
由上面代码可知,首先会初始化默认扩展,具体实现是@SPI
注解中是否有value
参数,value
参数即为默认扩展,然后,会加载对应文件路径下的所有配置放入map中,这里有个知识点,就是通过JDK自带的SPI扩展的方式,获取到四种全部文件路径
而且上述 代码可知,如果刚开始入参为true
时,会设置默认扩展的name
,再将该name
传入getExtension(String name);
回来继续看getExtension(String name);
传入参数不为true时的情况
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
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);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
由上述代码可知,获取到对应的实例之后,会对扩展进行注入,这里主要是为了做到一个实现类里需要依赖另一个 @SPI 接口的实例
同时做了以下事情:
- 先根据name来得到对应的扩展类。从ClassPath下META-INF文件夹下读取扩展点配置文件。
- 使用反射创建一个扩展类的实例
- 对扩展类实例的属性进行依赖注入,即IOC。
- 如果有wrapper,添加wrapper。即AOP。
Dubbo SPI高级用法之自动装配
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
//如果有一个方法以set开头,入参长度为1
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//拿到入参方法set后面的字符串,首字母小写
String property = getSetterProperty(method);
//先拿property的Extension
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//依赖注入
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
由源码可知,针对方法以set开头,参数只有一个,且没有加入DisableInject注解时,进行依赖注入。
这里会发现有objectFactory.getExtension(pt, property)
,此处的objectFactory
创建ExtensionLoader
时被初始化,并获得自适应扩展。
在这里,我们可以知道,dubbo会为每一个扩展创建一个自适应实例。如果扩展类上有@Adaptive
,会使用该类作为自适应类。如果没有,Dubbo会为我们创建一个,即AdaptiveExtensionLoader
会遍历所有的ExtensionFactory
实现,尝试着去加载扩展。如果找到了,返回。如果没有,在下一个ExtensionFactory
中继续找。
同时,在createExtension
方法中,我们看到了wrapper类。
其实就是典型的装饰者模式,当类中包含根据接口类为入参的构造函数时,我们可以称之为wrapper类,典型的如ProtocolFilterWrapper
、ProtocolListenerWrapper
等。
在加载扩展配置loadDirectory
时,会根据加载的类,判断为wrapper时,加入到缓存cachedWrapperClasses
中。
针对wrapper的接口类再进行依赖注入
2.getAdaptiveExtension()
前面讲到过,Dubbo需要在运行时根据方法参数来决定该使用哪个扩展,所以有了扩展点自适应实例。其实是一个扩展点的代理,将扩展的选择从Dubbo启动时,延迟到RPC调用时。Dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
// 获取目标extensionClasses,如果无法获取到,则在定义文件中进行加载
getExtensionClasses();
// 如果目标类型有使用@Adaptive标注的子类型,则直接使用该子类作为装饰类
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果目标类型没有使用@Adaptive标注的子类型,则尝试在目标接口中查找是否有使用@Adaptive标注的
// 方法,如果有,则为该方法动态生成子类装饰代码
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
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);
}
从Compiler的代码,默认实现是javassist。
JavassistCompiler
会使用一个StringBuilder来构建自适应类的Java源码。
先生成Java源代码,然后编译,加载到jvm中。通过这种方式,可以更好的控制生成的Java类。而且这样也不用care各个字节码生成框架的api等。
这个的确很有意思,也很奇妙,感兴趣的可以去源码详细看一下。
有几个点需要注意的是,在生成代码的时候,AdaptiveClassCodeGenerator
会对包含Adaptive
注解的方法加入具体实现,否则,直接抛错UnsupportedOperationException
3.getExtensionLoader()
这个方法属于核心方法,但是内容相对简单,主要作用是为当前接口类型实例化一个ExtensionLoader对象,然后将其缓存起来
网友评论