SpringMVC + RabbitMQ实战(XML配置rabbit,监听多条队列)详解编程语言

一、为什么使用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

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论