SpringBoot与消息队列

本文介绍了SpringBoot如何使用JMS(ActiveMQ)、AMQP(RabbitMQ)和KafKa进行消息收发。详细讲解了各个组件的配置、消息转换器的使用,并提供了代码示例。内容涵盖JMS的基本概念、AMQP与JMS的区别、RabbitMQ的发送与接收、KafKa的集成及监听消息方式。

Spring与消息队列

一、SpringBoot使用JMS

1、什么是JMS

JMS是java的一个标准,定义了使用消息代理的通用API,相当于JDBC,让java使用基于消息的异步通讯更加简单。

2、使用ActiveMQ收发消息

控制台地址(http://localhost:8161/admin/)

producer端:

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

编写配置:

spring:
  #使用的消息队列类型
  activemq:
    #链接消息代理的协议ip和端口
    broker-url: tcp://localhost:61616
    #链接代理的用户名密码
    user: admin
    password: admin
    #是否开启内存代理 如果不是同一个程序收发消息 则选择false默认true
    in-memory: false
  #jms设置
  jms:
  	#模板设置
    template:
      #设置默认主题
      default-destination: testmq

编写代码:

@RestController
public class producerController {
    //依赖注入JmsTemplate
    @Autowired
    private JmsTemplate jmsTemplate;

    @PostMapping(value = "/message")
    public ResponseEntity<String> message(@RequestParam("msg") String msg){
        //使用JmsTemplate的send方法发送消息
        jmsTemplate.send((session)->{
            return session.createObjectMessage(msg);
        });
        return ResponseEntity.ok(msg);
    }
}

这里send方法有很多重载,使用默认的send方法则将消息发送到默认主题,也就是配置文件中配置的默认主题。也可以自行指定主题。

consumer端:

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

编写配置:

spring:
  activemq:
    broker-url: tcp://localhost:61616
    user: admin
    password: admin

编写代码:

@Service
public class consumerService {
    @JmsListener(destination = "testmq")
    public void getMessage(String msg){
        System.out.println("--->" + msg);
    }
}

这里使用的是监听模式(推送模式)来收取消息,也可以编码手动接收(拉取模式)来接收消息,按照需求进行选择。拉去模式是消息驱动的,只需要在接收消息的方法上添加@JmsListener注解,设置感兴趣的topic即可。

3、遇到的问题

1、这里需要注意,如果监听的方法有返回值,则会抛出异常,无法寻找到主题。异常信息如下。

Failed to send reply with payload [test]; nested exception is javax.jms.InvalidDestinationException: Cannot determine response destination: Request message does not contain reply-to destination, and no default response destination set.

2、如果使用send方法发送领域对象,接收端需要手动进行转换,转换之前需要在容器中注入MessageConverter(springboot2.5.4中居然没有自动注入)。

3、使用SimpleMessageConverter进行转换时,转换的对象必须和发送时的对象全限定名一样,可以将领域对象打包发送到mvn仓库再进行引入。

4、如何使用send方法发送一个对象:

首先选择要使用的消息转换器并进行配置,这里使用MappingJackson2MessageConverter。

@Bean
public MappingJackson2MessageConverter messageConverter(){
    //创建新的消息转换器
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    //设置标识类型的属性->也就是添加一个属性到message里面用来标识类型
    converter.setTypeIdPropertyName("_typeId");

    //设置相应属性值对应的转换类型 也就是当_typeId = user的时候转换为User对象
    Map<String,Class<?>> typeMapping = new HashMap<>();
    typeMapping.put("user", User.class);
    //将值映射属性添加到消息转换器中
    converter.setTypeIdMappings(typeMapping);
    return converter;
}

producer:

@PostMapping(value = "/message")
public ResponseEntity<User> message(@RequestBody User user){
    //需要将对象类型转换为json 因为这个转换器只支持俩种类型的消息 Text和Byte
    ObjectMapper objectMapper = new ObjectMapper();
    jmsTemplate.send((session)->{
        //创建消息
        Message message = null;
        try {
            //转为json字符串
            message = session.createTextMessage(objectMapper.writeValueAsString(user));
        }catch(Exception e){
            e.printStackTrace();
        }
        //设置消息头 这一步很重要 这里的值和要转换的类型值相同为user
        message.setStringProperty("_typeId", "user");
        return message;
    });
    return ResponseEntity.ok(user);
}

consumer:

@Service
public class consumerService {
    @Autowired
    private MappingJackson2MessageConverter converter;
	
    //接收消息进行转换即可
    @JmsListener(destination = "testmq")
    public void getMessage(Message message) throws JMSException {
        System.out.println("--->" + converter.fromMessage(message));
    }
}

5、使用SimpleMessageConverter发送一个对象:

首先如果要使用SimpleMessageConverter发送一个对象的话,同样需要将对象转换为json,如果不进行转换的话,则无法进行类型匹配。

producer:

@PostMapping(value = "/message")
public ResponseEntity<User> message(@RequestBody User user){
    ObjectMapper objectMapper = new ObjectMapper();
    jmsTemplate.send((session)->{
        Message message = null;
        try {
            message = session.createTextMessage(objectMapper.writeValueAsString(user));
        }catch(Exception e){
            e.printStackTrace();
        }
        message.setStringProperty("_typeId", "user");
        return message;

    });
    return ResponseEntity.ok(user);
}

consumer:

@JmsListener(destination = "testmq")
public void getMessage(Message message) throws JMSException {
    System.out.println("--->" + converter.fromMessage(message));
}

6、对于converterAndSend和receiveAndConverter来说,领域对象类型也需要一致,或者使用MappingJackson2Converter进行转换,设置_typeId = user -> User.class。

7、处理消息头:

// 设置消息头
jmsTemplate.convertAndSend(user, (message)->{
    message.setStringProperty("head","head msg");
    return message;
});

// 获取消息头
@JmsListener(destination = "testmq")
public void getMessage(Message message) throws JMSException {
    System.out.println(message.getStringProperty("head"));
    System.out.println(converter.fromMessage(message));
}

4、内置的消息转换器

消息转换器功能
MappingJackson2MessageConverter使用Jackson2库实现消息与JSON格式之间的相互转换。
MarshallingMessageConverter使用JAXB库实现消息和XML格式之间的相互转换。
MessagingMessageConverter使用底层的MessageConverter实现消息的转换。
SimpleMessageConverter实现String->TextMessage,byte[]->BytesMessage,Map->MapMessage以及Serializable->ObjectMessage之间的转换。

二、SpringBoot使用AMQP

1、什么是AMQP

JMS和AMQP的区别:

JMS:通常而言提到JMS(Java MessageService)实际上是指JMS API。JMS是由Sun公司早期提出的消息标准,旨在为java应用提供统一的消息操作,包括create、send、receive等。JMS已经成为Java Enterprise Edition的一部分。从使用角度看,JMS和JDBC担任差不多的角色,用户都是根据相应的接口可以和实现了JMS的服务进行通信,进行相关的操作。

AMQP:AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。意味着我们可以使用Java的AMQP provider,同时使用一个python的producer加一个rubby的consumer。从这一点看,AQMP可以用http来进行类比,不关心实现的语言,只要大家都按照相应的数据格式去发送报文请求,不同语言的client均可以和不同语言的server链接。

2、使用RabbitMQ收发消息

控制台地址(http://localhost:15672/#/)

producer端:

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

编写配置:

spring:
  #设置rabbitmq
  rabbitmq:
    #rabbitmq地址
    addresses: 127.0.0.1
    #监听端口
    port: 5672
    #用户名
    username: guest
    #密码
    password: guest

编写代码:

@RestController
public class producerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping(value = "/message")
    public ResponseEntity<User> message(@RequestBody User user){
        MessageConverter converter = rabbitTemplate.getMessageConverter();
        rabbitTemplate.send("queue", converter.toMessage("message", new MessageProperties()));
        return ResponseEntity.ok(user);
    }
}

consumer端:

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

编写配置:

spring:
  #与生产者相同
  rabbitmq:
    addresses: 127.0.0.1
    port: 5672
    username: guest
    password: guest

编写代码:

@Service
public class consumerService {
    @RabbitListener(queues = {"queue"})
    public void getMessage(String message) {
        System.out.println(message);
    }
}

这里需要设置queues属性,可能是需要监听的队列名称,同时还需要在RabbitMQ中创建对应的队列才可以,也不知道有没有别的什么办法=。=。

可以使用RabbitTemplate发送实体类对象,不需要像JmsTemplate那样手动将实体类对象转换为JSON再进行发送,只需要配置Jackson2JsonMessageConverter即可,Jackson2JsonMessageConverter可以帮助手动转换实体类。

以上为SpringBoot操作RabbitMQ调用send方法发送简单消息的例子,接下来我们来看调用convertAndSend方法发送实体类作为消息的例子。

producer:

添加依赖:

编写配置:

编写代码:

// 配置类
@SpringBootApplication
public class ProducerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ProducerApplication.class, args);
	}

	@Bean
	public Jackson2JsonMessageConverter messageConverter(){
		return new Jackson2JsonMessageConverter();
	}
}

// 发送消息的Controller
@RestController
public class producerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping(value = "/message")
    public ResponseEntity<User> message(@RequestBody User user){
        rabbitTemplate.convertAndSend("queue", user);
        return ResponseEntity.ok(user);
    }
}

consumer:

添加依赖:

编写配置:

编写代码:

// 配置类
@SpringBootApplication
public class ConsumerApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(ConsumerApplication.class, args);
	}

	@Bean
	public Jackson2JsonMessageConverter messageConverter(){
		return new Jackson2JsonMessageConverter();
	}
}

// Service组件
@Service
public class consumerService {
    @RabbitListener(queues = {"queue"})
    public void getMessage(User user) {
        System.out.println(user);
    }
}

在俩种收发消息的方式中,都可以对消息头进行设置,send方法在构建消息时使用toMessage进行设置,convertAndSend方法通过方法的第三个参数进行设置。

关于一些其他的设置:

spring:
	rabbitmq:
		template:
			#可以设置默认使用的exchange
			exchange: xxx
			#可以设置默认使用的routing-key
			routing-key: key
			#设置默认的接收超时时间
			reveive-timeout: xms

3、内置的消息转换器

消息转换器功能
Jackson2JsonMessageConverter使用Jackson2库实现对象和JSON的相互转换。
SimpleMessageConverter同ActiveMQ的SimpleMessageConverter。
SerializerMessageConverter使用Spring的Serializer和Deserializer转换String和任意种类对象。

三、SpringBoot使用KafKa

1、SpringBoot使用KafKa

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

SpringBoot居然没有为KafKa制作专门的starter依赖=。=,还需要手动引入,不过还好,引入该依赖之后就会触发SpringBoot的自动配置。

producer:

编写配置:

spring:
  kafka:
  	#kafka服务地址 可以配置多个
    bootstrap-servers:
      - 127.0.0.1:9092
    #默认监听主题
    template:
      default-topic: test

编写代码:

@RestController
public class producerController {
    @Autowired
    private KafkaTemplate kafkaTemplate;

    @PostMapping(value = "/message")
    public ResponseEntity<User> message(@RequestBody User user) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        kafkaTemplate.sendDefault(objectMapper.writeValueAsString(user));
        return ResponseEntity.ok(user);
    }
}

#_#不知道为啥,如果不用打包的实体类总是会报错,只能暂时这样传递实体类了。

consumer:

编写配置:

spring:
  kafka:
    bootstrap-servers:
      - 127.0.0.1:9092
    template:
      default-topic: test

编写代码:

@Service
public class consumerService {
    //设置分组id 设置监听的主题
    @KafkaListener(groupId = "topic", topics = {"test"})
    public void getMessage(String user) {
        System.out.println(user);
    }
}

好吧,消息队列还是得好好研究一下 #_#。

2、Kafka总结

send方法:

send(String topic,Integer partition,K key,V data);

sendDefault(Integer partition,K key,V data);

kafka只有发送消息的方法,没有receive方法,kafka只支持使用监听的方式来接收消息。上面的send()方法可以切换主题,sendDefault()方法会使用默认主题发送消息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值