1.加载机制
Dubbo拥有良好的扩展性得益于运用恰到好处的各种设计模式,还有就是加载机制,基于Dubbo SPI加载机制,可以让整个框架的接口和具体实现完全解耦。
1.1 Java SPI和Dubbo SPI
**API (Application Programming Interface)**在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
**SPI (Service Provider Interface)**是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。
下面是使用Java SPI的简单例子,其中需要在META-INF/Services/目录下,创建一个以接口全路径命名的文件,如com.seewo.dubbo.demo.spi.printService文件,文件内容为具体实现类的全路径名,如果有多个实现类则用换行符分隔。
META-INF/services/com.seewo.dubbo.demo.spi.printService文件内容为实现类的全路径名
com.seewo.dubbo.demo.spi.PrintServiceImpl
com.seewo.dubbo.demo.spi.PrintServiceNewImpl
public interface PrintService {
void printInfo();
}
public class PrintServiceNewImpl implements PrintService {
@Override
public void printInfo() {
System.out.println("new hello world");
}
}
public class PrintServiceImpl implements PrintService {
@Override
public void printInfo() {
System.out.println("hello world");
}
}
public class jdkDemo {
public static void main(String[] args) {
ServiceLoader<PrintService> serviceServiceLoader =
ServiceLoader.load(PrintService.class);
//获取所有的SPI实现,循环调用printInfo方法
for (PrintService printService : serviceServiceLoader) {
//打印出"new hello world" "hello world"
printService.printInfo(); }
}
}
其中ServiceLoader的部分核心实现如下所示,这里指定了配置文件的保存路径,这里实现了迭代器,当进行循环迭代时会使用到当前线程的ClassLoader类加载器。
public final class ServiceLoader<S> implements Iterable<S> {
//扫描目录前缀
private static final String PREFIX = "META-INF/services/";
// 被加载的类或接口
private final Class<S> service;
// 用于定位、加载和实例化实现方实现的类的类加载器
private final ClassLoader loader;
// 上下文对象
private final AccessControlContext acc;
// 按照实例化的顺序缓存已经实例化的类
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 懒查找迭代器
private java.util.ServiceLoader.LazyIterator lookupIterator;
public static <S> ServiceLoader<S> load(Class<S> service) {
//使用当前线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 私有内部类,提供对所有的service的类的加载与实例化
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
String nextName = null;
private boolean hasNextService() {
if (configs == null) {
try {
//获取扫描的路径
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
//...
}
//....
}
}
private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//反射加载类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
}
try {
//实例化
S p = service.cast(c.newInstance());
//放进缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
//..
}
//..
}
}
}
Dubbo作为一个高度可扩展的RPC框架,也依赖于Java的SPI,并且Dubbo对Java原生的SPI 机制作出了一定的扩展,使得其功能更加强大。 使用Dubbo框架中的ExtensionLoader加载器也可以实现SPI效果,但需要在接口类上添加@SPI注解和对应的的实现类别名"impl"对应配置文件上的key=value(实现类全路径名)。
META-INF/dubbo/com.seewo.dubbo.demo.spi.printService文件内容如下
impl=com.seewo.dubbo.demo.spi.PrintServiceImpl
@SPI("impl")
public interface PrintService {
void printInfo();
}
PrintService printService =
ExtensionLoader.getExtensionLoader(PrintService.class).getDefaultExtension();
printService.printInfo();
从上面的Java SPI的原理中可以了解到,Java的SPI机制有着如下的弊端:
- 只能遍历所有的实现,并全部实例化,如果有扩展实现实现初始化很耗时,但后面可能没使用到,则浪费资源。
- z配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
- 扩展如果依赖其他的扩展,做不到自动注入和装配。 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持。
Dubbo SPI中有如下几个概念:
-
扩展点: 接口;
-
扩展: 扩展的实现;
-
扩展自适应实例: 其实就是一个Extension的代理,实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。dubbo框架会根据接口中的参数,自动地决定选择哪个实现;
-
@SPI: 该注解作用于扩展点的接口上,表明该接口是一个扩展点;
-
@Adaptive:@Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。
1.2 扩展点配置规范
Dubbo SPI和Java SPI类似,需要在 META-INF/dubbo/路径下放置对应的SPI配置文件,文件名称以需要命名为接口的全路径名字,配置文件的内容为 key=扩展点具体实现类的全路径名,如果有多个实现类则使用换行符作为分隔符。其中,key作为Dubbo SPI注解中传入的参数。另外,Dubbo SPI还兼容了Java SPI的配置路径和内容配置方式,在Dubbo框架启动的时候,会去默认扫描META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal/这个三个路径。
- SPI配置文件路径: META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal/
- SPI配置文件名称: 全路径类名
- 文件内容格式: key=value方式,多个则用换行符分隔
1.3 扩展点分类与缓存
Dubbo SPI分为Class类缓存、实例缓存。这两种缓存又能根据扩展类的种类分为普通扩展类、包装扩展类(Wrapper类)、自适应扩展(Adaptive类)等。
- Class类缓存: Dubbo SPI获取扩展类时,会先从缓存中读取,如果缓存中不存在,则加载配置文件,根据配置文件加载的Class缓存到内存中,而不实例化。
- 实例缓存: Dubbo不仅会缓存Class,而且也会缓存Class实例化后的对象,每次获取的时候,也是先从缓存中读取,如果缓存中不存在,则重新加载并缓存起来。
被缓存起来的Class和对象实例可以根据不同的特性分为不同的类别:
- 普通扩展类,配置在SPI配置文件中的扩展类实现;
- 包装扩展类,这种Wrapper类没有具体的实现,只做了通用逻辑的抽象,并且需要在构造方法中传入一个具体的扩展接口的实现。
- 自适应扩展类,一个扩展接口会有多种实现类,具体使用哪个实现类在运行时根据传入的URL中的某些参数动态确定和调整。
- 其他缓存,如扩展类加载器缓存、扩展名缓存等。
具体缓存实现在ExtensionLoader的实现如下所示:
public class ExtensionLoader<T> {
//扩展类与对应的扩展类加载器缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>>
EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
//扩展类与类实例缓存
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES
= new ConcurrentHashMap<>(64);
//扩展类与扩展名缓存
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
//普通扩展类缓存,不包括自适应扩展类和Wrapper类
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//扩展名与有@Activate注解对象的缓存
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
//扩展类名与扩展对象缓存
private final ConcurrentMap<String, Holder<Object>> cachedInstances
= new ConcurrentHashMap<>();
//实例化后的自适应(Adaptive)扩展对象,只能同时存在一个
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
//Wrapper类缓存
private Set<Class<?>> cachedWrapperClasses;
}
1.4 扩展点的特性
1.4.1 自动包装
ExtensionLoader在加载扩展时,如果发现这个扩展类包含其他扩展点作为其构造方法的参数,则这个扩展类会被认为是Wrapper类。以ProtocolFilterWrapper类为例,这个类继承了Protocol接口,这个类的构造函数注入了一个Protocol类型的参数,因此ProtocolFilterWrapper认定为Wrapper类,这是一种装饰器模式,可以将通用的逻辑抽象进行封装或对其子类进行增强,让子类更加专注自身的实现。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
....
}
1.4.2 自动加载
除了在构造函数中传入其他扩展实例,还会经常使用setter方法设置属性值,ExtensionLoader在执行扩展点初始化时,会自动通过setter方法注入对应的实现类。如下Transport扩展点的定义,在@Adaptive中传入了两个参数分别是"server"和"transport"。当外部调用Transport#bind方法时,会动态传入的参数"URL"提取参数"server"的value值,如果匹配上某个扩展实现类则直接使用对应的扩展实现类。如果未匹配上,则通过第二个参数"transport"提取value值,如果都没匹配上则抛出异常。也就是@Adaptive中传入多个参数,则依次实现类的匹配,直到最后抛出异常。
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
1.4.3 自适应
在Dubbo SPI中,使用了@Adaptive注解,可以动态地通过URL中的参数来确定要使用哪个具体的实现类。
1.4.4 自动激活
使用@Activate注解,可以标记对应的扩展点默认被激活启用,该注解还可以通过传入不同的参数,设置扩展点在不同的条件下下被自动激活。
2.扩展点注解使用
2.1 @SPI注解
@SPI注解有一个value属性,可以传入不同的参数来设置这个接口的默认实现类。Dubbo很多地方都是通过getExtension(Class type, String name)来获取扩展点接口的具体实现,此时对传入的Class做校验,判断是否是接口,是否有@SPI注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
//默认实现的key名称
String value() default "";
}
2.2 @Adaptive注解
@Adaptive注解可以标注在类、接口、枚举和方法上,类级别的只有AdaptiveExtensionFactory和AdaptiveCompiler,其他都是标注在方法级上。方法级别的注解在第一次getExtension时,会自动生成和编译一个动态的Adaptive类,从而达到动态实现类的效果。注解上可以传入value数组参数,在初始化Adaptive注解的接口时,会先对传入的URL进行key值匹配,第一个key没匹配上则匹配第二个,知道所有的key匹配完毕,如果还没匹配到则使用@SPI注解的默认值去匹配。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
//可以设置多个,会被依次匹配
String[] value() default {};
}
2.3 @Activate注解
@Activate可以标记在的类、接口、枚举和方法上,主要使用在多个扩展点实现上,需要根据不同条件被激活的场景中,如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
//URL中的分组如果匹配则激活,可以设置多个
String[] group() default {};
//如果URL中含有该key值,则会被激活
String[] value() default {};
//排序信息
int order() default 0;
}
3.ExtensionLoader工作原理
ExtensionLoder是整个扩展机制的主要实现类,在这个类里面实现了配置加载、扩展类缓存、自适应对象生成缓存等工作。
3.1 ExtensionLoader创建
ExtensionLoader通过工厂方法ExtensionFactory创建的,这里使用的是工厂设计模式,有多个ExtensionLoader生成实现。
@SPI
public interface ExtensionFactory {
<T> T getExtension(Class<T> type, String name);
}
工厂类有多个具体实现,可以看到的有SpringExtensionFactory、AdaptiveExtensionFactory、SpiExtensionFactory三个具体工厂实现类。
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
其中Dubbo和Spring容器是通过SpringExtensionFactory进行打通的,该工厂实现类保存了Spring上下文的静态方法,可以把Spring上下文保存到Set集合中,这样可以多次调用时添加了重复的上下文引用。SpringExtensionFactory是在ReferenBean和ServiceBean初始化时调用静态方法时保存Spring上下文。
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
ApplicationContextAware, InitializingBean, DisposableBean {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
}
}
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
}
}
public class SpringExtensionFactory implements ExtensionFactory {
//用来保存Spring上下文的Set集合
private static final Set<ApplicationContext> CONTEXTS
= new ConcurrentHashSet<ApplicationContext>();
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
}
}
....
@Override
public <T> T getExtension(Class<T> type, String name) {
//遍历所有Spring上下文,根据名字和类型从Spring容器中查找
for (ApplicationContext context : CONTEXTS) {
T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
if (bean != null) {
return bean;
}
}
return null;
}
}
SpiExtensionFactory的实现比较简单,直接使用ExtensionLaoder实现获取返回对象。
public class SpiExtensionFactory implements ExtensionFactory {
@Override
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();
}
}
return null;
}
}
AdaptiveExtensionFactory是默认实现,有@Adaptive注解,所有会去获取所有扩展类生成工厂并缓存起来,缓存的工厂类通过TreeSet排序,SPI排前面,Spring排后面,当使用getExtension时,会遍历所有的工厂,先从SPI容器中获取扩展类,如果没找到再从Spring容器中获取扩展类。
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
//用来缓存所有的工厂类实现,包括SpiExtensionFactory/SpringExtensionFactory
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
//工厂列表是也通过SPI实现的,这里可以获取到所有的工厂实现类
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;
}
}
3.2 ExtensionLoader初始化
ExtensionLoader的初始化配置规定了几种资源文件加载策略,可以通过ExtensionLoader#setLoadingStrategies设置好配置文件加载策略。如果想从其他目录文件加载类,自定义资源加载策略并设置即可。
//加载策略接口
public interface LoadingStrategy extends Prioritized {
//返回加载配置的路径
String directory();
//预处理
default boolean preferExtensionClassLoader() {
return false;
}
//排除在外的包路径
default String[] excludedPackages() {
return null;
}
}
//自定义的资源加载策略
public class DubboExternalLoadingStrategy implements LoadingStrategy {
@Override
public String directory() {
//配置文件保存路径
return "META-INF/dubbo/external/";
}
@Override
public boolean overridden() {
return true;
}
@Override
public int getPriority() {
//加载优先级
return MAX_PRIORITY + 1;
}
}
在获取扩展实例前是需要加载类信息进来的,通过ExtensionLoader#getExtensionClasses方法中,如果已经加载过放在缓存中就会获取缓存中的类信息,如果没有则调用ExtensionLoader#loadExtensionClasses方法,读取配置文件路径,然后加载类信息进来。
private Map<String, Class<?>> loadExtensionClasses() {
....
//遍历多个配置文件加载策略
for (LoadingStrategy strategy : strategies) {
//从配置文件保存路径中读取配置并加载
loadDirectory(extensionClasses, strategy.directory(),
type.getName(), strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.excludedPackages());
....
}
return extensionClasses;
}
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst,
boolean overridden, String... excludedPackages) { String fileName = dir + type;
try {
....
//通过getResources()或者getSystemResources得到配置文件
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
//遍历urls,解析字符串,得到扩展实现类,并缓存起来
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (hrowable t) {
...
}
}
}
加载完的扩展点配置后, 再通过反射获得所有扩展实现类实现并缓存起来。在加载Class文件时,会根据Class上的标识来判断扩展点类型,再根据类型分类做缓存。
private void loadClass(Map<String, Class<?>> extensionClasses,
java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
//如果类上有@Adaptive注解,则缓存为自适应类
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
//如果是扩展包装类,则直接缓存起来
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
...
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
}
3.2 ExtensionLoader主要功能方法解析
ExtensionLoader主要有获取普通扩展类(getExtension)、获取自适应扩展类(getAdaptiveExtension)、获取自动激活的扩展类(getActiveExtension)三个方法,这也是比较常用到的。
3.2.1 getExtension实现原理
在调用getExtension(String name)方法时,会先检查在cachedInstance缓存中的是否有现成的数据,没有则调用createExtension开始创建。
public T getExtension(String name) {
//先从缓存中看有没有现成的数据
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//没有就创建
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
}
private T createExtension(String name) {
...
try {
//从缓存中获取扩展类实例的情况
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//向扩展类注入其依赖的属性,如扩展类A又依赖类扩展类B
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//遍历扩展点包装类,用于初始化包装类实例
for (Class<?> wrapperClass : wrapperClasses) {
//找到构造方法的参数类型为type的包装类,为其注入扩展类实例
instance = injectExtension((T) wrapperClass
.getConstructor(type)
.newInstance(instance));
}
}
//初始化扩展实例
initExtension(instance);
return instance;
} catch (Throwable t) {
...
}
}
injectExtension总体上实现了Spring IOC功能, 通过反射获取到类的所有定义的方法,遍历获取set开头的方法,并得到set方法的参数类型,再通过ExtensionFactory寻找类型相同的扩展类实例,如果找到,则使用反射设置注入进去,这个在获取Wrapper包装类中比较常见。
private T injectExtension(T instance) {
....
try {
for (Method method : instance.getClass().getMethods()) {
//如果不是set开头的方法则跳过
if (!isSetter(method)) {
continue;
}
//获取方法第一个参数的类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
....
try {
//通过字符串截取,获取小写开头的类名,如setTestService,截取testService
String property = getSetterProperty(method);
//通过ExtensionFactory获取实例
Object object = objectFactory.getExtension(pt, property);
//如果获取了这个扩展类实现,则调用set方法,将实例注入进去
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
.....
}
}
} catch (Exception e) {
.....
}
return instance;
}
3.2.2 getAdaptiveExtension实现原理
在getAdaptiveExtension方法中,会为扩展点接口自动生成实现字符串,实现主要逻辑如下:为接口中每个有@Adaptive注解的方法生成默认的实现(没有注解的方法则为空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后,框架会使用不同的编译器,把实现字符串编译为自适应类并返回。
该注解可以传入value参数,是一个数组。Adaptived可以传入多个key值,在初始化@Adaptive注解的接口时,会先对传入的URL进行key值匹配,第一个key没匹配上则匹配第二个,以此类推。直到所有的key匹配完毕,如果还没有匹配到,则使用@SPI注解中填写的默认值再去匹配,如果@SPI注解没有默认的值,则会抛出异常。
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
....
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
...
}
}
}
}
return (T) instance;
}
//动态编译生成Class
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);
}
3.2.3 getActivateExtension实现原理
getActiveExtension(URL url, String key, String group)方法获取所有自动激活扩展点。参数分别为URL、URL中指定的多个key(多个逗号隔开)和URL中指定的分组信息(group)。
**
**
public List<T> getActivateExtension(URL url, String[] values, String group) {
//初始化所有扩展类实现的集合
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
//先加载所有扩展实现类
getExtensionClasses();
//遍历map缓存
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else {
continue;
}
//如果满足分组
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
//根据用户URL
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
...
}
}
4.扩展点动态编译实现
其中动态编译是自适应特性实现的基础,动态生成的自适应类只是字符串,需要通过编译才能真正转化成Class。动态编译就是为了提高性能,不用每次都是去使用反射代理一个类,而是采用直接编译生成好一个Class,Dubbo SPI通过代码动态生成,并配合动态编译器,灵活地在原始类基础上创建新的自适应类。Dubbo代码编译器有JDK、Javassist、AdaptiveCompiler编译器三种,都实现了Compiler接口。
Compiler接口有个@SPI注解,默认代码编译器的实现类为Javassist。其中Java动态生成Class的方式有很多,常见的通过字节码方式如CGLIB/ASM等生成,而Javassist是通过字符串代码再编译为Class的方式完成的。
//生成类定义字符串
String getSimpleCodeWithSyntax0(){
StringBuilder code = new StringBuilder();
code.append("package org.apache.dubbo.common.compiler.support;");
code.append("public class HelloServiceImpl_0 implements HelloService {");
code.append(" public String sayHello() { ");
code.append(" return \"Hello world!\"; ");
code.append(" }");
return code.toString();
}
//使用Javassist编译器编译类定义字符串后生成class
public void testCompileJavaClass1() throws Exception {
JavassistCompiler compiler = new JavassistCompiler();
Class<?> clazz = compiler.compile(getSimpleCodeWithSyntax0(), JavassistCompiler.class.getClassLoader());
Object instance = clazz.newInstance();
Method sayHello = instance.getClass().getMethod("sayHello");
sayHello.invoke(instance);
}
5.总结
本篇介绍了Dubbo SPI的一些概要信息,包括了Java SPI的区别、实现原理、Dubbo SPI的新特性、配置规范与的内部缓存等,比较重要的是@SPI/@Adaptive/@Activate三个注解,其中这三个注解的作用与实现原理,然后结合核心实现类ExtensionLoader,讲解了它的生成机制/初始化机制/使用的getExtension/getAdaptiveExtension/getActivateExtension三个主要方法的实现,最后还讲解了动态编译相关的内容。
参考文献
www.cnblogs.com/jy107600/p/… SPI原理解析
www.baidu.com/link?url=rY… 反射与动态代理性能对比