Spring-rabbit之@RabbitListener解析

rabbitmq中消费者可使用@RabbitListener标注的方法进行处理,这里介绍下@RabbitListener解析过程。

解析类

RabbitListenerAnnotationBeanPostProcessor类用于解析RabbitListener注解,该类实现了BeanPostProcessor,Ordered,BeanFactoryAware,BeanClassLoaderAware,EnvironmentAware说明该类具有以下特性:

  1. 是一个Bean后处理,@RabbitListener标注的类(或方法所在类)必须可被Bean实例化,也就是要么在@Bean方法中手动创建实例,或者用@@Component标注。
  2. 可设定Bean初始化优先级
  3. 注入BeanFactory,Environment,可以获取Bean工厂中的Bean实例和环境变量
  4. 单例实例化后处理
  5. 解析入口方法:postProcessAfterInitialization

查找RabbitListener标注的类或方法

  1. Bean工厂类中的Bean都是通过动态代理创建的代理类,要获取目标类,先使用AopUtils
    Class<?> targetClass = AopUtils.getTargetClass(bean);
  1. 获取目标类中RabbitListener注解

有2种方式配置RabbitListener,一种使用RabbitListener标注类且使用RabbitHandler标注方法,另一种使用RabbitListener标注方法,两种模式同时存在也可以。使用RabbitHandler标注的方法和RabbitListener标注的方法都会被当做消费者的消息监听方法

    //查找RabbitListener标注的类
    Collection<RabbitListener> classLevelListeners = findListenerAnnotations(targetClass);
    
    ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                //查找RabbitListener标注的方法
                Collection<RabbitListener> listenerAnnotations = findListenerAnnotations(method);
                if (listenerAnnotations.size() > 0) {
                    methods.add(new ListenerMethod(method,listenerAnnotations.toArray(new RabbitListener[listenerAnnotations.size()])));
                }
                //存在RabbitListener标注的类
                if (hasClassLevelListeners) {
                    //查找RabbitHandler标注的方法
                    RabbitHandler rabbitHandler = AnnotationUtils.findAnnotation(method, RabbitHandler.class);
                    if (rabbitHandler != null) {
                        multiMethods.add(method);
                    }
                }
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);//查找范围是除了Object类之外的方法

处理@RabbitListener标注的方法

@RabbitListener中可使用SpEL表达式

protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean,Object adminTarget, String beanName) {
    endpoint.setBean(bean);
    endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
    //Id
    endpoint.setId(getEndpointId(rabbitListener));
    //解析队列
    endpoint.setQueueNames(resolveQueues(rabbitListener));
    //独占队列
    endpoint.setExclusive(rabbitListener.exclusive());
    //分组
    if (StringUtils.hasText(group)) {
        Object resolvedGroup = resolveExpression(group);
        if (resolvedGroup instanceof String) {
            endpoint.setGroup((String) resolvedGroup);
        }
    }
    //优先级
    String priority = resolve(rabbitListener.priority());
    if (StringUtils.hasText(priority)) {
        endpoint.setPriority(Integer.valueOf(priority));
    }
    //查找rabbitAdmin
    String rabbitAdmin = resolve(rabbitListener.admin());
    if (StringUtils.hasText(rabbitAdmin)) {
        endpoint.setAdmin(this.beanFactory.getBean(rabbitAdmin, RabbitAdmin.class));
    }
    
    //查找containerFactory
    String containerFactoryBeanName = resolve(rabbitListener.containerFactory());
    if (StringUtils.hasText(containerFactoryBeanName)) {
        factory = this.beanFactory.getBean(containerFactoryBeanName, RabbitListenerContainerFactory.class);
    }
    //将endpoint关联到containerFactory中
    this.registrar.registerEndpoint(endpoint, factory);
}

private String[] resolveQueues(RabbitListener rabbitListener) {
    String[] queues = rabbitListener.queues();
    QueueBinding[] bindings = rabbitListener.bindings();
    //queues和bindings都配置会抛出异常
    if (queues.length > 0 && bindings.length > 0) {
        throw new BeanInitializationException("@RabbitListener can have 'queues' or 'bindings' but not both");
    }
    List<String> result = new ArrayList<String>();
    if (queues.length > 0) {
        for (int i = 0; i < queues.length; i++) {
            //队列名称可为SpEL表达式,如#{myQueue},注意不能带.,如my.queue(可能会报错)
            Object resolvedValue = resolveExpression(queues[i]);
            //解析队列名称,resolvedValue可为String[],Queue,String,最终都会解析为String,存放到result中
            resolveAsString(resolvedValue, result);
        }
    }else {
        return registerBeansForDeclaration(rabbitListener);
    }
    return result.toArray(new String[result.size()]);
}

private String[] registerBeansForDeclaration(RabbitListener rabbitListener) {
    List<String> queues = new ArrayList<String>();
    if (this.beanFactory instanceof ConfigurableBeanFactory) {
        for (QueueBinding binding : rabbitListener.bindings()) {
            //声明队列
            String queueName = declareQueue(binding);
            queues.add(queueName);
            //声明Exchange和Binding
            declareExchangeAndBinding(binding, queueName);
        }
    }
    return queues.toArray(new String[queues.size()]);
}
//具体声明队列和Exchange及Binding的代码可以去看源码

处理@RabbitListener标注的类和@RabbitHandler标注的方法

解析原理同上,只是解析信息封装在MultiMethodRabbitListenerEndpoint中,上面的是MethodRabbitListenerEndpoint

这里要说明下注解中均可以使用SpEL表达式,SpEL表达式可以解析BeanFactory和Environment中(spring.rabbitmq.emptyStringArguments(多个,分隔))的数据,如我们自己创建的Queue、Exchaneg、Binding等Bean这里都可以通过表达式引用(定义好Bean名称),还比如我们使用@Queue、@Exchange、@binding配置的属性,也可以通过表达式引用,这些配置值可以写在配置文件中。

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

推荐阅读更多精彩内容