前一篇文章总结了一下JDK的SPI机制,dubbo基于SPI基础上扩展了一套自己的SPI机制,通过ExtensionLoader类实现。第一次看ExtensionLoader源码,一脸懵逼,debug几次还是云里雾里。后来通过网上查资料,看到了Cooma,而Cooma的来源就是dubbo项目中的SPI。Cooma是一个独立的Java微容器,所以打算先从它讲起,后面的文章再分析dubbo框架中是如何运用ExtensionLoader的。
Cooma,官方介绍的已经非常详细了,这里大体说一下几个特点:
1)以插件方式加载扩展(SPI)
2)支持依赖扩展点的自动加载(IOC)
3 可以有扩展点Wrapper,为扩展写公共Filter代码(AOP)
下面通过分析Cooma源码来了解Cooma是这几大特点,Cooma的代码主要两个类Extension(把一个接口标识成扩展点),ExtensionLoader(加载和管理扩展)。
Extension:是一个注解,通过Extension注解将一个接口标识成扩展点。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Extension {
/**
* the default extension name.
*
* @since 0.1.0
*/
String value() default "";
}
ExtensionLoader:ExtensionLoader类应该说是Cooma的核心,通过它实现扩展点(SPI)的加载,扩展点(IOC)的依赖注入,扩展点(AOP)的包装。
属性:
加载相关属性:
//扩展点的加载相对路径(对应JDk SPI的扩展点加载路径"META-INF/service/")
private static final String EXTENSION_CONF_DIRECTORY = "META-INF/extensions/";
//适配类key标识(对应dubbo中的Adaptive注解)
private static final String PREFIX_ADAPTIVE_CLASS = "*";
//包装类key标识(对应dubbo中的cachedWrapperClasses)
//private static final String PREFIX_WRAPPER_CLASS = "+";
//名字分隔
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*,+\\s*");
//名字校验
private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
//类型与ExtensionLoader的映射,通过类型取对应的ExtensionLoader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
扩展点相关属性
private final Class<T> type;
//默认扩展点
private final String defaultExtension;
private final Holder<Map<String, Class<?>>> extClassesHolder = new Holder<Map<String, Class<?>>>();
private volatile Map<String, Map<String, String>> name2Attributes;
private final ConcurrentMap<Class<?>, String> extClass2Name = new ConcurrentHashMap<Class<?>, String>();
//适配类,适配类唯一
private volatile Class<?> adaptiveClass = null;
//包装类映射关系
private volatile Map<String, Class<? extends T>> name2Wrapper;
方法:
//私有构造函数
private ExtensionLoader(Class<T> type)
设置ExtensionLoader的type属性,type属性用于获取type对应的所有扩展点,以及默认扩展点属性defaultExtension。
//获取类对应的ExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
因为ExtensionLoader是私有的,所以只能通过该方法获取类对应的ExtensionLoader,该方法首先判断type是否为null,若为null则抛出参数异常,是否是接口,若不为接口则抛出参数异常,判断接口是否有Extension注解标识,若没有抛出参数一样。如果上述条件都通过,则根据类型到EXTENSION_LOADERS中取对应的EXTENSION_LOADERS,若为null,则新建一个ExtensionLoader,将类型作为key,ExtensionLoader为value放入map中。
获取扩展点:
public T getExtension(String name)
public T getExtension(String name, Map<String, String> properties)
public T getExtension(Map<String, String> properties)
public T getExtension(String name, List<String> wrappers)
public T getExtension(String name, Map<String, String> properties, List<String> wrappers)
name:扩展点对应的标识,通过类获取对应的扩展点,例如:
impl1=com.alibaba.cooma.ext2.impl.Ext2Impl
通过name的impl1,可以获取Ext2Impl。
properties:用于扩展点的依赖注入属性值
wrappers:根据wrappers中的值从name2Wrapper中获取包装类。
getExtension扩展点的获取执行逻辑:1.createExtension获取扩展点然后实例化扩展点;2.inject依赖注入扩展点;若有包装类则对扩展类执行包装操作createWrapper;
获取扩展点然后实例化扩展点:
createExtension——>getExtensionClass——>getExtensionClasses——>loadExtensionClasses0——>readExtension0
createExtension:执行getExtensionClass操作,获取扩展点class,然后实例化。
getExtensionClass:执行getExtensionClasses()操作,返回对应的extClassesHolder map,然后根据参数name,获取对应的扩展点class。
getExtensionClasses:判断extClassesHolder中是否有值,没有调用loadExtensionClasses0进行加载。
loadExtensionClasses0:根据fileName = EXTENSION_CONF_DIRECTORY + type.getName()[META-INF/extensions/{type}],获取文件,调用readExtension0获取文件内容。
readExtension0:按行读取文件内容,如果name以PREFIX_ADAPTIVE_CLASS,这该扩展点是一个适配类,若以PREFIX_WRAPPER_CLASS开头,则该类是一个包装类,判断构造函数是否正确。若既不是适配类,也不是包装类,则判断是否有默认构造函数。
依赖注入:
private T inject(T instance, Map
遍历扩展点实例的所有方法,方法名以set开头,参数个数为1,方法是public方法,若属性也是一个扩展点,则获取扩展点,然后通过反射进行依赖注入。
扩展点的包装:
private T createWrapper(T instance, Map<String, String> properties, List<String> wrappers)
遍历wrappers,取出对应的包装类,包装类也是一个扩展点,它的职责就是对具体的扩展点进行能力增强。得到包装类以后,获取包含参数为扩展的构造函数,然后进行依赖注入。
获取默认的扩展点:
public T getDefaultExtension()
public T getDefaultExtension(List<String> wrappers)
以参数defaultExtension调用getExtension获取扩展点。
通过上面的分析,其实使用Cooma很容易,首先建立接口,然后在接口上注@Extension(“”)注解,基于该接口新建扩展点,在META-INF/extensions/{type}下书写扩展点,调用getExtensionLoader获得ExtensionLoader,通过获得的ExtensionLoader的getExtension方法获取扩展点实例。