Dubbo之参数注入

建议先阅读上一篇文章Dubbo之@Activate,效果更佳。

关键词:依赖注入

 


参数注入?大部分的说法是依赖注入。因为Dubbo是通过方法参数来实现注入的,我就叫它参数注入了吧。

我将Dubbo中的参数注入分成两类,一类是通过set方法注入,另一类是包装类,是通过构造器注入,只不过这里的构造器是包装类的构造器。那什么是Set方法注入,什么又是包装注入呢,两者有啥区别呢?


█ 如何使用 ——————————————————————★

先来看看Set方法注入

①修改DogService,在类中添加下面的内容。

private AnimalService animalService;

// com.alibaba.dubbo.common.compiler.Compiler
private Compiler compiler;

public void setDog(AnimalService animalService) {
    this.animalService = animalService;
}

public void setJavassist(Compiler compiler) {
    this.compiler = compiler;
}

public void showResult() {
    System.out.println(animalService.getClass().getName());
    System.out.println(compiler.getClass().getName());
}

②运行代码。

public class DITest {

    public static void main(String[] args) {

        ExtensionLoader<AnimalService> loader = ExtensionLoader.getExtensionLoader(AnimalService.class);
        AnimalService animalService = loader.getExtension("dog");
        // 因为要调用showResult方法,强转成DogService
        DogService dogService = (DogService)animalService;
        // 输出结果:
        //study.rui.dubbo.AnimalService$Adaptive
        com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
        dogService.showResult();

    }

}

结果输出的是:

study.rui.dubbo.AnimalService$Adaptive

com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler


█ 源码解读 ——————————————————————★

看见上面的输出结果中类的名称,是否有点眼熟。看过《Dubbo之@Adaptive》的同学应该会知道,这是getAdaptiveExtension方法返回的结果。你是否猜想,Dubbo的注入是注入@Adaptive修饰的类或根据@Adaptive方法动态编译产生的类吗?下面,我们来看看源码。

injectExtension方法实现了注入的功能,在实现类的实例被创建之后开始注入

private T injectExtension(T instance) {
        try {
            // 这里的objectFactory是AdaptiveExtensionFactory
            // 在getExtensionLoader方法中的代码:
            // this.objectFactory = type == ExtensionFactory.class ? null : (ExtensionFactory)getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
            // 可见只要参数接口类型不是ExtensionFactory,结果就是AdaptiveExtensionFactory(因为此类上标注了@Adaptive)
            if (this.objectFactory != null) {
                // 获取当前扩展实现类所有的方法,getMethods只会获取到public方法
                Method[] var2 = instance.getClass().getMethods();
                int var3 = var2.length;
                // 遍历所有方法
                for(int var4 = 0; var4 < var3; ++var4) {
                    Method method = var2[var4];
                    // 方法满足四个条件就会执行注入:
                    // 1.set开头。
                    // 2.只有一个参数。
                    // 3.访问修饰符是public(感觉这个是多余的,因为getMethods已经只能获取public方法了)。
                    // 4.方法上没有标注@DisableInject
                    if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers()) && method.getAnnotation(DisableInject.class) == null) {
                        Class pt = method.getParameterTypes()[0];

                        try {
                            // 5.方法名要大于3,因为set就占用了3个长度
                            // 截取方法名,获取配置key
                            // 比如setName,获取到的值是:name
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 根据截取后获取到的配置key,去获取对应的扩展实现类
                            // 实际调用的就是AdaptiveExtensionFactory的getExtension方法了
                            Object object = this.objectFactory.getExtension(pt, property);
                            // 找到了就注入
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception var9) {
                            logger.error("fail to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
                        }
                    }
                }
            }
        } catch (Exception var10) {
            logger.error(var10.getMessage(), var10);
        }

        return instance;
    }

}

根据截取方法名得到的配置key,去获取扩展实现类。下面来看看AdaptiveExtensionFactory的getExtension方法。

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    // 用于保存所有的普通ExtensionFactory
    // 过滤掉了@Adaptive修饰的,和包装类
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList();
        // 注意getSupportedExtensions这里获取到adaptive本身
        Iterator var3 = loader.getSupportedExtensions().iterator();
        // 获取到SpiExtensionFactory和SpringExtensionFactory 
        while(var3.hasNext()) {
            String name = (String)var3.next();
            list.add(loader.getExtension(name));
        }
        // 顺序一定是SpiExtensionFactory在前,SpringExtensionFactory在后
        this.factories = Collections.unmodifiableList(list);
    }

    public <T> T getExtension(Class<T> type, String name) {
        // 依次从SpiExtensionFactory、SpringExtensionFactory 中去获取
        Iterator var3 = this.factories.iterator();

        Object extension;
        do {
            if (!var3.hasNext()) {
                return null;
            }

            ExtensionFactory factory = (ExtensionFactory)var3.next();
            extension = factory.getExtension(type, name);
        } while(extension == null);
        // 获取到了就返回实现类实例
        // 获取不到就返回null
        return extension;
    }
}

factories里的顺序为什么是SpiExtensionFactory在前,SpringExtensionFactory在后?

猜想:是不是配置文件中的顺序决定的?

spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory

Dubbo的配置顺序是上面这样的。和猜想的不对,那是怎么回事呢?关键在于getSupportedExtensions方法。

 

public Set<String> getSupportedExtensions() {
    Map<String, Class<?>> clazzes = this.getExtensionClasses();
    // 这里将结果放进了TreeSet中,TreeSet是具有排序功能的Set
    // TreeSet的实现用的是TreeMap。如果没有指定排序器,TreeMap用的是key的排序规则
    // 这里的key是String类型的,所以会使用String的排序规则。(以后有机会聊聊TreeMap)
    // 所以,按照排序spi要比spring在前
    return Collections.unmodifiableSet(new TreeSet(clazzes.keySet()));
}

下面来看看SpiExtensionFactory。

public <T> T getExtension(Class<T> type, String name) {
    // 前置判断类型是否是接口,是否标注了@SPI注解
    if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
        ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
        // 就两种结果:
        // 获取接口类型的扩展实现类数量大于0,就创建自适应扩展类实例
        // 否则返回null。
        if (loader.getSupportedExtensions().size() > 0) {
            // 如果没有实现类上标注@Adaptive,或者接口方法也没有@Adaptive这里会报错
            return loader.getAdaptiveExtension();
        }
    }

    return null;
}

可见。依赖注入,要么不注入,要么注入的就是自适应扩展实现类。

// 静态方法也是可以正常注入的
public static void setDog(AnimalService animalService) {
    System.out.println(animalService.getClass().getName());
}
// 与方法的返回值也没有关系。有关系的是方法必须是public,和参数必须是一个
public String setDog(AnimalService animalService) {
    System.out.println(animalService.getClass().getName());
    return "";
}
// setCat和setDog注入的都是同一个study.rui.dubbo.AnimalService$Adaptive对象。(为什么?你想想上面的分析过程)
// set后面的属性名要是配置key。不是配置key的话也不会注入
public void setCat(AnimalService animalService) {
    System.out.println(animalService.getClass().getName());
}

再来看看包装类注入。

在createExtension方法中:

this.injectExtension(instance);
// 在方法set的依赖注入完成之后,紧接着来包装类的注入
Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
Class wrapperClass;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
    // 遍历所有的包装类,然后通过包装类的有参构造函数创建对象
    // 继续injectExtension,完成包装类里面的set方法注入,将包装类对象赋值给instance
    for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
        wrapperClass = (Class)var5.next();
    }
}
// 可以看出,如果存在包装类,这里返回的就是包装类实例了
return instance;

通过代码:wrapperClass.getConstructor(this.type).newInstance(instance),是不是可以猜测:包装类的特征了,要有一个有参构造函数,参数的类型就是接口的类型。

包装类是从cachedWrapperClasses集合里面取的,那是什么时候被放进去的呢?在loadClass中:

// 具体看isWrapperClass
else if (this.isWrapperClass(clazz)) {
    Set<Class<?>> wrappers = this.cachedWrapperClasses;
    if (wrappers == null) {
        this.cachedWrapperClasses = new ConcurrentHashSet();
        wrappers = this.cachedWrapperClasses;
    }

    wrappers.add(clazz);
}

 

// 有一个接口实现类,它有个构造函数,构造函数只有一个接口类型的参数
private boolean isWrapperClass(Class<?> clazz) {
    try {
        // 如果不存在,这个方法会报异常
        clazz.getConstructor(this.type);
        return true;
    } catch (NoSuchMethodException var3) {
        return false;
    }
}

█ 总结 ——————————————————————★

通过getExtension获取扩展实现类的时候,如果有包装类,就将实际的实现类包装进包装类里,将包装类的实例返回。通过getAdaptiveExtension和getActivateExtension就没有这样的处理逻辑。

set方法注入和包装类注入的区别就是,set方法注入返回的是实际实现类本身,包装类注入的话,返回的是包装类实例。

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页