Dubbo之自适应扩展点源码分析(六)
一、问题的引入
前文实现了一个简单的SPI机制,但是还有一些不完善的地方,比如在使用扩展点时,如何动态的确定使用的是哪个扩展点实现。
举个例子:
比如一个扩展点SimpleExt
:
@SPI("impl1") public interface SimpleExt { @Adaptive String echo(URL url, String s); @Adaptive({"key1", "key2"}) String yell(URL url, String s); String bang(URL url, int i); }
他有3个实现扩展点实现:SimpleExtImpl1
、SimpleExtImpl2
、SimpleExtImpl3
,那么在调用方法时,如何动态确定使用的是哪个扩展点呢?
Dubbo提出了一种思路,可以通过在运行期动态生成一个自适应扩展点实现类来要解决上面的问题:
1.首先需要确定扩展点中哪些方法需要调用不同的扩展点实现,可以通过@Adaptive
注解来标注需要生成自适应扩展点实现的类和方法。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }
- 标注在扩展点实现类上时,表示该类为自适应扩展点实现类;
- 标注在扩展点方法上时,比如例子中的
echo
和yell
方法,那么表示这两个方法需要根据 参数URL 调用对应的扩展点实现。
2.参数URL 包括协议、权限信息、参数等一系列信息。
3.根据注解和URL,生成自适应扩展点实现类,生成流程图如下:
二、概念
三、自适应扩展点的标识
- 该注解可以声明在类级别上
- 也可以声明在方法级别
- 如果修饰在类级别,那么直接返回修饰的类
- 如果修饰在方法界别,动态创建一个代理类(javassist)
四、源码
1.通过前面的图可知ExtensionLoader.getAdaptiveExtension
方法获得扩展点的自适应实现类,使用cachedAdaptiveInstance
缓存实例对象。因为我们使用静态工厂来创建单一实例对象ExtensionLoader
,所以需要使用volatile
修饰实例对象以保证线程安全性。
public T getAdaptiveExtension() { // 查找实例,从缓存中查找 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { // 创建异常缓存,避免失败后重复创建,浪费系统资源 if (createAdaptiveInstanceError != null) { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); //如果缓存不存在就创建一个 if (instance == null) { try { // 实例对象为空,则新增createAdaptiveExtension() instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { // 捕获异常,将异常添加到缓存;再抛出,防止吞掉异常 createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance; }
2. 第一步中如果实例对象为空,进入createAdaptiveExtension的创建
方法:
private T createAdaptiveExtension() { try { // newInstance只可以调用无参的构造函数,如果无法创建对象,会抛出InstantiationException异常 //其中injectExtension是个依赖注入过程,有兴趣可以自己看 //getAdaptiveExtensionClass().newInstance()是去获取一个自适应的扩展类 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
3. 接下来调用getAdaptiveExtensionClass
方法生成扩展类代码:
// 这里需要注意,他只持有带有@Adaptive注解的扩展点实现类,其他的都需要通过createAdaptiveExtensionClass动态生成 private volatile Class<?> cachedAdaptiveClass = null; private Class<?> getAdaptiveExtensionClass() { // 获取所有扩展点实现类 getExtensionClasses (); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 调用createAdaptiveExtensionClass生成AdaptiveClass return cachedAdaptiveClass = createAdaptiveExtensionClass (); }
这里包含两种情况,一种是直接拿cachedAdaptiveClass
里对象,一种通过createAdaptiveExtensionClass
生成。那么cachedAdaptiveClass
中的值是什么呢?
还记得之前我们说过在类上面的@Adaptive
注解,在getExtensionClasses
方法中,如果遇到类上面有@Adaptive
注解的,将该类放入cachedAdaptiveClass
。
执行流程如下图:
其中loadClass
会把扩展实现类中有@Adaptive
注解的类放入cachedAdaptiveClass
。这样getAdaptiveExtensionClass
时会直接获取。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) 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注解,如果包含Adaptive注解,则加入cachedAdaptiveClass缓存 // Dubbo 目前只有两个扩展点使用类上Adaptive注解,Compile 和 ExtensionFactory if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { clazz.getConstructor(); 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)) { cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n, overridden); } } } }
private void cacheAdaptiveClass(Class<?> clazz, boolean overridden) { if (cachedAdaptiveClass == null || overridden) { cachedAdaptiveClass = clazz; //// 判断唯一性 } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getName() + ", " + clazz.getName()); } }
如果cachedAdaptiveClass
为null
,那么需要通过createAdaptiveExtensionClass
来生成自适应扩展点实现类:
// 生成自适应扩展点实现类 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); }
最后调用compiler.compile (code, classLoader);
,其中code
为createAdaptiveExtensionClassCod返回值
。
Compiler
也是一个扩展点,用来生成自适应扩展点Class类对象:
@SPI("javassist") public interface Compiler { /** * Compile java source code. * * @param code Java source code * @param classLoader classloader * @return Compiled class */ Class<?> compile(String code, ClassLoader classLoader); }
他默认的自适应扩展点实现类AdaptiveCompiler
,根据第三步可知,该实现类会放入cachedAdaptiveClass
中,在调用getAdaptiveExtension
时,返回该实现类:
// 代理模式,该类的compile方法实际上执行Compile默认扩展点实现类 // JavassistCompiler的Compile方法 @Adaptive public class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) { DEFAULT_COMPILER = compiler; } @Override public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); } }
Compile
作为一个扩展点,为了符合框架中对自适应扩展点的统一调用规范,使用注解在AdaptiveCompiler
类上,并使用委托代理模式,实际调用为Compile扩展点的默认实现类JavassistCompiler
的Compile方法。
JavassistCompiler
通过compile
方法调用javassist
动态生成class。
通过createAdaptciveExtensionClassCode
生成SimpleExt
的自适应扩展点code如下:
public class SimpleExt$Adaptive implements com.ryan.myspi.adaptive.SimpleExt { public java.lang.String yell(com.ryan.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException ("url == null"); com.ryan.common.URL url = arg0; String extName = url.getParameter ("key1", url.getParameter ("key2", "impl1")); com.ryan.myspi.adaptive.SimpleExt extension = null; try { extension = (com.ryan.myspi.adaptive.SimpleExt) ExtensionLoader.getExtensionLoader (com.ryan.myspi.adaptive.SimpleExt.class).getExtension (extName); } catch (Exception e) { extension = (com.ryan.myspi.adaptive.SimpleExt) ExtensionLoader.getExtensionLoader (com.ryan.myspi.adaptive.SimpleExt.class).getExtension ("impl1"); } return extension.yell (arg0, arg1); } public java.lang.String echo(com.ryan.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException ("url == null"); com.ryan.common.URL url = arg0; String extName = url.getParameter ("simple.ext", "impl1"); com.ryan.myspi.adaptive.SimpleExt extension = null; try { extension = (com.ryan.myspi.adaptive.SimpleExt) ExtensionLoader.getExtensionLoader (com.ryan.myspi.adaptive.SimpleExt.class).getExtension (extName); } catch (Exception e) { extension = (com.ryan.myspi.adaptive.SimpleExt) ExtensionLoader.getExtensionLoader (com.ryan.myspi.adaptive.SimpleExt.class).getExtension ("impl1"); } return extension.echo (arg0, arg1); } public java.lang.String bang(com.ryan.common.URL arg0, int arg1) { throw new UnsupportedOperationException ("method public abstract java.lang.String com.ryan.myspi.adaptive.SimpleExt.bang(com.ryan.common.URL,int) of interface com.ryan.myspi.adaptive.SimpleExt is not adaptive method!"); } }
为了节约篇幅,这里直接展示生成的code,如果想了解细节,可以参考原文代码。
在SimpleExt$Adaptive
中:
echo()
:获取SimpleExt
默认扩展点实现类,调用该实现类的echo
方法。
yell()
:根据注解@Adaptive({"key1", "key2"})
中的key1和key2,然后获取URL参数中的对应值也就是扩展点实现类别名,获取该实现类并调用yell
方法。
bang()
:该方法由于没有@Adaptive
注解,直接抛出UnsupportedOperationException
异常。
由于SimpleExt$Adaptive
是在运行时动态生成的,可以看到其类似于代理模式,在方法中根据URL获取对应的扩展点实现类,然后调用对应的方法。
当然,我们也可以手动创建一个自适应扩展点:
//@Adaptive public class SimpleExt$Adaptive implements com.ryan.myspi.adaptive.SimpleExt { --- }
那么为什么要使用javassist
呢? 对于框架中大量的扩展点,相同的逻辑则带来了很多重复的工作量,并且及易引入bug。
因此,使用javassist
生成自适应扩展点使得对框架的扩展的变得更加优雅;他将生成自适应扩展点的逻辑进行了抽象,统一通过该方式来生成,减少了框架开发者的工作量。
对自适应扩展点实现机制进行单元测试:
@Test public void getAdaptiveExtension(){ SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension(); Map<String, String> map = new HashMap<String, String>(); map.put("key2", "impl2"); URL url = new URL("p1", "1.2.3.4", 1010, "path1", map); String echo = ext.yell(url, "haha"); assertEquals("Ext1Impl2-yell", echo); url = url.addParameter("key1", "impl3"); // note: URL is value's type echo = ext.yell(url, "haha"); assertEquals("Ext1Impl3-yell", echo);
使用IOC注入扩展点依赖
对扩展点依赖注入直接使用了ExtensionLoader
的工厂方法。在Dubbo中是不建议这样使用的。作为框架,Dubbo尽可能的支持良好的扩展功能,被注入的扩展点实例可以通过SPI的方式产生,也可以为Spring容器中的bean实例,或者自定义的容器。
因此抽象一个扩展点ExtensionFactory
,用来生产扩展点依赖注入的实例对象,达到控制反转的目的:
@SPI public interface ExtensionFactory { <T> T getExtension(Class<T> type, String name); }
ExtensionFactory
做为一个扩展点可以按需求增加扩展实现类,比如:SpringExtensionFactory
、SpiExtensionFactory
、AdaptiveExtensionFactory
等。
接着通过在ExtensionLoader
中引入Extensionfactory
:
private final ExtensionFactory objectFactory; // 构造函数改为私有 private ExtensionLoader(Class<T> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
同时在injectExtension
方法中通过objectFactory.getExtension (pt, property)
获取扩展点注入实例:
private T injectExtension(T instance) { if (objectFactory == null) { return instance; } try { // 通过反射注入扩展类 for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { continue; } /** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) { continue; } // 注入 Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { //// 获取参数 String property = getSetterProperty(method); Object object = objectFactory.getExtension(pt, property); // 反射调用set方法注入该扩展类 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; }
至此,我们得到经过依赖注入的扩展点实现类。
注:
xtensionLoader
中getAdaptiveExtension
和getExtensionLoader
方法返回的对象不同,getAdaptiveExtension
返回自适应扩展点实现类对象;getExtensionLoader
返回不包含自适应扩展点的所有扩展点实现类对象。在方法级别严格遵守了单一职责原则。
五、应用
之前介绍了dubbo中SPI扩展点之自适应,那么线程池就是自适应扩展点的应用体现;dubbo中已实现的线程池扩展点有:
以fixed为ID的FixedThreadPool示例分析:
1、FixedThreadPool的UML图如下:
2、查看ThreadPool接口:
/** * ThreadPool */ @SPI("fixed")//默认扩展实现为fixed public interface ThreadPool { /** * Thread pool * * @param url URL contains thread parameter * @return thread pool */ //配置文件中配置,threadpool的key,找到对应的线程池实现 @Adaptive({THREADPOOL_KEY}) Executor getExecutor(URL url); }
下面以FixedThreadPool为基础实现自定义的线程池扩展:
1、线程池实现, 这里主要是基于对 FixedThreadPool 中的实现做扩展出线程监控的部分:
public class GhyThreadPool extends FixedThreadPool implements Runnable { private static final Logger LOGGER= LoggerFactory.getLogger ( GhyThreadPool.class ); private static final double ALARM_PERCENT=0.90; private final Map<URL, ThreadPoolExecutor> THREAD_POOLS=new ConcurrentHashMap<> ( ); public GhyThreadPool(){ //每隔5秒打印线程使用情况 Executors.newSingleThreadScheduledExecutor ( ) .scheduleWithFixedDelay (this,5,5,TimeUnit.SECONDS); } public Executor getExecutor(URL url){ //从父类中创建线程池 final Executor executor=super.getExecutor ( url ); if (executor instanceof ThreadPoolExecutor){ THREAD_POOLS.put ( url,((ThreadPoolExecutor) executor) ); } return executor; } @Override public void run() { //循环线程池,如果超出指定部分,进行操作 for (Map.Entry<URL,ThreadPoolExecutor> entry:THREAD_POOLS.entrySet ()){ final URL url=entry.getKey (); final ThreadPoolExecutor executor=entry.getValue (); //当前执行中的线程数 final int activeCount=executor.getActiveCount (); //总计线程数 final int poolSize=executor.getCorePoolSize (); double used=activeCount/poolSize; final int usedNum=(int)(used*100); LOGGER.info ( "线程池执行状态" +activeCount,poolSize,usedNum); if (used>=ALARM_PERCENT){ LOGGER.error ( "超出警戒值:"+url.getIp(),usedNum,url ); } } } }
2、SPI声明,创建文件 META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool
3、在服务提供方项目引入该spring-boot-server依赖(我是懒,其实可以把这做成一个共公MAVEN工程进行引用)
4、在服务提供方项目中设置使用该线程池生成器
为什么这样写呢?前面查看ThreadPool接口的时候,通过@Adaptive注解已经明确key值为threadpool。这样自定义过程就完成了,接下来就可以自己调用主程序进行测试了