如何在filter等dubbo自管理组件中注入spring的bean

1.抛出问题

dubbo的设计思路是微内核+插件,Filter等插件被dubbo创建,而不是被spring创建。
Filterspring两不相认,如果想在Filter中使用被spring管理的对象,注入spring的bean,怎么办?

2.先给出结论

Filter中新建一个setter方法。此方法名称形如setAbc,有且仅有一个参数。
spring上下文中定义一个名为“abc”的bean,类型要对。
如此即可实现,在Fliter被实例化后,此setter被调用,传入名为“abc”的bean(一定要类型正确,名字对上更好,详细算法见3.2节结尾处)。
(4.章介绍另一种机制,spring利用instrumentationload-time-weaver令非spring管理的对象也能使用spring基础设施,开阔思路,不建议用)

3.源码分析

这部分尽可能从现象到原因探索。

  1. 插件实例化后可被调setter,参数从objectFactory中来
  2. objectFactoryExtensionFactory接口类型的,SpringExtensionFactory是此接口的一个实现类
  3. SpringExtensionFactory在使用dubbo-spring时被设置好

3.1.插件实例化后可被调setter,参数从objectFactory中来

Filter中的setter可被dubbo调起,在springApplicationContext中按名字查找并塞进一个bean。
Filter等插件被按SPI机制管理,com.alibaba.dubbo.common.extension.ExtensionLoader<T>public T getExtension(String name)函数被用于加载特定的插件,其中有一层缓存,真正的创建过程在private T createExtension(String name)函数中。createExtension(...)函数源码如下

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

可见其中的主要步骤:

  1. 调用ClassnewInstance()方法,实例化插件
  2. 调用ExtensionLoader<T>injectExtension(T instance),从objectFactory中拿一个bean,调用插件的setter方法
  3. 处理一下wrapper

再看injectExtension(...)函数源码:

private T injectExtension(T instance) {
    try {
        if (objectFactory != 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 property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

可见其中的主要步骤:

  1. 遍历方法
  2. 如果是setter(即,方法名以“set”开头,有1个参数,是public
  3. 得到属性名,从objectFactory中得到bean
  4. 调用setter

3.2.objectFactory是ExtensionFactory接口类型的,SpringExtensionFactory是此接口的一个实现类

属性private final ExtensionFactory objectFactory在私有构造函数中被初始化:

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

接口ExtensionFactory中只有一个方法,见源码:

@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     *
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);

}

按照dubboSPI机制的惯例,见META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory内容:

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

关注“spring”名称对应的SpringExtensionFactory,其源码片段:

public <T> T getExtension(Class<T> type, String name) {
    for (ApplicationContext context : contexts) {
        if (context.containsBean(name)) {
            Object bean = context.getBean(name);
            if (type.isInstance(bean)) {
                return (T) bean;
            }
        }
    }

    logger.warn("No spring extension(bean) named:" + name + ", try to find an extension(bean) of type " + type.getName());

    for (ApplicationContext context : contexts) {
        try {
            return context.getBean(type);
        } catch (NoUniqueBeanDefinitionException multiBeanExe) {
            throw multiBeanExe;
        } catch (NoSuchBeanDefinitionException noBeanExe) {
            if (logger.isDebugEnabled()) {
                logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
            }
        }
    }

    logger.warn("No spring extension(bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");

    return null;
}

可见其中查找逻辑:

  1. 遍历ApplicationContext
  2. 先按名字找bean,要求类型必须正确
  3. 如果上一步找不到,就不考虑此名字了。改为仅按类型找,要求比类型bean必须是唯一的

3.3.SpringExtensionFactory在使用dubbo-spring时被设置好

查找SpringExtensionFactory在哪里被调用,仅ReferenceBeanServiceBean,这两个类是使用dubbo-spring一定绕不开的。
涉及SpringExtensionFactory的逻辑相似,ReferenceBeanServiceBean均实现了ApplicationContextAware,回调方法中有

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    SpringExtensionFactory.addApplicationContext(applicationContext);
}

ServiceBean的此方法中多了增加监听器的功能)
可见其中调用了SpringExtensionFactory的静态方法public static void addApplicationContext(ApplicationContext context)

4.另一种做法

核心思路是利用instrumentationload-time-weaver
启动时加入-javaagent参数,在自定义Filter上加@Configurable注解。
不建议的理由主要是需要修改启动脚本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容