SpringBoot与JMS集成(中间件为ActiveMQ)

一、ActiveMQ学习
1、基本概念以及介绍
Apache ActiveMQ是最受欢迎和强有力的开源消息和集成模式服务器,支持许多跨语言客户端和协议,便利使用企业集成模式还有许多先进的特性。

相关命令
activemq start:启动
activemq stop:关闭

管理界面:
http://127.0.0.1:8161/admin/
login:admin
password:admin

监听端口
ActiveMQ默认端口是61616。
windows查看端口:netstat -an|find "61616"

unix查看端口:netstat -nl|grep 61616

2、两种消息模式
(1)点对点模式
点对点的模式主要建立在一个队列上面,当连接一个队列的时候,发送端不需要知道接收端是否正在接收,发送的消息,将会先进入消息队列中,如果
有接收端在监听,则会发向接收端,如果没有接收端接收,则会保存到activemq服务器,直到接收端接收信息,点对点的消息模式可以有多个发送端,
多个接收端,但是一条消息,只会被一个接收端接收,那个接收端先连上activemq,则会先接收到,而后来的接收端则接收不到那条信息。

(2)订阅/发布模式
订阅/发布模式,同样可以有多个发送端与接收端,但是接收端和发送端存在时间上的依赖,如果发送端发送消息的时候,接收端并没有接收信息,那么
activemq不会保存信息,认为消息已经发送。换一种说法,就是发送端发送消息的时候,接收端不在线,是接收端不到信息的,哪怕以后监听消息,同样
也是接收不到的。这个模式还有一个特点,发送端发送的消息,将会被所有的接收端接收。不类似点对点,一条消息只会被一个接收端接收到。

点对点模式发送端
public class QueueSender {

  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
    Connection conn = factory.createConnection();
    Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    // 开启连接
    conn.start();
    Destination des = session.createQueue("sampleQueue");
    MessageProducer producer = session.createProducer(des);
    // 默认为persistent,当activemq关闭时,队列数据将会被保存。当为non_persistent时,activemq关闭时,队列数据将会被清空。
    producer.setDeliveryMode(DeliveryMode.PERSISTENT);
    producer.send(session.createTextMessage("Hello World!"));
    producer.close();
    session.close();
    conn.close();
  }
}

点对点模式接收端
public class QueueConsumer {

  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
    Connection conn = factory.createConnection();
    // 开启连接
    conn.start();
    Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    Destination des = session.createQueue("sampleQueue");
    MessageConsumer consumer = session.createConsumer(des);
    consumer.setMessageListener(message -> {
      if (message instanceof TextMessage) {
        TextMessage tm = TextMessage.class.cast(message);
        try {
          System.out.println(tm.getText());
        } catch (JMSException e) {
          e.printStackTrace();
        }
      }
    });
    // 程序等待
    System.in.read();
    consumer.close();
    session.close();
    conn.close();
  }
}

订阅模式发送端
public class TopicSender {

  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
    Connection conn = factory.createConnection();
    // 开启连接
    conn.start();
    Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    Destination des = session.createTopic("myTopic");
    MessageProducer producer = session.createProducer(des);
    // 默认为persistent,当activemq关闭时,队列数据将会被保存。当为non_persistent时,activemq关闭时,队列数据将会被清空。
    producer.setDeliveryMode(DeliveryMode.PERSISTENT);
    producer.send(session.createTextMessage("Hello World!"));
    producer.close();
    session.close();
    conn.close();
  }
}

订阅模式接收端
public class TopicConsumer {

  public static void main(String[] args) throws Exception {
    ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
    Connection conn = factory.createConnection();
    // 开启连接
    conn.start();
    Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    Destination des = session.createTopic("myTopic");
    MessageConsumer consumer = session.createConsumer(des);
    consumer.setMessageListener(message -> {
      if (message instanceof TextMessage) {
        TextMessage tm = TextMessage.class.cast(message);
        try {
          System.out.println(tm.getText());
        } catch (JMSException e) {
          e.printStackTrace();
        }
      }
    });
    // 程序等待
    System.in.read();
    consumer.close();
    session.close();
    conn.close();
  }
}

3、发送消息的数据类型
//纯字符串的数据
session.createTextMessage();
//序列化的对象
session.createObjectMessage();
//流,可以用来传递文件等
session.createStreamMessage();
//用来传递字节
session.createBytesMessage();
//这个方法创建出来的就是一个map,可以把它当作map来用,当你看了它的一些方法,你就懂了
session.createMapMessage();
//这个方法,拿到的是javax.jms.Message,是所有message的接口
session.createMessage();

4、保证消息的成功处理
创建session的时候使用客户端确认模式,如:
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
AUTO_ACKNOWLEDGE:
当消息发送给接收端之后,就自动确认成功了,而不管接收端有没有处理成功,而一旦确认成功后,就会把队列里面的消息给清除掉,避免下一个接收端
收到同样的消息。
CLIENT_ACKNOWLEDGE:
由客户端进行确认,当客户端接收到消息进行处理后,客户端调用Message接口的acknowledge方法进行确认,如果不确定,activemq将会发给下一个接收端处理。
注意:CLIENT_ACKNOWLEDGE模式只在点对点模式有效,订阅/发布模式中,即使不确认,也不会保存消息。

二、Spring中的JMS
1、Spring集成JMS各包介绍
Spring提供了JMS集成框架,简化了JMS API的使用,JMS大致能分为两块功能:消息的生产和消费。JMSTemplate类用于消息的生产和异步消息的接收,
Spring提供了许多消息消息监听器容器用来创建消息驱动的POJO,同时也提供了声明式的方法来创建消息监听器。

org.springframework.jms.core提供了使用JMS的核心功能,包括通过处理资源创建和释放来简化JMS API使用的JMS模板类。Spring模板类的设计原则是提供
帮助方法执行通用的操作和为了更加复杂的使用,而把处理任务的核心委托给用户实现的回调接口。
org.springframework.jms.support提供了JMSException的异常转化功能,会把受检的JMSException转换成非受检异常,例如javax.jms.JMSException会被包装为
UncategorizedJmsException。
org.springframework.jms.support.converter提供了在Java对象和JMS消息之间的消息转换器抽象。
org.springframework.jms.support.destination提供了许多管理JMS目的地的策略,比如为存储在JNDI的目的地提供服务定位器。
org.springframework.jms.annotation提供了支持使用@JMSListener注解的必要设施。
org.springframework.jms.config提供了jms命名空间的解析实现以及配置监听器容器的java配置支持。
org.springframework.jms.connection提供了独立应用程序中适合使用的连接工厂的使用。同时也包含了JMS的平台事务管理器,也就是JmsTransactionManager。

2、使用Spring JMS
(1)JmsTemplate
JmsTemplate类简化了JMS的使用,因为当发送消息和异步接收消息它会处理资源的创建和释放,使用JmsTemplate只需要实现回调接口。
MessageCreator回调接口指定session就能创建消息,对于更复杂的使用,SessionCallback提供用户Jms会话和ProducerCallbacn回调。
备注:JmsTemplate实例一旦配置就是线程安全的,这就意味着可以配置一个JmsTemplate类的实例然后注入共享引用到多个协作者中,
简单来说,JmsTemplate是有状态的,因为它维护者一个连接工厂的引用,但是这种状态是非会话状态。

(2)Connections
JmsTemplate需要一个连接工厂的引用,通常被客户端应用作为工厂创建连接,而且它还包含了一些配置参数,许多参数是供应相关的,比如SSL配置选项。

(3)Caching Messaging Resources
在连接工厂和发送操作间,有三个中间对象会被创建和销毁,如下:
ConnectionFactory->Connection->Session->MessageProducer->send
为了优化资源的使用、提升性能,将会提供两个连接工厂。

(4)连接工厂分类
SingleConnectionFactory:每次都会创建一个新的连接,适合测试
CachingConnectionFactory:基于SingleConnectionFactory的基础上增加了Sessions、MessageProducers和MessageConsumer的缓存,初始缓存大小为1,
通过sessionCacheSize可以增加缓存sessions的数量。
注意:实际缓存Sessions的数量将会比设定的要大,因为缓存的sessions是基于确认模式的。

备注:推荐使用apache的PooledConnectionFactory,PooledConnectionFactory支持Connection、Session和MessageProducer的池化,连接、会话和消息生产者实例
使用后会返回池里,以便后续使用。而Consumer不会池化,消费者处理完消息后应该关闭,而不是放到池里后续使用。尽管消费者空闲,但ActiveMQ会继续发送消息
到消费者的预获取缓冲区,消息会在预获取缓冲区阻塞,直到消费者再次被激活。

(5)Destination Management
JmsTemplate会把目的地名的解析委托给JMS目的地解析对象,也就是DestinationResolver的实现。DynamicDestinationResolver是JmsTemplate使用的
默认实现,它提供解析动态目的地。
通常在JMS应用中的目的地只在运行时得知,当应用程序部署时是不会被创建的。这是因为在交互式的系统组件中有共享程序逻辑,这些系统组件会根据
一个已知的命名规则在运行期间创建目的地。

(6)Message Listener Containers
消息监听容器用来接收来自JMS消息队列的消息并驱动已注入的消息监听器。监听容器负责所有消息接收的线程并分发消息给监听器,消息监听器容器是在
MDP(message-driven POJO)和消息提供者的媒介,它关注接收消息的注册、参与事务、资源获取和释放以及异常转化。

分类:
SimpleMessageListenerContainer
这种消息监听容器会在启动时创建固定数量的JMS sessions和consumers,并且用标准的MessageConsumer.setMessageListener()注册监听器,然后让JMS
消息生产者调用回调接口。
注意:SimpleMessageListenerContainer并不允许动态适应运行时需求以及参与外部事务,它只支持本地事务,将sessionTransacted标识设为true即可。来自消息监听器的异常
会导致回滚,而且消息会重新发送。

DefaultMessageListenerContainer
这种消息监听器是使用最多的,和SimpleMessageListenerContainer相反,这种容器变体允许动态适应运行时需求,当配置了JtaTransactionManager时,每条被
接收的消息都会伴随XA transaction而注册。这种监听器在JMS生产者的低需求、参与外部管理事务的功能和兼容Java EE环境中打造了平衡。

(7)Transaction management
Spring提供了JmsTransactionManager为单个连接工厂管理事务,允许应用程序利用Spring的事务管理特性。JmsTransactionManager管理本地资源事务,
绑定来自指定连接工厂的JMS连接/会话对到线程中,JmsTemplate自动检测事务资源并相应进行操作。

三、SpringBoot与ActiveMQ集合使用

1、加入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.iboxpay</groupId>
	<artifactId>activemq_learning</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>activemq_learning</name>
	<description>activemq_learning for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!-- spring框架 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-activemq</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<!-- spring测试框架 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- mybatis与spring集成 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!-- mysql连接驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!-- druid -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>

		<!-- mq连接池 -->
		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>activemq-pool</artifactId>
		</dependency>

		<!-- jsp -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
			<scope>provided</scope>
		</dependency>
		<!-- jstl中包含标准标签库 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>

		<!--javamelody应用监控 -->
		<dependency>
			<groupId>net.bull.javamelody</groupId>
			<artifactId>javamelody-spring-boot-starter</artifactId>
			<version>1.74.0</version>
		</dependency>

		<!-- fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.49</version>
		</dependency>

		<!-- commons lang -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
		<!-- commons code -->
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

2、集成相关配置

@Configuration
@PropertySource("classpath:jms.properties")
public class ActiveMQConfig {

  @Autowired
  private JmsConfig jmsConfig;

  @Autowired
  private MyListener myListener;

  /**
   * ActiveMQ连接池
   * @return
   */
  @Bean(name = "pooledConnectionFactory", destroyMethod = "stop")
  public PooledConnectionFactory pooledConnectionFactory() {
    ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
    connectionFactory.setBrokerURL(jmsConfig.getBrokerUrl());
    return new PooledConnectionFactory(connectionFactory);
  }

  /**
   * 发送或接收目的地
   * @return
   */
  @Bean("activeMQQueue")
  public ActiveMQQueue activeMQQueue() {
    return new ActiveMQQueue(jmsConfig.getDestinationName());
  }

  /**
   * JmsTemplate消息发送模板类配置
   * @param connectionFactory
   * @param activeMQQueue
   * @return
   */
  @Bean
  public JmsTemplate jmsTemplate(@Qualifier("pooledConnectionFactory") PooledConnectionFactory connectionFactory,
      @Qualifier("activeMQQueue") ActiveMQQueue activeMQQueue) {
    JmsTemplate jmsTemplate = new JmsTemplate();
    // 指定连接工厂
    jmsTemplate.setConnectionFactory(connectionFactory);
    // 开启事务
    jmsTemplate.setSessionTransacted(true);
    // 指定确认模式,默认为自动确认
    jmsTemplate.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
    // 指定发送目的地
    jmsTemplate.setDefaultDestination(activeMQQueue);
    return jmsTemplate;
  }

  /**
   * jms事务管理器,创建session时需打开事务
   * @param connectionFactory
   * @return
   */
  @Bean(name = "jmsTransactionManager")
  public JmsTransactionManager jmsTransactionManager(
      @Qualifier("pooledConnectionFactory") PooledConnectionFactory connectionFactory) {
    return new JmsTransactionManager(connectionFactory);
  }

  /**
   * 消息监听器容器,用来接收来自指定目的地的消息
   * @param connectionFactory
   * @param jmsTransactionManager
   * @param threadPoolTaskExecutor
   * @param activeMQQueue
   * @return
   */
  @Bean
  public DefaultMessageListenerContainer defaultMessageListenerContainer(
      @Qualifier("pooledConnectionFactory") PooledConnectionFactory connectionFactory,
      @Qualifier("jmsTransactionManager") JmsTransactionManager jmsTransactionManager,
      @Qualifier("threadPoolTaskExecutor") ThreadPoolTaskExecutor threadPoolTaskExecutor,
      @Qualifier("activeMQQueue") ActiveMQQueue activeMQQueue) {
    DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    // 开启事务
    container.setSessionTransacted(true);
    container.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
    container.setDestination(activeMQQueue);
    // 指定事务管理器
    container.setTransactionManager(jmsTransactionManager);

    // 指定消息监听器
    container.setMessageListener(myListener);
    // 并发消费者数量
    container.setConcurrentConsumers(jmsConfig.getConcurrentConsumers());
    // 最大并发消费者数量
    container.setMaxConcurrentConsumers(jmsConfig.getMaxConcurrentConsumers());
    // 指定任务执行器运行监听线程
    container.setTaskExecutor(threadPoolTaskExecutor);
    // 接收时长
    container.setReceiveTimeout(jmsConfig.getReceiveTimeout());
    return container;
  }

  /**
   * 线程池任务执行器配置
   * @return
   */
  @Bean("threadPoolTaskExecutor")
  public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(20);
    threadPoolTaskExecutor.setMaxPoolSize(100);
    threadPoolTaskExecutor.setKeepAliveSeconds(300);
    threadPoolTaskExecutor.setQueueCapacity(1000);
    threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
    return threadPoolTaskExecutor;
  }

}
@Component
@ConfigurationProperties(prefix = "jms")
public class JmsConfig {

  /**
   * broker地址
   */
  private String brokerUrl;

  /**
   * 并发连接消费者数
   */
  private Integer concurrentConsumers;

  /**
   * 最大并发连接消费者数
   */
  private Integer maxConcurrentConsumers;

  /**
   * 目的地
   */
  private String destinationName;

  /**
   * 消息接收时长
   */
  private Integer receiveTimeout;

  public String getBrokerUrl() {
    return brokerUrl;
  }

  public Integer getConcurrentConsumers() {
    return concurrentConsumers;
  }

  public Integer getMaxConcurrentConsumers() {
    return maxConcurrentConsumers;
  }

  public String getDestinationName() {
    return destinationName;
  }

  public Integer getReceiveTimeout() {
    return receiveTimeout;
  }

  public void setBrokerUrl(String brokerUrl) {
    this.brokerUrl = brokerUrl;
  }

  public void setConcurrentConsumers(Integer concurrentConsumers) {
    this.concurrentConsumers = concurrentConsumers;
  }

  public void setMaxConcurrentConsumers(Integer maxConcurrentConsumers) {
    this.maxConcurrentConsumers = maxConcurrentConsumers;
  }

  public void setDestinationName(String destinationName) {
    this.destinationName = destinationName;
  }

  public void setReceiveTimeout(Integer receiveTimeout) {
    this.receiveTimeout = receiveTimeout;
  }

  @Override
  public String toString() {
    return "JmsConfig [brokerUrl=" + brokerUrl + ", concurrentConsumers=" + concurrentConsumers
        + ", maxConcurrentConsumers=" + maxConcurrentConsumers + ", destinationName=" + destinationName
        + ", receiveTimeout=" + receiveTimeout + "]";
  }

}
#================ jms相关配置 ===============#

#broker地址
jms.brokerURL=tcp://127.0.0.1:61616
#并发消费者数量
jms.concurrentConsumers=1
#最大并发消费者数量
jms.maxConcurrentConsumers=1
#发送目的地名称
jms.destinationName=spring_jms_test
#消息监听器容器接收时长
jms.receiveTimeout=0

以上为jms.properties属性文件内容

3、消息监听器配置

@Component
public class MyListener implements MessageListener {

  private static Logger logger = LoggerFactory.getLogger(MyListener.class);

  @Override
  public void onMessage(Message message) {
    if (message instanceof TextMessage) {
      try {
        TextMessage textMessage = TextMessage.class.cast(message);
        System.out.println(textMessage.getText());
      } catch (JMSException e) {
        logger.error("接收消息失败:{}", e.getMessage(), e);
      }
    }
  }

}

4、消息发送控制器

@Controller
public class JmsController {

  @Autowired
  private JmsTemplate jmsTemplate;

  @Autowired
  private ThreadPoolTaskExecutor threadPoolTaskExecutor;

  @RequestMapping("sendMessage.action")
  @ResponseBody
  public Map<String, Object> sendMessage(String textMessage) {
    Map<String, Object> resp = new HashMap<>();
    if (StringUtils.isBlank(textMessage)) {
      resp.put("statusCode", 0);
      resp.put("msg", "消息不能为空!");
      return resp;
    }

    // 异步发送消息
    threadPoolTaskExecutor.execute(() -> {
      // 实现回调接口创建消息
      jmsTemplate.send(session -> {
        return session.createTextMessage(textMessage);
      });
    });

    resp.put("statusCode", 1);
    resp.put("msg", "消息发送成功!");
    return resp;
  }

}

该学习项目github地址:https://github.com/liuyalou1996/activemq_learning

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌波漫步&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值