SpringBoot集成原生RabbitMQ中遇到的问题

由于项目与其他系统集成,数据交互采用MQ队列形式.在帮助老系统编写MQ工具类时发现,接收方出现异常后,接收方会与MQ服务器断开连接.必须重新连接.在生产环境很不稳定.使用守护线程思想实现异常断电重连. 代码如下:

(一) MQ工具类代码

public class MQUtils {

   private static ExecutorService service = Executors.newFixedThreadPool(10);

   private static Logger logger = Logger.getLogger(MQUtils.class);

   /**
    * @return Connection 连接对象
    * @throws Exception
    *             IO异常,连接超时异常
    */
   public static Connection getConnection() throws Exception {
      logger.info("开始读取MQ服务器配置信息");
      Properties properties = new Properties();
      // 使用InPutStream流读取properties文件
      InputStream in = MQUtils.class.getClassLoader().getResourceAsStream("config-mq.properties");
      properties.load(in);
      in.close();
      String host = properties.getProperty("host");
      String port = properties.getProperty("port");
      String username = properties.getProperty("username");
      String password = properties.getProperty("password");

      ConnectionFactory factory = new ConnectionFactory();
      factory.setRequestedHeartbeat(20);
      factory.setHost(host);
      factory.setPort(Integer.parseInt(port));
      factory.setUsername(username);
      factory.setPassword(password);
      factory.setSharedExecutor(service);

      // 网络异常自动连接恢复
      factory.setAutomaticRecoveryEnabled(true);
      // 每10秒尝试重试连接一次
      factory.setNetworkRecoveryInterval(20);
      // 创建连接
      Connection connection = factory.newConnection();
      logger.info("与MQ服务器连接创建成功");
      return connection;
   }

   private static Integer counnt = 0;

   /**
    * 向MQ发送JSON数据 (点对点模式)
    *
    * @param connection
    *            连接对象
    * @param queueName
    *            队列名称
    * @param JSON
    *            传递数据
    */
   public static void sendByDeclare(String queueName, String JSON) {
      try {
         Connection connection = getConnection();
         Channel channel = connection.createChannel();
         channel.queueDeclare(queueName, true, false, false, null);
         channel.basicPublish("", queueName, null, JSON.getBytes());
         logger.info("生产者启动,当前模式为[[点对点模式]]");
         logger.info("生产者向 队列 [" + queueName + "] 发送消息成功");
         channel.close();
         connection.close();
      } catch (Exception e) {
         e.printStackTrace();
      } 
   }

   /**
    * 从MQ中取出JSON数据 (点对点模式)
    * 
    * @param connection
    *            连接对象
    * @param queueName
    *            队列名称
    * @return String
    */
   public static void receiveByDeclare(EnhanceConsumer consumer) {
      try {
         Channel channel = getConnection().createChannel();
         channel.queueDeclare(consumer.getQueue(), true, false, false, null);
          logger.info(" 消费者启动,当前模式[[点对点模式]] : 等待接收队列["+ consumer.getQueue() +"]消息");
         channel.basicConsume(consumer.getQueue(), true, consumer);
         channel.addShutdownListener(new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException e) {
               logger.error("MQ服务器异常");
            }
         });
         //开启守护线程,如果接收方出现异常,重新连接MQ服务器
         new CheckConsumerDownThread(consumer, channel).run();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /**
    * 向MQ发送JSON数据 (广播模式)
    * 
    * @param connection
    * @param queueName
    * @param JSON
    */
   public static void sendByFanout(String exchangeName, String JSON) {
      try {
         Connection connection = getConnection();
         Channel channel = connection.createChannel();
         channel.exchangeDeclare(exchangeName, "fanout");
         channel.basicPublish(exchangeName, "", null, JSON.getBytes());
         logger.info("生产者启动,当前模式为[[广播模式]]");
         logger.info("生产者向交换机 [" + exchangeName + "] 发送消息成功");
         channel.close();
         connection.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /**
    * 从MQ中取出JSON数据 (广播模式)
    * 
    * @param consumer
    */
   public static void receiveByFanout(EnhanceConsumer consumer) {
      try {
         Channel channel = getConnection().createChannel();
         channel.exchangeDeclare(consumer.getExchange(), "fanout");
         channel.queueBind(consumer.getQueue(), consumer.getExchange(), "");
         logger.info("已将队列[[" + consumer.getExchange() + "]]与队列[[" + consumer.getQueue() + "]]进行绑定");
         logger.info(" 消费者启动,当前模式为[[广播模式]] : 等待接收队列[" + consumer.getQueue() + "]消息");
         channel.basicConsume(consumer.getQueue(), true, consumer);
         channel.addShutdownListener(new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException e) {
               logger.error("MQ服务器异常");
            }
         });
         new CheckConsumerDownThread(consumer, channel).run();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

public interface EnhanceConsumer extends Consumer {

     String getQueue() ;

     String getExchange();

}
public class UltimateConsumer implements EnhanceConsumer {

     String queue;

     String exchange;

      public UltimateConsumer(String queue) {
        this.queue = queue;
    }

    public UltimateConsumer(String queue,String exchange) {
        this.queue = queue;
        this.exchange = exchange;
    }

    public String getExchange() {
        return exchange;
    }

    public void setExchange(String exchange) {
        this.exchange = exchange;
    }

    @Override
    public String getQueue() {
        return queue;
    }

    public void setQueue(String queue) {
        this.queue = queue;
    }

    @Override
    public void handleConsumeOk(String s) {

    }

    @Override
    public void handleCancelOk(String s) {

    }

    @Override
    public void handleCancel(String s) {

    }

    @Override
    public void handleShutdownSignal(String s, ShutdownSignalException e) {

    }

    @Override
    public void handleRecoverOk(String s) {

    }

    @Override
    public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] body){
    }
}

public class FullAmountConsumer extends UltimateConsumer  {

    private static Logger logger = Logger.getLogger(FullAmountConsumer.class);

    public FullAmountConsumer(String queue) {
        super(queue);
    }

    public FullAmountConsumer(String queue, String exchange,T service) {
        super(queue, exchange);
    }

    @Override
    public void handleDelivery(String s, Envelope envelope, AMQP.BasicProperties basicProperties, byte[] body) {
        try {
            MqJson mqJson = JSONUtils.jsonToObject(new String(body, "UTF-8"), MqJson.class);
            logger.info("接收到队列数据");
            if (mqJson.getType().equals("全量表")){
                logger.info("确认为全量表接收数据");
                ShareFullAmoutDto shareFullAmoutDto = JSONUtils.jsonToObject(mqJson.getData(), ShareFullAmoutDto.class);
                ShareFullAmoutService shareFullAmoutService =ApplicationContextProvider.getBean("shareFullAmoutService",ShareFullAmoutService.class);
                shareFullAmoutService.create(shareFullAmoutDto);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

将接口进行集成封装,实现接口中的handleDelivery,编写业务代码.

(二) 接收方需要在项目启动时开启MQ接收数据

@Component
public class RabbitListener {

    @Value("${mq.queue.name}")
    private  String queueName;

    private static ExecutorService singleThreadExecutor  = Executors.newSingleThreadExecutor();

    private static final String kery = "key";

    @PostConstruct
   public void receiverMq(){
        synchronized (kery){
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    MQUtils.receiveByDeclare(new FullAmountConsumer(queueName));
                }
            });
        }
   }
}

在测试中发现,业务Service无法注入到FullAmountConsumer实现类中.原因是开启多线程后,两个线程并没ApplicationContext上下文中,到时无法注入业务service,如果强行new,因为没有统一的上下文,后续的repository等对象也是null.

解决办法:使用工具,获得主线程上下文对象,从中获取业务对象service,进行业务操作.

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    private ApplicationContextProvider(){}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public  static <T> T getBean(String name,Class<T> aClass){
        return context.getBean(name,aClass);
    }
}

获取到的业务对象service为主线程的service,就可以正常进行业务操作了.


你可能感兴趣的:(SpringBoot,RabbitMQ)