Dubbo SPI的使用
ExtensionLoader<Person> extensionLoader = ExtensionLoader.getExtensionLoader(Person.class);
Person person = extensionLoader.getExtension("black");
通过ExtensionLoader得到Person类的扩展点,然后通过getExtension方法,可以得到这个接口扩展点下面的子类,并可以使用这个类的方法,看过 Java SPI的应该都了解,那么Dubbo里的代码是怎么写的呢?
ExtensionLoader
看看getExtensionLoader方法的代码
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
//一定要有SPI的注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//一个type对应一个loader类,先从缓存中查看没有的话new个新的
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//每一个接口type对应的扩展点加载器
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
可以看到参数的属性必须要是接口,而且要有SPI的注解。首先会在一个EXTENSION_LOADERS的ConcurrentMap缓存里看里面有没有这个接口的扩展点加载器,
没有的话就会调用这个map里的putIfAbsent,生成一个ExtensionLoader,并放进map里
查看ExtensionLoader的构造方法
private ExtensionLoader(Class<?> type) {
//表示扩展点加载器所要加载的接口的类型
this.type = type;
// objectFactory表示当前ExtensionLoader内部的一个对象工厂,可以用来获取对象 得到ExtensionFactory接口的adaptive实例 AdaptiveExtensionFactory实例,利用AdaptiveExtensionFactory实例来获取某个类
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
这个objectFactory,我认为就是一个工厂类,是用来获取对象的。这个其实也是用ExtensionLoader来获得ExtensionFactory的扩展点再调用getadAdaptiveExtension()方法来得到的。
这个是用来得到带有Adaptive注解的接口实现类,可以看到AdaptiveExtensionFactory类,上游Adaptive注解,而它的getExtension方法,也就是获取对象的方法的代码是
public <T> T getExtension(Class<T> type, String name) {
// 遍历两个ExtensionFactory,从ExtensionFactory中得到实例,只要从某个ExtensionFactory中获取到对象实例就可以了
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name); // SpringExtensionFactory,, SpiExtensionFactory
if (extension != null) {
return extension;
}
}
return null;
}
可以看到是分别从factories集合里得到的ExtensionFactory进行getExtension方法,获得对象。其实是调用SpiExtensionFactory和SpringExtensionFactory的getExtension方法
SpringExtensionFactory的getExtension方法
public <T> T getExtension(Class<T> type, String name) {
//SPI should be get from SpiExtensionFactory
// 如果接口上存在SPI注解,就不从spring中获取对象实例了
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
// 从ApplicationContext中获取bean, byname
for (ApplicationContext context : CONTEXTS) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
if (Object.class == type) {
return null;
}
// byType
for (ApplicationContext context : CONTEXTS) {
try {
return context.getBean(type);
} catch (NoUniqueBeanDefinitionException multiBeanExe) {
logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
} catch (NoSuchBeanDefinitionException noBeanExe) {
if (logger.isDebugEnabled()) {
logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
}
}
}
logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
return null;
}
可以看到是通过spring get bean的方式来获取根据name或type来获取。也可以得知,如果和spring整合,Dubbo中的一些对象都会弄成bean放进spring里。
看SpiExtensionFactory的getExtension方法
public <T> T getExtension(Class<T> type, String name) {
// 接口上存在SPI注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension(); // 接口的Adaptive类(代理对象)
}
}
return null;
}
这些后面再看
接着初始化完ExtensionLoader后
会通过这个扩展点加载器的getExtension方法来得到类对象
public T getExtension(String name) {
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();
// 如果有两个线程同时来获取同一个name的扩展点对象,那只会有一个线程会进行创建
if (instance == null) {
synchronized (holder) { // 一个name对应一把锁
instance = holder.get();
if (instance == null) {
// 创建扩展点实例对象
instance = createExtension(name); // 创建扩展点对象
holder.set(instance);
}
}
}
return (T) instance;
}
再看createExtension方法
private T createExtension(String name) {
// 获取扩展类 {name: Class} key-Value 接口的所有实现类
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);
}
// 依赖注入 IOC
injectExtension(instance);
// AOP,cachedWrapperClasses无序
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//生成一个Wrapper实例(传入了instance),然后进行依赖注入
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));// new xxwrapper(实例)
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
第一步获取扩展类的方法getExtensionClasses()
private Map<String, Class<?>> getExtensionClasses() {
// cachedClasses是一个Holder对象,持有的就是一个Map<String, Class<?>>
// 为什么要多此一举,也是为了解决并发,Holder对象用来作为锁
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses(); // 加载、解析文件 Map
cachedClasses.set(classes);
}
}
}
return classes;
}
逻辑在loadExtensionClasses()里
private Map<String, Class<?>> loadExtensionClasses() {
// cache接口默认的扩展类
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//加载解析配置文件中的对应类的所有类
//都会对这个map里面put值
// 类似org.apache.dubbo.rpc.Protocol "META-INF/dubbo/internal/"
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
//把加载解析的配置文件从"org.apache."替换成了"com.alibaba."
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//配置文件路径不同
//"META-INF/dubbo/"
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//"META-INF/services/
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
重点是下面的loadDirectory。别看它有这么多loadDirectory怪吓人的,但仔细看就可以看到其实只是后面的一个参数不同而已,都是同一个方法,就是加载文件的路径不同
//配置文件位置1,沿用java的目录
private static final String SERVICES_DIRECTORY = "META-INF/services/";
//配置文件位置
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
//配置文件位置2,dubbo自定义的目录
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
可以对应着看看文件是在哪配置的。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
// 根据文件中的内容得到urls, 每个url表示一个扩展 http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 遍历url进行加载,把扩展类添加到extensionClasses中
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
上面通过加载文件,可以得到一个urls集合,我随便找一个,就是这种,一行对应着一个url
adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler
而loadResource就是加载url
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
//判断有没有注释
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
//key=value左边和右边
int i = line.indexOf('=');
if (i > 0) {
//key
name = line.substring(0, i).trim();
//value
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加载类,并添加到extensionClasses中
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
可以看到,在这个方法里面,会判断这一行有没有#号,有的话就是注释就不读了,然后它判断有没有等号,也就是左边和右边,分别是name和我们的类,然后分别传进这个loadClass方法里,并且还用Class.forName反射得到了这个类。
而我们最后要得到的就是这个extensionClasses这个map,所以这时候我想的是这方法里面是把这个类和name,put到这个map里去,看看代码是不是呢
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//查看这个类是不是这个接口的实现类
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 当前接口手动指定了Adaptive类
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
// 是一个Wrapper类
//
//是Wrapper类,就缓存进一个set里
cacheWrapperClass(clazz);
} else {
// 需要有无参的构造方法
clazz.getConstructor();
// 在文件中没有name,但是在类上指定了Extension的注解上指定了name
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 缓存一下被Activate注解了的类
cacheActivateClass(clazz, names[0]);
// 有多个名字
for (String n : names) {
// clazz: name
cacheName(clazz, n);
// name: clazz
//把类以及name存进map
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
其实没有这么简单,这代码挺长的。它会判断这个类有没有带Adaptive注解,是不是wrapper类。因为我们传进来的这个name,它不是一个接口,它是我们需要通过接口的扩展点获取的类。判断是不是wrapper就看它能不能拿到type参数对应的构造方法。如果是Adaptive类或者是个wrapper类,就会放进缓存里。如果不是这些就都会放进这个map里。
然后回到createExtension方法,得到这个map后,通过get(name),我们可以得到这个扩展类。而Dubbo再得到扩展类后,还进行了很多操作
injectExtension(instance);
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
// 利用set方法注入
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
//看注解有没有加禁止依赖注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// set方法中的参数类型
Class<?> pt = method.getParameterTypes()[0]; // Person接口
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 得到setXxx中的xxx
String property = getSetterProperty(method); // person
// 根据参数类型或属性名,从objectFactory中获取到对象,然后调用set方法进行注入
// AdaptiveExtensionFactory
Object object = objectFactory.getExtension(pt, property); // User.class, user
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;
}
这里可以看到,通过遍历instance类的各个方法,判断这个方法是不是setxxx,来进行依赖注入,而怎么依赖注入的呢,就是用到了之前提到的objectFactory,之前提到,是可以通过SPI,或者spring,他们的getExtension方法,其实SPI就是和咋们这个方式差不多,通过配置文件去找,Spring就是在IOC容器里,找bean。
然后会遍历这个cacheWrapperClasses,也就是遍历这个接口的所有wrapper类。把实例对象传进wrapper里去,通过依赖注入。
public class Wrapper<T> {
private T data;
Wrapper(T data) {
this.data = data;
}
Object getData() {
return data;
}
}
随便在Dubbo找一个Wrapper类的定义,可以看到它里面是一个带有构造方法的,所以这里是通过依赖注入,将对象注入进这个wrapper对象里。