Spring Cloud Alibaba 教程 | Dubbo(四):获取扩展类

获取自适应扩展实例

通过调用ExtensionLoader类的getAdaptiveExtension()方法可以获取到自适应扩展类实例对象。

public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
    	......//省略部分代码
	    instance = createAdaptiveExtension();
        cachedAdaptiveInstance.set(instance);
    }
    return (T) instance;
}

前面的文章已经介绍过ExtensionLoader的私有缓存,其中cachedAdaptiveInstance就是用来缓存自适应扩展类实例的。
若缓存没有则执行createAdaptiveExtension()方法。

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

创建自适应扩展类实例通过getAdaptiveExtensionClass()获取到Class去newInstance()创建实例对象,接着执行injectExtension(T instance)方法,该方法实现了Dubbo SPI的IOC特性,我们稍后解析。

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

获取Class是通过调用getExtensionClasses(),获取到之后会将其缓存到cachedAdaptiveClass,从代码看到getExtensionClasses()是没有返回值的,不过在该方法的内部执行过程中会给cachedAdaptiveClass赋值。

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
     classes = loadExtensionClasses();
        cachedClasses.set(classes);
    }
    return classes;
}

getExtensionClasses()方法只要是处理普通扩展类的加载和缓存,并没有直接处理自适应扩展类,在执行loadExtensionClasses()加载扩展类的时候会将扩展配置文件中的所有类型的扩展实现类都加载出来,包括普通扩展类、自适应扩展类和包装扩展类。

private Map<String, Class<?>> loadExtensionClasses() {
	......//代码省略
   	Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadDirectory(extensionClasses, DUBBO_DIRECTORY);
    loadDirectory(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

加载所有的扩展实现类是通过加载DUBBO_INTERNAL_DIRECTORY目录下的扩展配置文件,该目录值就是META-INF/dubbo/internal/,另外两个目录在Dubbo框架中没有用上。
加载的过程会顺序执行下面三个方法:

  • loadDirectory()
  • loadResource()
  • loadClass()
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            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();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

loadDirectory()方法首先通过dir + type.getName()组成filename,然后通过ClassLoader获取到资源文件,获取到资源文件后执行loadResource()方法加载这些资源文件。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                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;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                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);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

加载的过程就是将扩展配置一行行读取出来,读取之后赋值给line,最后按照“=”分割成name和line。例如读取到配置
printService1=com.alibaba.dubbo.demo.spi.impl.PrintServiceImpl1后name就是printService1,line就是com.alibaba.dubbo.demo.spi.impl.PrintServiceImpl1。
每读取到一行扩展配置,就执行一次loadClass()方法。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
	......//省略部分代码
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        }
    } else if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        for (String n : names) {
            if (!cachedNames.containsKey(clazz)) {
                cachedNames.put(clazz, n);
            }
            Class<?> c = extensionClasses.get(n);
            if (c == null) {
                extensionClasses.put(n, clazz);
            }
        }
   }
}

loadClass()方法代码很多,我提取了最重要的代码,如果Clazz是一个包含@Adaptive注解的类就赋值给cachedAdaptiveClass,即记载了自适应配置类。如果isWrapperClass(clazz)是true,则该Clazz是一个包装类,检测方法就是看该扩展类是否存在一个参数为该扩展接口类型的构造方法,有就是一个包装扩展类,包装扩展类的Class缓存在cachedWrapperClasses。最后就是处理普通扩展类,分别赋值给cachedNames和extensionClasses。

这样整个扩展配置就加载完成了,loadExtensionClasses()方法负责了加载指定type类型所有的扩展配置。

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

回到开始的createAdaptiveExtension()方法,通过getAdaptiveExtensionClass().newInstance()创建出了自适应扩展配置实例,紧接着调用injectExtension()方法,该方法注入该实例依赖的其他扩展类。

private T injectExtension(T instance) {
	 try {
	     if (objectFactory != null) {
	         for (Method method : instance.getClass().getMethods()) {
	             if (method.getName().startsWith("set")
	                     && method.getParameterTypes().length == 1
	                     && Modifier.isPublic(method.getModifiers())) {
	                 Class<?> pt = method.getParameterTypes()[0];
	                 try {
	                     String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
	                     Object object = objectFactory.getExtension(pt, property);
	                     if (object != null) {
	                         method.invoke(instance, object);
	                     }
	                 } catch (Exception e) {
	                     logger.error("fail to inject via method " + method.getName()
	                             + " of interface " + type.getName() + ": " + e.getMessage(), e);
	                 }
	             }
	         }
	     }
	 } catch (Exception e) {
	     logger.error(e.getMessage(), e);
	 }
	 return instance;
}

该方法先获取该扩展类里面所有以set开头只有一个参数且是public的方法,接着通过执行objectFactory.getExtension(Class type, String name)获取到普通扩展类实例,最后执行method.invoke(instance, object)注入该实例。关于objectFactory.getExtension(Class type, String name)内容下面会详细分析。

获取动态自适应扩展实例

当一个扩展接口的方法包含了@Adaptive注解时,可以通过参数URL动态指定使用哪个扩展配置类。

修改之前的扩展接口PrintService,在方法printInfo()添加@Adaptive注解

@SPI("printService1")
public interface PrintService {
    @Adaptive
    public void printInfo(URL url,String message);
}

修改两个扩展实现类PrintServiceImpl1和PrintServiceImpl2

public class PrintServiceImpl1 implements PrintService {
    public PrintServiceImpl1() {
        System.out.println("PrintServiceImpl1 construct");
    }
    @Override
    public void printInfo(URL url, String message) {
        System.out.println("PrintServiceImpl1 message:"+message);
    }
}
public class PrintServiceImpl2 implements PrintService {
    public PrintServiceImpl2() {
        System.out.println("PrintServiceImpl2 construct");
    }
    @Override
    public void printInfo(URL url, String message) {
        System.out.println("PrintServiceImpl2 message:"+message);
    }
}

修改测试代码,通过URL参数控制选择的扩展实现类:map.put(“print.service”,“printService1”)

public class Test {
    public static void main(String[] args) {
        PrintService printService =
                ExtensionLoader.getExtensionLoader(PrintService.class)
                        .getAdaptiveExtension();
        Map<String, String> map = new HashMap<String, String>();
        map.put("print.service","printService1");//通过URL指定使用的扩展实现类
        URL url = new URL("protocol", "1.2.3.4", 12345, "path", map);
        printService.printInfo(url,"hello dubbo");
    }
}

断点执行该代码,你会发现获取到的PrintService实例类型是PrintService$Adaptive,而这个类我们并没有创建,是Dubbo框架自己在运行时动态创建编译的。

跟踪代码发现该类是通过createAdaptiveExtensionClass()方法生成的

    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

因为此时cachedAdaptiveClass值是null(PrintService没有自适应扩展类,如果有那么URL将不起作用),所以会执行到createAdaptiveExtensionClass()方法

private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClassCode()负责创建PrintService$Adaptive代码,赋值给code,将code代码拷贝出来即可得到该类源代码:

package com.alibaba.dubbo.demo.spi;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class PrintService$Adaptive implements com.alibaba.dubbo.demo.spi.PrintService {

	public void printInfo(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
		if (arg0 == null) throw new IllegalArgumentException("url == null");
		com.alibaba.dubbo.common.URL url = arg0;	
		String extName = url.getParameter("print.service", "printService1");
		if(extName == null) 
			throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.demo.spi.PrintService) name from url(" + url.toString() + ") use keys([print.service])");
			
			com.alibaba.dubbo.demo.spi.PrintService extension 
				= (com.alibaba.dubbo.demo.spi.PrintService)ExtensionLoader
					.getExtensionLoader(com.alibaba.dubbo.demo.spi.PrintService.class).getExtension(extName);
			extension.printInfo(arg0, arg1);
		}
	}
}

可以看到通过URL参数获取到extName,接着通过ExtensionLoader获取到对应的普通扩展类实例,通过该实例执行真正的接口实现方法extension.printInfo(arg0, arg1)。

回到刚才的createAdaptiveExtensionClass()方法,赋值给code之后,紧接着就是通过ExtensionLoader获取Compiler实例,通过它执行代码编译工作。
Compiler也是一个扩展接口,有两个扩展实现类JavassistCompiler和JdkCompiler,默认使用JavassistCompiler,编译的实现过程这里就不展开讨论了,感兴趣的读者可以自己去了解。

最终执行结果:

PrintServiceImpl1 construct
PrintServiceImpl1 message:hello dubbo

通过URL的map参数动态指定扩展实现类时,设置map.put(key,value)参数值时,key的值为扩展接口名去掉驼峰,并在除开头首字母以外原来驼峰名称位置间加入一个点号“.”分隔,例如扩展接口是AaaBbbCcc,那么map的key就应该是aaa.bb.ccc,value就是目标扩展配置类名称。

获取普通扩展实例

public T getExtension(String name) {
   ......//省略部分代码
   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) {
        instance = createExtension(name);
        holder.set(instance);
   }
   return (T) instance;
}

通过执行getExtension()方法传入指定的name可以获取到普通扩展实例,普通扩展实例缓存在cachedInstances,通过Holder包装添加getter和setter方法,如果缓存没有该实例,则调用createExtension(name)创建扩展实例。

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name);
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
        EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
        instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
}

该方法首先执行getExtensionClasses()获取所有的普通扩展Class,该方法在上面已经解析过,获取到Class之后创建实例并缓存到EXTENSION_INSTANCES,接着执行injectExtension(instance)注入依赖的其他扩展类。
如果该扩展接口包含了包装扩展类,那么将循环所有的包装类,依次创建包装扩展类实例(将instance 作为构造参数传入),创建后的实例又赋值给instance,这样最后返回的就是该扩展接口的最后一个包装扩展类实例,这实际上是通过使用装饰器设计模式,让Dubbo SPI达到AOP控制特性。
当然每个包装扩展类实例同样需要通过injectExtension()注入依赖扩展。

ExtensionFactory

在injectExtension()方法,我们是通过执行Object object = objectFactory.getExtension(pt, property)获取到指定的扩展配置实例的。ExtensionFactory是一个扩展接口

@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

每一个扩展接口(除了ExtensionFactory本身)在加载其对应的ExtensionLoader实例时都拥有一个ExtensionFactory实例对象,它真正目标实例是AdaptiveExtensionFactory。

ExtensionFactory拥有三个实现类:

  • SpiExtensionFactory
  • SpringExtensionFactory
  • AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

AdaptiveExtensionFactory负责管理 SpiExtensionFactory和SpringExtensionFactory,
在构造方法中获取到ExtensionLoader实例后通过调用loader.getSupportedExtensions()获取到所有的的普通扩展类class,最后执行list.add(loader.getExtension(name))获取到SpiExtensionFactory和SpringExtensionFactory,并存储到list。

所以执行getExtension(Class type, String name),实际上是通过SpiExtensionFactory或者SpringExtensionFactory获取扩展实例,优先从SpiExtensionFactory获取。

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

SpiExtensionFactory实现比较简单,不检测name参数,直接通过调用扩展接口对应的ExtensionLoader实例获取自适应扩展类实例。

public class SpringExtensionFactory implements ExtensionFactory {

    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        contexts.remove(context);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

}

SpringExtensionFactory通过注入ApplicationContext应用上下文,从spring容器中获取对应的实例。关于何时注入ApplicationContext的问题后面会介绍到。

关注公众号了解更多原创博文

Alt

发布了122 篇原创文章 · 获赞 127 · 访问量 93万+

猜你喜欢

转载自blog.csdn.net/u010739551/article/details/104211007