一、为什么使用SpringMVC整合RabbitMQ?
由于springboot项目rabbit-rfc与sapjco3.jar的不兼容性,具体表现为:在Linux上执行java -jar rabbit-rfc.jar时,出现以下错误:
It is not allowed to rename or repackage the original archive “sapjco3.jar”
这是因为springboot项目依赖jar包的默认打包规则即为Maven的默认打包规则:
%artifactId%-%version%.jar
所以当sapjco3在maven中的dependency如下时:
<dependency>
<groupId>com.sap</groupId>
<artifactId>sapjco3</artifactId>
<version>3.0.12</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/sapjco3.jar</systemPath>
</dependency>
sapjco3.jar最终在package项目jar包时会被重新打包命名为sapjco3-3.0.12.jar,所以才会出现上述不允许重命名或重打包sapjco3.jar的错误。
具体参考stackoverflow的这个问题:
IT虾米网
解决此问题的方法之一就是不要让sapjco3.jar在打包或运行时被重命名,所以使用SpringMVC + 本地Tomcat的办法,在IntelliJ Idea 中配置为发布模式war,便可以在项目启动时在target目录中生成以项目名命名的war包。
注:后来此问题已被解决,见我的博客:《sapjco问题记录及解决办法》IT虾米网
附:IntelliJ Idea war和war exploded的区别
是选择war还是war exploded 这里首先看一下他们两个的区别:
war模式:将WEB工程以包的形式上传到服务器 ; war exploded模式:将WEB工程以当前文件夹的位置关系上传到服务器;
(1)war模式这种可以称之为是发布模式,看名字也知道,这是先打成war包,再发布;
(2)war exploded模式是直接把文件夹、jsp页面 、classes等等移到Tomcat
部署文件夹里面,进行加载部署。因此这种方式支持热部署,一般在开发的时候也是用这种方式。
(3)在平时开发的时候,使用热部署的话,应该对Tomcat进行相应的设置,这样的话修改的jsp界面什么的东西才可以及时的显示出来。
二、项目实战
项目架构参考我的博文《RabbitMQ中间件方案:BPM-SAP的应用解耦和用户并发量控制》、SpringMVC的其他配置参考《SpringMVC + Spring + Hibernate实战(通用配置)》。
以下两个项目需要引入必要的依赖:
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
版本号没必要拘泥,进入maven repository官网,引入最新版本的依赖即可。
项目一:api-rabbit-mvc,生产requestQueues,消费responseQueues
创建5条request_queue,并监听5条response_queue,监听类(消费者,相当于springboot-amqp [email protected])为ResponseMapConsumer,监听方法为listenResponseMap([email protected])[email protected]e i(1 ~ 5),并监听response_queue 1 – 5,只要监听到response queue i,能通过UUID和序列号i的对应校验,就将监听得到的SAP响应Json数据返回给前端或BPM的HttpClient。
applicationContext.xml
说明:
首先使用context:component-scan扫描到com.jake下所有被上下文注解标记的类,如(@[email protected]@[email protected]),context:annotation-config用于开启上下文注解,与context:component-scan功能重复。其中,rabbit:queue用于自动创建队列;message-converter使用了Jackson2JsonMessageConverter类,用于传递Json数据(本项目中暂未使用);rabbit-listener中使用queue-names属性监听了多条队列,队列名之间以逗号分隔;ref为消费者类名在上下文注解中的别名(默认类名首字母小写),method为其中处理监听队列携带数据的具体方法(参数与队列携带参数类型相同)。由于没有使用rabbit:binding标签,所以所有创建的rabbit消息队列都被绑定到RabbitMQ服务器默认的直连交换机AMQP Default上。
<context:component-scan base-package="com.jake"/>
<!--<context:annotation-config/>-->
<rabbit:connection-factory
id="rabbitConnectionFactory" host="127.0.0.1"
username="guest" password="guest" port="5672"/>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:queue id="requestQueue1" name="REQUEST_QUEUE1"/>
<rabbit:queue id="requestQueue2" name="REQUEST_QUEUE2"/>
<rabbit:queue id="requestQueue3" name="REQUEST_QUEUE3"/>
<rabbit:queue id="requestQueue4" name="REQUEST_QUEUE4"/>
<rabbit:queue id="requestQueue5" name="REQUEST_QUEUE5"/>
<bean id="jackson2JsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory" message-converter="jackson2JsonMessageConverter"/>
<rabbit:listener-container message-converter="jackson2JsonMessageConverter">
<rabbit:listener queue-names="RESPONSE_QUEUE1, RESPONSE_QUEUE2, RESPONSE_QUEUE3, RESPONSE_QUEUE4, RESPONSE_QUEUE5"
ref="responseMapConsumer" method="listenResponseMap"/>
</rabbit:listener-container>
生产者
package com.jake.apirabbitmvc.producer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class RequestMapProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendRequestMap(String queueName, Map<String, Object> requestMap) {
rabbitTemplate.convertAndSend(queueName, requestMap);
}
}
消费者
package com.jake.apirabbitmvc.consumer;
import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class ResponseMapConsumer {
@Getter
@Setter
private Map<String, Object> responseMap;
public void listenResponseMap(Map<String, Object> responseMap) {
setResponseMap(responseMap);
}
}
控制层
调用生产者的sendRequestMap方法发送请求Json数据,调用消费者的getResponseMap方法实时获取响应Json数据。
package com.aac.apirabbitmvc.controller;
import com.aac.apirabbitmvc.constant.Queues;
import com.aac.apirabbitmvc.consumer.ResponseMapConsumer;
import com.aac.apirabbitmvc.producer.RequestMapProducer;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping(value = "/rfc")
public class BpmToRfcController {
@Autowired
private RequestMapProducer requestMapProducer;
@Autowired
private ResponseMapConsumer responseMapConsumer;
@RequestMapping(value = "/json")
public Map<String, Object> getResponseMap(@RequestBody Map<String, Object> requestMap) throws InterruptedException {
String requestID = UUID.randomUUID().toString();
int reqQNo = RandomUtils.nextInt(1, 6);
requestMap.put("requestID", requestID);
requestMap.put("reqQNo", reqQNo);
requestMapProducer.sendRequestMap(Queues.REQUEST_QUEUE + reqQNo, requestMap);
Map<String, Object> responseMap = responseMapConsumer.getResponseMap();
// [email protected],响应数据不为NULL时退出循环。
while (responseMap == null) {
Thread.sleep(100);
responseMap = responseMapConsumer.getResponseMap();
}
String responseID = (String) responseMap.get("responseID");
int resQNo = (int) responseMap.get("resQNo");
// [email protected],当标记值相同时退出循环。
while (!(StringUtils.equals(requestID, responseID) && reqQNo == resQNo)) {
Thread.sleep(100);
responseMap = responseMapConsumer.getResponseMap();
responseID = (String) responseMap.get("responseID");
resQNo = (int) responseMap.get("resQNo");
}
return responseMap;
}
}
项目二:rfc-rabbit-mvc,生产responseQueues,消费requestQueues
创建5条response_queue,并监听5条request_queue,监听类(消费者,相当于springboot-amqp [email protected])为RequestMapConsumer,监听方法为listenRequestMap([email protected])。只要监听到request_queue 1 – 5中的任意一条(假设为i),就获取其中的请求Json数据,通过其中的参数调用RFC接口,获取SAP的响应Json数据responseMap,将该Json数据发送到response_queue i(1 ~ 5)。
applicationContext.xml
<context:component-scan base-package="com.jake"/>
<!--<context:annotation-config/>-->
<rabbit:connection-factory
id="rabbitConnectionFactory" host="127.0.0.1"
username="guest" password="guest" port="5672"/>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:queue id="responseQueue1" name="RESPONSE_QUEUE1"/>
<rabbit:queue id="responseQueue2" name="RESPONSE_QUEUE2"/>
<rabbit:queue id="responseQueue3" name="RESPONSE_QUEUE3"/>
<rabbit:queue id="responseQueue4" name="RESPONSE_QUEUE4"/>
<rabbit:queue id="responseQueue5" name="RESPONSE_QUEUE5"/>
<bean id="jackson2JsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory" message-converter="jackson2JsonMessageConverter"/>
<rabbit:listener-container message-converter="jackson2JsonMessageConverter">
<rabbit:listener queue-names="REQUEST_QUEUE1, REQUEST_QUEUE2, REQUEST_QUEUE3, REQUEST_QUEUE4, REQUEST_QUEUE5"
ref="requestMapConsumer" method="listenRequestMap"/>
</rabbit:listener-container>
生产者
package com.jake.rfcrabbitmvc.producer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class RequestMapProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendResponseMap(String queueName, Map<String, Object> responseMap) {
rabbitTemplate.convertAndSend(queueName, responseMap);
}
}
消费者
package com.jake.rfcrabbitmvc.consumer;
import com.jake.rfcrabbitmvc.constant.Queues;
import com.jake.rfcrabbitmvc.producer.RequestMapProducer;
import com.jake.rfcrabbitmvc.service.BpmToSapService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class RequestMapConsumer {
@Autowired
private RequestMapProducer requestMapProducer;
@Autowired
private BpmToSapService bpmToSapService;
public void listenRequestMap(Map<String, Object> requestMap) {
Map<String, Object> responseMap = bpmToSapService.getSapJson(requestMap);
String requestID = (String) requestMap.get("requestID");
int reqQNo = (int) requestMap.get("reqQNo");
responseMap.put("responseID", requestID);
responseMap.put("resQNo", reqQNo);
requestMapProducer.sendResponseMap(Queues.RESPONSE_QUEUE + reqQNo, responseMap);
}
}
根据请求Json数据获取SAP响应Json数据的bpmToSapService.getSapJson(requestMap)
方法参考我的博客:《访问SAP统一RFC连接接口(RESTFUL风格)》
三、发布war包至Linux
在localhost使用postman测试,能够正常获取SAP的响应Json数据后,将两个项目的war包上传至Linux服务器Tomcat的webapps的目录下,bin目录执行./startup.sh,运行项目。
访问linux-server-ip:8080
可以访问Tomcat主页。
携带请求Json数据访问linux-server-ip:8080/api-rabbit-mvc/rfc/json.action
无法获取响应Json数据,分析可能的原因是当响应Json数据为null时,控制层就不返回数据。由此可以推断出api项目无法监听到rfc项目返回的Json数据。
携带请求Json数据直接访问linux-server-ip:8080/rfc-rabbit-mvc/rfc/json.action
,发现报类似以下的错误:ClassNotFound:JCoConnection does not exist
,明显缺少了sapjco3.jar和libsapjco3.so文件,百度谷歌搜索:sapjco3 linux,找到在Linux环境下配置有关sapjco的环境变量的方法。
参考博客:IT虾米网
如果已经配置好了,依然报错,那么执行source /etc/profile
使环境变量重新生效即可。
再访问linux-server-ip:8080/rfc-rabbit-mvc/rfc/json.action
就可以获取Json数据了。
四、记录SpringBoot和SpringMVC项目创建的消息队列不通用的问题
4.1 背景
[email protected](SpringMVC-XML形式)项目创建的Rabbit消息队列,那么这些消息队列就不能被SpringMVC(SpringBoot)所消费。
4.2 暂时的解决方法
目前对这个问题没有深究,暂时的解决方法是如果希望消息队列是供SpringMVC项目使用的,但却是由SpringBoot项目创建的,那么只有在RabbitMQ的后台管理页面上删除消息队列,重启Tomcat(重启SpringMVC-RabbitMQ项目)已创建供SpringMVC项目使用的消息队列。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/19594.html