什么是SPI
SPI全称Service Provider Interface,在面向的对象的设计里,不同模块之间推崇面向接口编程,不建议在模块中对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。SPI使得程序能在ClassPath路径下的META-INF/services文件夹查找接口的实现类,自动加载文件里所定义的实现类。
使用场景
-
数据库驱动java.sql.Driver,Mysql,Oracle都有自己的驱动实现jar包,只需引入不同的jar包即可加载不同的数据库驱动,例如mysql-connector-java中,其META-INF/services下制定了mysql驱动的实现类
-
Dubbo中针对Java SPI做了一层封装,Dubbo SPI更加灵活、高效。如Dubbo SPI使用在根据URL中指定的协议(dubbo、thrift、redis)来动态指定协议,根据URL中指定loadBalance算法来指定特定的负载均衡实现
下面以实例来说明JAVA SPI使用及Dubbo SPI原理
Java SPI
假设定义一个接口ISpiDemoService
如下
public interface ISpiDemoService {
void sayHello();
}
Demo
定义两个实现类
JavaSpiImp1
public class JavaSpiImp1 implements ISpiDemoService {
@Override
public void sayHello() {
System.out.println("java spi implemention 111");
}
}
JavaSpiImp2
public class JavaSpiImp2 implements ISpiDemoService {
@Override
public void sayHello() {
System.out.println("java spi implemention 222");
}
}
在META-INF/services下新建文件(名称为接口全限定名),文件内容为接口实现类全限定名
调用java.util.ServiceLoader#load(java.lang.Class)加载实现类,并测试输出
缺点
默认加载了所有实现类(然后通过迭代器访问provider实现类),实际上程序了不一定能用到所有的实现,也不能方便的获取某一个实现类。如果能实现按需加载能减少一定程度的资源浪费
Dubbo SPI
Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类
Demo
ISpiDemoService接口上加上@SPI
注解
配置文件中加入key-value格式的配置,key自定义,value为实现类的全路径
通过ExtensionLoader.getExtensionLoader(ISpiDemoService.class)得到特定接口的类加载器,再通过extensionLoader.getExtension(key)获取特定的实现类
Dubbo SPI优点
- 能够实现按需加载,JDK SPI仅仅通过接口类名获取所有实现,在通过迭代器获取指定实现,而ExtensionLoader则通过接口类名和key值获取一个实现
- 支持AOP(将实现类包装在Wrapper中,Wrapper中实现公共增强逻辑)
- 支持IOC(能够通过set方法注入其他扩展点)
按需加载,上面的Demo例子里已经体现过了,下面针对AOP、IOC特性分析Dubbo是如何实现的,先了解下Dubbo中类加载的过程
ExtensionLoader.getExtension
首先从ExtensionLoader.getExtension
入手看下实现类的加载过程,dubbo中会对SPI中加载的Class及Class的实例进行缓存,其中cachedInstances就是实例的缓存
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//先尝试从缓存中获取实现类实例
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
//双重检查锁定
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//调用createExtension创建拓展对象
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
逻辑比较简单、缓存里找不到对应拓展实现类,就调用createExtension创建一个,下面进入到了createExtension方法
//缓存了 Class及对应的实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
private T createExtension(String name) {
//第一步先获取拓展类的Class对象
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//尝试从缓存中获取
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//反射创建Class对应实例instance
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//向实例instance中注入其依赖的对象
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//遍历wrapper,向wrapper包装类中注入依赖的对象(将实例本身注入到包装类中)
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
看到这里基本扩展类的加载流程就清晰了,分为几个关键步骤
- getExtensionClasses()先从resource目录下读取指定位置的配置文件中的拓展类的定义,转化为Class,代码如下:
同样的,针对Class也做了缓存,先从缓存中获取Class。loadDirectory
方法从指定位置"META-INF/services/",
“META-INF/dubbo/”,"META-INF/dubbo/internal/"中加载拓展类配置
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;
}
/**
* synchronized in getExtensionClasses
* */
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;
}
- 获取到实现类Class后,判断缓存中是否有Class对应的实例,如果没有,则反射生成一个实例
- 调用injectExtension注入依赖
- 往Wrapper中注入依赖
injectExtension
方法是实现IOC依赖注入的关键,往Wrapper中注入依赖,通过Wrapper包装实例,从而在Wrapper的方法中进行方法增强是实现AOP的关键,下面分别分析
IOC
看下injectExtension的实现,其核心思想是遍历其Set方法,从Set方法中取出属性的变量名,从ExtensionFactory中获取依赖对象的实例,再反射调用set方法注入依赖
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
//遍历所有方法
for (Method method : instance.getClass().getMethods()) {
//不是set方法 continue
if (!isSetter(method)) {
continue;
}
//如果方法包含DisableInject注解,不进行注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
//判断set方法中的类型参数是否是原始类型,如果是原始类型,不进行注入
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//获取注入的属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
//从objectFactory获取依赖的对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//反射调用set方法进行注入
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;
}
AOP
实现AOP的关键是ExtensionLoader#createExtension
实际返回的是Wrapper类(存在对应Wrapper类的情况下)
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//遍历Wrapper类型的Class
for (Class<?> wrapperClass : wrapperClasses) {
//首先反射调用Wrapper的构造方法,创建一个Wrapper类,往Wrapper类中注入依赖
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
以ProtocolListenerWrapper
为例,其内部包含Protocol实例,其export,refer方法最终调用的还是Protocol的export,refer方法,但在ProtocolListenerWrapper的export,refer中增强了Protocol的export,refer方法,做了一些额外的判定、封装处理
最后附上本文中的Demo地址,有兴趣的可以跑一下
https://github.com/hosaos/JavaLearningDemo