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