由于项目与其他系统集成,数据交互采用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,就可以正常进行业务操作了.