Springboot2.3.1整合ActiveMQ


Springboot2.3.1整合ActiveMQ

本文中,将初步探索ActiveMQ

整合其 点对点模式、Topic模式、虚拟主题Topic三种模式

ActiveMQ 安装请查看docker-软件安装篇

一、依赖与Yml配置

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

Yml配置

spring:
  activemq:
    #ActiveMQ通信地址
    broker-url: tcp://xxx:61616
    #账户
    user: xxxx
    #密码
    password: xxxx

这即是最简单的配置,,,咱们一步步踩坑

二、点对点模式

先定义一个公共层对象,用来消费者生产者间消息传输

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Driver implements Serializable {
    private Long id;
    private String name;
}

定义queue

@Configuration
public class EasyActiveConfig {
    @Bean
    public Queue easyQueue() {
        return new ActiveMQQueue("active_easy_queue");
    }

}

生产者

使用 JmsMessagingTemplate 进行消息发送

@Service
public class EasyProvider {
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    public void send() {
        for (int i = 0; i < 3; i++) {
            jmsMessagingTemplate.convertAndSend("active_easy_queue","123123");
        }
    }
}

消费者

使用 JmsListener 监听队列

@Component
public class EasyConsumer {
    @JmsListener(destination = "active_easy_queue")
    public void aa(String aa) {
        System.out.println("收到简单的消息" + aa);
    }
}

测试发送Json字符串 --发现是OK的

image-20200720224825700

咱们再来试试直接发送对象–改造咱们的生产者以及消费者

@Service
public class EasyProvider {
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    public void send() {
        for (int i = 0; i < 3; i++) {
            jmsMessagingTemplate.convertAndSend("active_easy_queue", new Driver((long)i, "小司机"));
        }
    }
}

@Component
public class EasyConsumer {
    @JmsListener(destination = "active_easy_queue")
    public void aa(Driver aa) {
        System.out.println("收到简单的消息" + aa);
    }
}

测试一下:

报错:

Could not convert JMS message; nested exception is javax.jms.JMSException: Failed to build body from content

Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.leilei.common.Driver!

image-20200720225143932

根据错误信息查看官网以及百度,发现ActiveMQ 其默认是不支持直接对象发送的,发送时 实质会将其序列化进行传输,但activemq默认发送对象的包是不安全的,所以,序列化会失败,导致消息发送失败

解决办法:设置包名为安全

image-20200720225450802

OK,再测一波

刚才发送失败的消息,保存到了队列中,下一次队列发送消息时,将之前未被消费的又发了出来

image-20200720225551062

三、Topic模式

topic 主题模式 与点对点模式最大的区别是 其可以进行一对多,多个消费者监听到topic,即可同时收到消息

配置

@Configuration
public class ActiveTopicConfig {
    @Bean
    public Topic topic() {
        return new ActiveMQTopic("lei_topic") ;
    }
    /**
     * springboot默认只配置queue类型消息,如果要使用topic类型的消息,则需要配置该bean
     * @param connectionFactory  factory.setPubSubDomain(true);
     * @return
     */
    @Bean
    public JmsListenerContainerFactory leiTopicFactory(ConnectionFactory connectionFactory){
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        //这里必须设置为true,false则表示是queue类型 虚拟主题(virtual则必须设为false)
        factory.setPubSubDomain(true);
        return factory;
    }
}

生产者

与简单模式一样,仅仅将之前的queue的名字替换为了topic的名字

@Service
public class    ActiveTopicProvider {
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    public void sendMessage() {
        for (int i = 0; i < 30; i++) {
        jmsMessagingTemplate.convertAndSend("lei_topic",new Driver((long)i,"司机"+i));
        }
    }
}

消费者

我这里定义了两个消费者

concurrency 消费者并发数 ,我这里暂时都定为1 一会定义多个查看区别

@Component
public class ActiveTopicConsumer {
    /**
     * destination 为定义的topic的名字
     * concurrency 消费者并发数
     * containerFactory 之前设置的JmsListenerContainerFactory bean 名字
     * @param driver
     */
    @JmsListener(destination="lei_topic", concurrency = "1",containerFactory = "leiTopicFactory")
    public void ListenTopic(Driver driver){
        System.out.println(Thread.currentThread().getName()+"消费者1接收到topic消息:" + driver);
    }
    @JmsListener(destination="lei_topic",concurrency = "1", containerFactory = "leiTopicFactory")
    public void ListenTopic2(Driver driver){
        System.out.println(Thread.currentThread().getName()+"--消费者--2--接收到topic消息:" + driver);
    }
}

测试

嗯?消息未收到?我topic定义的假的???

image-20200720230312972

排查了很久,代码没有问题,于是百度下topic配置 发现 还需要yml配置

image-20200720230446025

修改配置后,再测一波

果然来了。每一个消费者都消费了一条消息 ,我循环发送了 30次消息,每一个消费者都接收到了三十次消息

image-20200720230633998

上边说了消费者并行数 ,咱们将其中一个消费者并行数设置大一点

设为10个

    @JmsListener(destination="lei_topic",concurrency = "10", containerFactory = "leiTopicFactory")
    public void ListenTopic2(Driver driver){
        System.out.println(Thread.currentThread().getName()+"--消费者--2--接收到topic消息:" + driver);
    }

image-20200720230916290

圈红的为消费者1 只有一个并行数,圈紫色的为我设置了消费者并行数为10的消费者2,

我们可以看到,消费者2 同时有多条线程去消费消息,也造成了一个重复消费的情况…

到时可以确定:消费者并行数 设置多线程式的消费者,并发消费

问题:我这里造成了重复消费(百度未解决,难道要手动再判断消息是否被消费了??)

四、虚拟主题VirtualTopic

为什么有了topic还要使用 virtualTopic?

我们从上边的例子先来梳理下topic的工作模式:

使用topic方式,那么如果消费端部署的是集群方式,那么每一个都订阅了,在发送消息的时候,集群中的每一个订阅者都有可能收到,但这肯定不会是我们想要的结果.

所以virtualtopic这个东西,这个东西我们可以理解为是queue和topic的结合,使用topic的一对多的广播功能,又满足了消费者在做了集群的时候,只有一个收到,也就是队列的一对一的特效。

配置

需要注意的是 activemq中 并没有 virtualTopic的构造方法,其更像是一个约定,只要topic 名字是 VirtualTopic.xxx 其就会使其成为一个虚拟主题

factory.setPubSubDomain(false); 这一步尤为重要(实质queue模式则就是设置的false),否则消费者收不到消息

敲黑板,划重点了!!!

image-20200720232349323

这也更好地理解了virtual是 是queue和topic的结合

@Configuration
@EnableJms
public class VirtualConfig {

	//定义虚拟主题的名字
    private static final String VIRTUAL_TOPIC_NAME = "VirtualTopic.Orders";


    @Bean("virtualTopicQueue")
    public ActiveMQTopic virtualTopicQueue(){
        return new ActiveMQTopic(VIRTUAL_TOPIC_NAME);
    }

    /**
     * 特别特别注意配置虚拟主题 需要设为false
     * @param connectionFactory
     * @return
     */
    @Bean(name = "virtualFactory")
    public JmsListenerContainerFactory<?> virtualFactory(ConnectionFactory connectionFactory){
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setPubSubDomain(false);
        factory.setConnectionFactory(connectionFactory);
        return factory;
    }

}

生产者

@Component
@Slf4j
public class VirtualProvider {
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;
    @Autowired
    private ActiveMQTopic virtualTopicQueue;

    public void sendTopicMessage(String message){
        for (int i = 0; i < 5; i++) {
            jmsMessagingTemplate.convertAndSend(virtualTopicQueue,message);
        }
    }
}

消费者

说明下:我这里定义了三个消费者

监听:Consumer.A.VirtualTopic.Orders

​ Consumer.A.VirtualTopic.Orders

​ Consumer.B.VirtualTopic.Orders

虚拟主题的消费者监听:必须以此固定格式 Consumer.xxx.虚拟主题名字(VirtualTopic.xxxx) 例如上方的:Consumer.B.VirtualTopic.Orders

设置两个消费者监听Consumer.A.VirtualTopic.Orders 模拟一个消费者集群的情况下,看看当生产者发送消息的时候,多个Consumer.A.VirtualTopic.Orders 是否只有一个消费了,是否避免了消费者集群下,重复消费问题!

@Component
@Slf4j
public class VirtualConsumer {

    /**
     * 设置Consumer.A. 两个
     * @param receiveStr
     * @throws JMSException
     */
    @JmsListener(destination = "Consumer.A.VirtualTopic.Orders",containerFactory = "virtualFactory" )
    public void receiveTopicQueueA1(String receiveStr) throws JMSException {
        log.info("Consumer.A1-----Orders:{}",receiveStr);
 
    }
    /**
     * 设置Consumer.A. 两个
     * @param receiveStr
     * @throws JMSException
     */
    @JmsListener(destination = "Consumer.A.VirtualTopic.Orders",concurrency = "50",containerFactory = "virtualFactory" )
    public void receiveTopicQueueA2(String receiveStr) throws JMSException {
        log.info("Consumer.A2-----Orders:{}",receiveStr);

    }
    /**
     * 设置Consumer.B. 1个
     * @param receiveStr
     * @throws JMSException
     */
    @JmsListener(destination = "Consumer.B.VirtualTopic.Orders",containerFactory = "virtualFactory" )
    public void receiveTopicQueueB(String receiveStr) throws JMSException {
        log.info("Consumer.B-----Orders:{}",receiveStr);

    }
}

测试:

由于生产者是循环发送了五次。那么预测结果应该是 监听 Consumer.B.VirtualTopic.Orders 会消费五次, 监听 Consumer.A.VirtualTopic.Orders的两个消费者共消费五次

测试结果正确:

image-20200720233043012

可能细心看代码的小伙伴也发现了,我监听 Consumer.A.VirtualTopic.Orders 的第二个消费者设置了最大并行数。。。。但是测试结果虽然有多个线程,但是也避免了重复消费的情况!!

image-20200720233255159

五、总结

点对点模式:

定义queue 然后 jmsMessagingTemplate 发送消息,但仅支持多对多

注意是否直接发送对象 需要直接发送对象需在Yml配置 spring.activemq.packages.trust-all=true

topic模式:

Yml开启 spring.jms.pub-sub-domain=true

定义个 ActiveMQTopic 设置名字 (除VirtualTopic 外无其他讲究)

定义 JmsListenerContainerFactory 注意设置 setPubSubDomain(true);

消费者设置了最大并行数后,多线程消费,但消息被重复消费

消费者集群下,每个消费者都会受到topic传来的消息(消费者集群下使用不太乐观)

virtualTopic 模式:

虚拟主题模式

定义个 ActiveMQTopic 设置名字 (必须是 VirtualTopic.xxx格式)

这一步与topic模式 是反的

定义 JmsListenerContainerFactory 注意设置 setPubSubDomain(false);

消费者监听格式固定 Consumer.xxx.虚拟主题名字(VirtualTopic.xxxx)

设置消费者并行数后,有多线程,不会被重复消费 消费者集群模式下最佳选择

activemq的目前学习就到这里,后续不断更新!!!

附上项目源码:springboot-activemq

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Spring Boot中整合JAXB,您可以按照以下步骤进行操作: 1. 添加依赖:在您的项目的pom.xml文件中,添加JAXB依赖项。 ```xml <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> ``` 2. 创建Java类:创建要映射到XML的Java类。使用JAXB注解配置类和字段。 ```java @XmlRootElement public class Person { private String name; private int age; // 省略构造函数、getter和setter方法 } ``` 3. 创建XML转换工具类:创建一个工具类,用于执行XML转换操作。 ```java import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.io.File; public class JAXBUtils { public static void marshal(Object object, File file) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass()); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(object, file); } public static <T> T unmarshal(Class<T> clazz, File file) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(clazz); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); return clazz.cast(unmarshaller.unmarshal(file)); } } ``` 4. 配置Spring Boot:在您的Spring Boot应用程序的配置类上添加JAXB相关的配置。 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.xml.MarshallingHttpMessageConverter; import org.springframework.oxm.jaxb.Jaxb2Marshaller; @Configuration public class JAXBConfig { @Bean public Jaxb2Marshaller jaxb2Marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setPackagesToScan("com.example.demo"); // 设置要扫描的包路径 return marshaller; } @Bean public HttpMessageConverter<Object> marshallingHttpMessageConverter(Jaxb2Marshaller jaxb2Marshaller) { return new MarshallingHttpMessageConverter(jaxb2Marshaller); } } ``` 5. 使用JAXB:在您的控制器或服务类中使用JAXB进行XML转换操作。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class PersonController { @Autowired private Jaxb2Marshaller jaxb2Marshaller; @GetMapping("/person") public Person getPerson() { Person person = new Person(); person.setName("John"); person.setAge(25); return person; } @GetMapping("/xml") public String getXml() { Person person = getPerson(); StringWriter writer = new StringWriter(); jaxb2Marshaller.marshal(person, new StreamResult(writer)); return writer.toString(); } } ``` 这样,当访问`/person`接口时,将返回一个Person对象的JSON表示。当访问`/xml`接口时,将返回Person对象的XML表示。 这就是在Spring Boot中整合JAXB的基本步骤。您可以根据需要进行扩展和自定义。希望对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值