【Dubbo系列】扩展点自适应与IOC增强

5 篇文章 0 订阅

前言

本文是**【Dubbo系列】无处不在的SPI** 系列文章的第四小节。

【Dubbo系列】无处不在的SPI:崭露头角 主要介绍了JDK的SPI机制,以及ServiceLoader的使用。

【Dubbo系列】无处不在的SPI之羽翼渐丰 补充了SPI和API的区别,并对JDK的SPI进行改写,通过ExtensionLoader实现了仅加载所需要的扩展实现类,达到节约资源的目的。

【Dubbo系列】无处不在的SPI之独当一面 通过静态工厂生成ExtensionLoader单一实例对象,并通过反射实现了扩展点依赖注入功能。

扩展点自适应

前几文实现了一个简单的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个实现扩展点实现:SimpleExtImpl1SimpleExtImpl2SimpleExtImpl3,那么在调用方法时,如何动态确定使用的是哪个扩展点呢?

Dubbo提出了一种思路,可以通过在运行期动态生成一个 自适应扩展点实现类 来要解决上面的问题:

  • 首先需要确定扩展点中哪些方法需要调用不同的扩展点实现,可以通过@Adaptive注解来标注需要生成自适应扩展点实现的类和方法。
  @Documented
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.TYPE, ElementType.METHOD})
  public @interface Adaptive {
  
      String[] value() default {};
  
  }

​ 标注在扩展点实现类上时,表示该类为自适应扩展点实现类;
​ 标注在扩展点方法上时,比如例子中的echoyell方法,那么表示这两个方法需要根据 条件 调用对应的扩展点实现。

  • 其次增加使用具体扩展点实现的条件,可以通过参数 URL 来指定。URL 包括协议、权限信息、参数等一系列信息。

  • 根据注解和条件,生成自适应扩展点实现类,生成流程图如下:

  1. 通过ExtensionLoader.getAdaptiveExtension方法获得扩展点的是自适应实现类,使用cachedAdaptiveInstance 缓存实例对象。因为我们使用静态工厂来创建单一实例对象ExtensionLoader,所以需要使用volatile修饰实例对象以保证线程安全性。

    // 创建两个缓存,一个保存创建成功的自适应扩展点实现类,一个保存失败后的异常信息,
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object> ();
    private volatile Throwable createAdaptiveInstanceError = null;
    
    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {   
        // 查找实例
        Object instance = cachedAdaptiveInstance.get ();
        if (instance == null) {
            // 创建异常缓存,避免失败后重复创建,浪费系统资源
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get ();
                    if (instance == null) {
                        try {
                            // 实例对象为空,则新增createAdaptiveExtension()
                            instance = createAdaptiveExtension ();                    
                            cachedAdaptiveInstance.set (instance);
                        } catch (Throwable t) {
                            // 捕获异常,将异常添加到缓存;再抛出,防止吞掉异常
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException ("fail to create adaptive instance: " + t.toString (), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException ("fail to create adaptive instance: " + createAdaptiveInstanceError.toString (), createAdaptiveInstanceError);
            }
        }
    
        return (T) instance;
    }
    
  2. 第一步中如果实例对象为空,进入createAdaptiveExtension方法:

    private T createAdaptiveExtension() {
        try {
            // newInstance只可以调用无参的构造函数,如果无法创建对象,会抛出InstantiationException异常
            return injectExtension ((T) getAdaptiveExtensionClass ().newInstance ());
        } catch (Exception e) {
            throw new IllegalStateException (" can not create adaptiveExtensionClass  " + type + ", cause: " + e.getMessage ());
        }
    }
    
  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, Class<?> clazz, String name) {
    // 判断是类上是否有Adaptive注解,如果包含Adaptive注解,则加入cachedAdaptiveClass缓存
    // Dubbo 目前只有两个扩展点使用类上Adaptive注解,Compile 和 ExtensionFactory
    if (clazz.isAnnotationPresent (Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else {
            // 判断唯一性
            if (cachedAdaptiveClass != clazz) {
                throw new IllegalStateException (" adaptiveClass is redundance : " + cachedAdaptiveClass.getClass ().getName ());
            }
        }
    } else{
        extensionClasses.put (name, clazz); 
    }
}
  1. 如果cachedAdaptiveClassnull,那么需要通过createAdaptiveExtensionClass来生成自适应扩展点实现类:

    // 生成自适应扩展点实现类
    private Class<?> createAdaptiveExtensionClass() {
        // 生成自适应类的代码
        String code = createAdaptiveExtensionClassCode ();
        ClassLoader classLoader = findClassLoader ();
        com.ryan.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader (com.ryan.compiler.Compiler.class).getAdaptiveExtension ();
        return compiler.compile (code, classLoader);
    }
    

    最后调用compiler.compile (code, classLoader);

Compiler也是一个扩展点,用来生成自适应扩展点Class类对象:

@SPI("javassist")
public interface Compiler {
    Class<?> compile(String code, ClassLoader classLoader);
}

他默认的自适应扩展点实现类AdaptiveCompiler,根据第三步可知,该实现类会放入cachedAdaptiveClass中,在调用getAdaptiveExtension时,返回该实现类:

// 代理模式,该类的compile方法实际上执行Compile默认扩展点实现类
// JavassistCompiler的Compile方法
@Adaptive
public class AdaptiveCompiler implements Compiler {
    //
    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader (Compiler.class);
        compiler = loader.getDefaultExtension ();
        return compiler.compile (code, classLoader);
    }
}

createAdaptiveExtensionClass方法最后compiler.compile (code, classLoader);实际上为调用JavassistCompilerComplie方法。

看一下Compile扩展点的类图:

mark

Compile作为一个扩展点,为了符合框架中对自适应扩展点的统一调用规范,使用注解在AdaptiveCompiler类上,并使用委托代理模式,实际调用为Compile扩展点的默认实现类JavassistCompiler的Compile方法。

  1. 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注入扩展点依赖

【Dubbo系列】无处不在的SPI之独当一面 中,对扩展点依赖注入直接使用了ExtensionLoader的工厂方法。在Dubbo中是不建议这样使用的。作为框架,Dubbo尽可能的支持良好的扩展功能,被注入的扩展点实例可以通过SPI的方式产生,也可以为Spring容器中的bean实例,或者自定义的容器。

因此抽象一个扩展点ExtensionFactory,用来生产扩展点依赖注入的实例对象,达到控制反转的目的:

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

ExtensionFactory做为一个扩展点可以按需求增加扩展实现类,比如:SpringExtensionFactorySpiExtensionFactoryAdaptiveExtensionFactory等。

接着通过在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 (instance == null) {
        throw new IllegalArgumentException (" injectExtension param instance == 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 methodName = method.getName ();
                String property = methodName.length () > 3 ? methodName.substring (3, 4).toLowerCase () + methodName.substring (4) : "";
   // Object ob = ExtensionLoader.getExtensionLoader (pt).getExtension (property);
                Object ob = objectFactory.getExtension (pt, property);
                // 反射调用set方法注入该扩展类
                if (ob != null) {
                    method.invoke (instance, ob);
                }
            } catch (Exception e) {
                System.out.println ("fail to inject via method :" + method.getName ());
            }
        }
    }
    return instance;
}

至此,我们得到经过依赖注入的扩展点实现类。

注:

ExtensionLoadergetAdaptiveExtensiongetExtensionLoader方法返回的对象不同,getAdaptiveExtension返回自适应扩展点实现类对象;
getExtensionLoader返回不包含自适应扩展点的所有扩展点实现类对象。在方法级别严格遵守了单一职责原则。

小结

主要研究了Dubbo的自适应扩展点IOC依赖注入的实现原理。并在前几个小结的基础上对代码进行完善。

参考

文中代码地址:https://github.com/non-trivial/myspi/tree/IOC

JAVA programming ASSISTant :http://www.javassist.org/

Dubbo官方文档 :http://dubbo.apache.org/zh-cn/docs/dev/coding.html

推荐阅读:

Mybatis拦截器源码深度解析

线程池的前世今生
了解更多请关注微信公众号

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值