基于springboot实现多消费者监听注解MultiJmsListener

前置说明
以spring-boot-starter-activemq为例子来说明
springboot提供JmsListener用以支持对消息的监听。但有些时候为了提高消费效率,需要对同一个queue或者topic使用多个监听器进行消费。而通过JmsListener注解,只能支持单线程消费,如果要做多个消费者,需要多次使用JmsListener,代码如下:
/**
 * 单线程监听Queue
 * @param message
 */
@JmsListener(destination = "test.queue1")
public void consumer4(String message) {
    System.out.println("==queue消息:" + message);
}
/**
 * 单线程监听Queue
 * @param message
 */
@JmsListener(destination = "test.queue1")
public void consumer5(String message) {
    System.out.println("==queue消息:" + message);
}

本文基于springboot-2.1.4自定义注解MultiJmsListener
本文采用spring-boot-starter-activemq整合activeMQ
源码清单
MultiJmsListener注解
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MessageMapping
public @interface MultiJmsListener {
    //用以定义监听器beanName的前缀(保留字段)
    String prefixId() default "";

    String destination();

    //定义监听线程的个数
    int num() default 1;

    //定义消息的ContainerFactory,实际值参考ActiveMQConstant
    String containerFactory() default "";
}

传统消息监听器
public class ActiveMQMessageListener<T> implements MessageListener {
    private final Logger logger = LoggerFactory.getLogger(ActiveMQMessageListener.class);

    //消息监听ID
    private int id;

    //执行方法
    private Method executeMethod;

    //执行类
    private Object executeObject;

    //监听目标
    private String dest;

    private Class<T> executeClass;

    /** 有参构造器 */
    public ActiveMQMessageListener(Class<T> executeClass, Method executeMethod, int id, String dest) {
        this.executeMethod = executeMethod;
        this.dest = dest;
        this.id = id;
        init(executeClass);
        this.executeClass = executeClass;
    }

    /**
     * 初始化操作
     * @param executeClass
     */
    private void init(Class<T> executeClass) {
        try {
            this.executeObject = executeClass.newInstance();
        } catch (Exception e) {
            throw new JmsException("方法反射异常:", e);
        }
    }

    public ActiveMQMessageListener() {}

    @Override
    public void onMessage(Message message) {
        if (logger.isDebugEnabled()) {
            logger.debug("监听器[{}]监听到消息", id);
        }
        TextMessage textMessage = (TextMessage) message;
        try {
            //反射的方式调用方法,用以执行实际消费流程
            executeMethod.invoke(executeObject, textMessage.getText());
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error("消息监听异常:", e);
            }
            throw new JmsException("消息监听异常:", e);
        }
    }
 //省略掉get/set方法
 
自动配置
自动配置类,引入JmsListenerContainerFactory用以同时支持Queue和Topic
/**
 * 配置topic模式下的JmsListenerContainerFactory
 * @param activeMQConnectionFactory
 * @return
 */
@Bean(name = ActiveMQConsumerConstant.CONTAINER_FACTORY_TOPIC)
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
    DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
    //支持Topic模式
    bean.setPubSubDomain(true);
    bean.setConnectionFactory(activeMQConnectionFactory);
    //FrameworkAutoConfigureManager.addAutoConfigureBeanMap("jmsListenerContainerTopic", JmsListenerContainerFactory.class);
    return bean;
}


/**
 * 配置queue模式下的JmsListenerContainerFactory
 * @param activeMQConnectionFactory
 * @return
 */
@Bean(name = ActiveMQConsumerConstant.CONTAINER_FACTORY_QUEUE)
public JmsListenerContainerFactory<?> jmsListenerContainerQueue(ConnectionFactory activeMQConnectionFactory) {
    DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
    bean.setConnectionFactory(activeMQConnectionFactory);
    //FrameworkAutoConfigureManager.addAutoConfigureBeanMap("jmsListenerContainerQueue", JmsListenerContainerFactory.class);
    return bean;
}

/**
 * 用以支持MultiJmsListener注解的后置处理器
 * @return
 */
@Bean
public MultiJmsListenerAnnotationBeanPostProcessor multiJmsListenerAnnotationBeanPostProcessor() {
    MultiJmsListenerAnnotationBeanPostProcessor multiJmsBeanPostProcessor =
            new MultiJmsListenerAnnotationBeanPostProcessor();
    //FrameworkAutoConfigureManager.addAutoConfigureBeanMap("multiJmsListenerAnnotationBeanPostProcessor", MultiJmsListenerAnnotationBeanPostProcessor.class);
    return multiJmsBeanPostProcessor;
}

常量类
定义ActiveMQConsumerConstant常量,用以配置ContainerFactory对应的BeanName
public interface ActiveMQConsumerConstant {
    /** Queue模式下的JmsListenerContainerFactory的Bean名称 */
    String CONTAINER_FACTORY_QUEUE = "jmsListenerContainerQueue";

    /** Topic模式下的JmsListenerContainerFactory的Bean名称 */
    String CONTAINER_FACTORY_TOPIC = "jmsListenerContainerTopic";
}

 

public class MarkConstant {
    public static final String DOT = ".";
}


后置处理器
定义一个Bean后置处理器处理MultiJmsListener对应的方法
public class MultiJmsListenerAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton {

    //用以缓存方法中没有MultiJmsListener注解的
    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

    //JMS 注册机
    private JmsListenerEndpointRegistrar jmsListenerEndpointRegistrar = new JmsListenerEndpointRegistrar();

    private BeanFactory beanFactory;

    @Autowired
    public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) {
        jmsListenerEndpointRegistrar.setEndpointRegistry(endpointRegistry);
    }

    @Autowired
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        jmsListenerEndpointRegistrar.setBeanFactory(beanFactory);
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //特殊bean过滤
        if((bean instanceof AopInfrastructureBean)
                || (bean instanceof JmsListenerContainerFactory)
                || (bean instanceof JmsListenerEndpointRegistry)) {
            return bean;
        }

        //获取bean对象的最终类型
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        if (this.nonAnnotatedClasses.contains(targetClass)) {
            return bean;
        }

        //选出该bean中包含MultiJmsListener注解的方法
        Map<Method, MultiJmsListener> annotatedMethods = MethodIntrospector.selectMethods(targetClass, new MethodIntrospector.MetadataLookup<MultiJmsListener> () {
            @Override
            public MultiJmsListener inspect(Method method) {
                return AnnotationUtils.getAnnotation(method, MultiJmsListener.class);
            }
        });

        // 若当前类中未找到含该注解的方法,直接return bean
        if (annotatedMethods.isEmpty()) {
            this.nonAnnotatedClasses.add(targetClass);
            return bean;
        }

        //对每个方法做处理,添加监听
        annotatedMethods.forEach((method, multiJmsListener) -> processMultiListener(multiJmsListener, method, targetClass));

        //返回bean对象
        return bean;
    }

    /*
     * 提交监听器到进程
     * @param multiJmsListener 注解
     * @param method 方法
     * @param targetClass bean对应的class类
     * @param <T>
     */
    private <T> void processMultiListener(MultiJmsListener multiJmsListener, Method method, Class<T> targetClass) {
        //线程数
        int threadNum = multiJmsListener.num();
        if (threadNum <= 0) {
            numthreadNum= 1;
        }

        //for循环
        for (int i = 1; i <= threadNum; i ++) {
            //多少个线程数,开启多少个监听
            ActiveMQMessageListener activeMQMessageListener = new ActiveMQMessageListener<>(targetClass, method, i, multiJmsListener.destination());
            //将监听器注册到JMSEndpointRegistrar
            register(multiJmsListener, activeMQMessageListener);
        }
    }

    /*
     * 向JMSEndpointRegistrar注册Endpoint
     * @param activeMQMessageListener 消息监听器
     */
    private void register(MultiJmsListener multiJmsListener, ActiveMQMessageListener activeMQMessageListener) {
        SimpleJmsListenerEndpoint simpleJmsListenerEndpoint = new SimpleJmsListenerEndpoint();
        //endpointId = 类名+方法名+id
        simpleJmsListenerEndpoint.setId(buildEndpointId(activeMQMessageListener));
        simpleJmsListenerEndpoint.setDestination(activeMQMessageListener.getDest());
        simpleJmsListenerEndpoint.setMessageListener(activeMQMessageListener);
        jmsListenerEndpointRegistrar.setBeanFactory(this.beanFactory);

        //不同的模式,引用的containerFactory不同
        String containFactory = multiJmsListener.containerFactory();
        if (StringUtils.isBlank(containFactory)) {
            //默认使用Queue
            containFactory = ActiveMQConsumerConstant.CONTAINER_FACTORY_QUEUE;
        }
        JmsListenerContainerFactory<?> factory = (JmsListenerContainerFactory)this.beanFactory.getBean(containFactory, JmsListenerContainerFactory.class);
        jmsListenerEndpointRegistrar.setContainerFactoryBeanName(ActiveMQConsumerConstant.CONTAINER_FACTORY_QUEUE);
        jmsListenerEndpointRegistrar.registerEndpoint(simpleJmsListenerEndpoint, factory);

    }

    /*
     * 生成端点Endpoint的ID
     * @param activeMQMessageListener
     * @return
     */
    private String buildEndpointId(ActiveMQMessageListener activeMQMessageListener) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(activeMQMessageListener.getExecuteClass().getName())
                .append(MarkConstant.DOT)
                .append(activeMQMessageListener.getExecuteMethod().getName())
                .append(activeMQMessageListener.getId());
        return stringBuilder.toString();
    }

    /**
     * 在类初始化后,执行后续操作
     */
    @Override
    public void afterSingletonsInstantiated() {
        //执行registrar的后置操作
        jmsListenerEndpointRegistrar.afterPropertiesSet();
    }

    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }
}

测试

@Service
public class AlarmMqCustomer {
@Autowired
private AlarmMapper alarmMapper;

//注解示例代码
@MultiJmsListener(destination = "test.queue0", num = 4)
public void consumer(String message) {
    System.out.println("==============================");
    System.out.println(message);
    System.out.println("==============================");
}
}
输出结果
输出片段示例
测试queue
==============================
id=2
==============================
测试queue
==============================
id=1
==============================
测试queue
==============================
id=3
==============================
测试queue
==============================

如果想把数据存入数据库,需要注意的地方是在

AlarmMqCustomer类里是不能通过@Autowired注入对象的,必须通过手动注入。

 

例如:

public class AlarmMqCustomer {

    @Autowired
    private AlarmMapper alarmMapper;

    //@JmsListener(destination = "foo.bar")
    @MultiJmsListener(destination = "foo.bar",num=10)
    public void getQueueMessage(String message) {
        System.out.println("队列");
        doMessage(message);
    }

    public void doMessage(String message) {
        Map<String, Object> map = new HashMap<>();
        //逻辑处理
        。。。
        if(alarmMapper == null ){
            alarmMapper = (AlarmMapper) SpringContextUtil.getBean("alarmMapper");
        }
        alarmMapper.insertFaultAlarmInfo(map);
    }

    
}

 

springboot实现手动获取bean对象,首先启动类得实现接口ApplicationListener<ContextRefreshedEvent> 并实现方法

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
   SpringContextUtil.setApplicationContext(event.getApplicationContext());
}

SpringContextUtil 类代码

/**
 * 获取spring容器,以访问容器中定义的其他bean
 *  
 */
public class SpringContextUtil {
    //spring上下文
    private static ApplicationContext applicationContext;

    /**
     *实现ApplicationContextAware接口的回调方法,设置上下文环境
     *@param applicationContext
     *     
     */
    public static void setApplicationContext(ApplicationContext applicationContext) {
        if (null == SpringContextUtil.applicationContext) {
            SpringContextUtil.applicationContext = applicationContext;
        }
    }


    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     *通过name获取 Bean.
     *
     *@param name
     *@return
     *      
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);

    }

    /**
     *通过name获取 Bean.
     *
     *@param clazz
     *@return
     *      
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     *通过name,以及Clazz返回指定的Bean
     *
     *@param name
     * @param clazz
     *@return
     *      
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}

说明
id不同表示监听线程不同
总结
方法层级的注解实现,常见的有两个方式,一种是直接通过AOP切面去实现,还有一种是通过BeanPostProcessor,在Bean初始化后,做后置处理。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值