Spring Boot 2.x 整合ActiveMQ时版本引起的问题

之前项目使用的是一直是Spring Boot 2.0.*的版本整合ActiveMQ。本来以为Spring Boot 2的版本之间改动应该不会太大,所以闲来无事试着更改成Spring Boot 2.1.*的版本,结果出现了一些版本上的问题。

依赖环境

首先添加ActiveMQ相关的依赖:

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

将版本更改为Spring Boot 2.1.1

<!-- pom文件部分代码 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
</parent>

添加测试用相关业务代码,为了测试方便直接将发送、接收整合在一个Service业务中

测试用Service层接口

public interface MessageService {
    //发送消息
    void sendMessage(Object message);
    //发送消息对象
    void sendMessageObj(Object message);
    //接收消息
    void receiveMessage(String message);
}

测试用Service层实现

@Slf4j
@Service
public class MessageServiceImpl implements MessageService {
    /**
     * 依赖注入jmsTemplate
     */
    @Resource
    private JmsTemplate jmsTemplate = null;

    /**
     * 发送消息
     */
    @Override
    public void sendMessage(Object message) {
        jmsTemplate.convertAndSend(message);
    }

    /**
     * 发送消息对象
     */
    @Override
    public void sendMessageObj(Object message) {
        jmsTemplate.convertAndSend(message);
    }

    /**
     * 接收消息
     */
    @Override
    @JmsListener(destination="${spring.jms.template.default-destination}")
    public void receiveMessage(String message) {
        /*
        if (message instanceof ActiveMQTextMessage) {
            ActiveMQTextMessage ac = (ActiveMQTextMessage) message;
            try {
                log.info("------->" + ac.getText());
            } catch (JMSException e) {
                log.error(e);
            }
        } else if(message instanceof ActiveMQObjectMessage) {
            ActiveMQObjectMessage s = (ActiveMQObjectMessage) message;
            try {
                MessageCustom object = (MessageCustom)s.getObject();
                log.info(object);
            } catch (JMSException e) {
                log.error(e);
            }
        }
        */
        System.err.println(message);
    }

}

测试用Controller层

/**
 * 发送字符串消息
 */
@RequestMapping("/sendMessage")
public void sendMessage(String message) {
    messageService.sendMessage(message);
}

测试用application.properties配置文件

spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
spring.activemq.password=admin
spring.activemq.packages.trusted=com.activemq.bean,java.lang
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=50
spring.jms.pub-sub-domain=true
spring.jms.template.default-destination=active.default.destination

# 将spring boot日志级别调整为debug级别,以便更方便查看打印出来的日志信息(只针对org.springframework及其子包)
logging.level.org.springframework=debug

运行报错分析

参考:https://github.com/spring-projects/spring-boot/issues/15998

此时启动Spring Boot应用后,会出现如下日志信息。只截取重点的几个部分:

....
Negative matches:
-----------------
   ActiveMQConnectionFactoryConfiguration.PooledConnectionFactoryConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.messaginghub.pooled.jms.JmsPoolConnectionFactory' (OnClassCondition)
   ActiveMQConnectionFactoryConfiguration.SimpleConnectionFactoryConfiguration:
      Did not match:
         - @ConditionalOnProperty (spring.activemq.pool.enabled=false) found different value in property 'enabled' (OnPropertyCondition)
      Matched:
         - @ConditionalOnClass found required class 'org.springframework.jms.connection.CachingConnectionFactory' (OnClassCondition)
   ActiveMQXAConnectionFactoryConfiguration:
      Did not match:
         - @ConditionalOnBean (types: org.springframework.boot.jms.XAConnectionFactoryWrapper; SearchStrategy: all) did not find any beans of type org.springframework.boot.jms.XAConnectionFactoryWrapper (OnBeanCondition)
      Matched:
         - @ConditionalOnClass found required class 'javax.transaction.TransactionManager' (OnClassCondition)
....
 JmsAnnotationDrivenConfiguration.JndiConfiguration:
      Did not match:
         - @ConditionalOnJndi JNDI environment is not available (OnJndiCondition)
   JmsAutoConfiguration:
      Did not match:
         - @ConditionalOnBean (types: javax.jms.ConnectionFactory; SearchStrategy: all) did not find any beans of type javax.jms.ConnectionFactory (OnBeanCondition)
      Matched:
         - @ConditionalOnClass found required classes 'javax.jms.Message', 'org.springframework.jms.core.JmsTemplate' (OnClassCondition)
   JmsAutoConfiguration.MessagingTemplateConfiguration:
      Did not match:
         - Ancestor org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration did not match (ConditionEvaluationReport.AncestorsMatchedCondition)
      Matched:
         - @ConditionalOnClass found required class 'org.springframework.jms.core.JmsMessagingTemplate' (OnClassCondition)
.....
Unconditional classes:
----------------------
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
    org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
2019-01-11 10:52:44.446 DEBUG 680 --- [main] o.s.b.d.LoggingFailureAnalysisReporter   : Application failed to start due to an exception

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.jms.core.JmsTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1644) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
......
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean of type 'org.springframework.jms.core.JmsTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.jms.core.JmsTemplate' in your configuration.

出现了没有找到jmsTemplate组件的错误。查了网上各种出现此错误的解决办法,都是手动创建connectionFactory并创建JmsTemplate相关Bean对象。但是这种方式治标不治本。既然Spring Boot官方有整合ActiveMQ的相关方法,不可能会要求用户自己手动去创建相关的Bean对象的。

通过查看日志(以上截取的有这段日志):

JmsAutoConfiguration:
      Did not match:
         - @ConditionalOnBean (types: javax.jms.ConnectionFactory; SearchStrategy: all) did not find any beans of type javax.jms.ConnectionFactory (OnBeanCondition)

说明Spring容器中并未有ConnectionFactory相关的bean对象。再往上翻日志会有如下几段日志:

ActiveMQConnectionFactoryConfiguration.PooledConnectionFactoryConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.messaginghub.pooled.jms.JmsPoolConnectionFactory' (OnClassCondition)
   ActiveMQConnectionFactoryConfiguration.SimpleConnectionFactoryConfiguration:
      Did not match:
         - @ConditionalOnProperty (spring.activemq.pool.enabled=false) found different value in property 'enabled' (OnPropertyCondition)
      Matched:
         - @ConditionalOnClass found required class 'org.springframework.jms.connection.CachingConnectionFactory' (OnClassCondition)

Spring Boot使用PooledConnectionFactoryConfiguration静态内部类需要类路径下存在org.messaginghub.pooled.jms.JmsPoolConnectionFactory类。

这里截取PooledConnectionFactoryConfiguration相关的源码:

@Configuration
@ConditionalOnClass({ JmsPoolConnectionFactory.class, PooledObject.class })
static class PooledConnectionFactoryConfiguration {

    @Bean(destroyMethod = "stop")
    @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true", matchIfMissing = false)
    public JmsPoolConnectionFactory pooledJmsConnectionFactory(
            ActiveMQProperties properties,
            ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(
                properties,
                factoryCustomizers.orderedStream().collect(Collectors.toList()))
                        .createConnectionFactory(ActiveMQConnectionFactory.class);
        return new JmsPoolConnectionFactoryFactory(properties.getPool())
                .createPooledConnectionFactory(connectionFactory);
    }
}

PooledConnectionFactoryConfiguration这个静态内部类中pooledJmsConnectionFactory方法上标注了@Bean,说明此方法返回的对象会被加入到Spring容器中。而返回的JmsPoolConnectionFactory对象元类实现了ConnectionFactory接口。所以此静态内部类便是出现问题的源头。

由于Spring Boot自动配置该内部类的条件是类路径下必须存在JmsPoolConnectionFactoryPooledObject两个类。查看了依赖包中并未找到JmsPoolConnectionFactory该类,从日志信息上看到该类应该存放于org.messaginghub.pooled.jms包下的。但是我们依赖中并未引入相关的依赖包。从maven中央仓库中查询该包的前缀org.messaginghub发现该依赖是在PooledJMS Library下的。然后尝试引入该maven依赖并去掉原来的ActiveMQ Pool依赖:

<dependency>
    <groupId>org.messaginghub</groupId>
    <artifactId>pooled-jms</artifactId>
</dependency>
<!-- 去掉原来的连接池依赖
 <dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-pool</artifactId>
</dependency> 
-->

重新启动Spring Boot应用后,问题解决

Spring Boot 2.1.*中引入了CachingConnectionFactory,所以在application.properties可以使用spring.jms.cache.**相关的配置。

查看Spring Boot 2.1.和Spring Boot 2.0.整合ActiveMQ时需要的重要类ActiveMQConnectionFactoryConfiguration源码:

Spring Boot 2.1.*版本的源码如下

@Configuration
@ConditionalOnMissingBean(ConnectionFactory.class)
class ActiveMQConnectionFactoryConfiguration {

    @Configuration
    @ConditionalOnClass(CachingConnectionFactory.class)
    @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true)
    static class SimpleConnectionFactoryConfiguration {

        private final JmsProperties jmsProperties;

        private final ActiveMQProperties properties;

        private final List<ActiveMQConnectionFactoryCustomizer> connectionFactoryCustomizers;

        SimpleConnectionFactoryConfiguration(JmsProperties jmsProperties,
                ActiveMQProperties properties,
                ObjectProvider<ActiveMQConnectionFactoryCustomizer> connectionFactoryCustomizers) {
            this.jmsProperties = jmsProperties;
            this.properties = properties;
            this.connectionFactoryCustomizers = connectionFactoryCustomizers
                    .orderedStream().collect(Collectors.toList());
        }

        @Bean
        @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "true", matchIfMissing = true)
        public CachingConnectionFactory cachingJmsConnectionFactory() {
            JmsProperties.Cache cacheProperties = this.jmsProperties.getCache();
            CachingConnectionFactory connectionFactory = new CachingConnectionFactory(
                    createConnectionFactory());
            connectionFactory.setCacheConsumers(cacheProperties.isConsumers());
            connectionFactory.setCacheProducers(cacheProperties.isProducers());
            connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize());
            return connectionFactory;
        }

        @Bean
        @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false")
        public ActiveMQConnectionFactory jmsConnectionFactory() {
            return createConnectionFactory();
        }

        private ActiveMQConnectionFactory createConnectionFactory() {
            return new ActiveMQConnectionFactoryFactory(this.properties,
                    this.connectionFactoryCustomizers)
                            .createConnectionFactory(ActiveMQConnectionFactory.class);
        }

    }

    @Configuration
    @ConditionalOnClass({ JmsPoolConnectionFactory.class, PooledObject.class })
    static class PooledConnectionFactoryConfiguration {

        @Bean(destroyMethod = "stop")
        @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true", matchIfMissing = false)
        public JmsPoolConnectionFactory pooledJmsConnectionFactory(
                ActiveMQProperties properties,
                ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
            ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(
                    properties,
                    factoryCustomizers.orderedStream().collect(Collectors.toList()))
                            .createConnectionFactory(ActiveMQConnectionFactory.class);
            return new JmsPoolConnectionFactoryFactory(properties.getPool())
                    .createPooledConnectionFactory(connectionFactory);
        }

    }

}

Spring Boot 2.0.*版本的源码如下

@Configuration
@ConditionalOnMissingBean(ConnectionFactory.class)
class ActiveMQConnectionFactoryConfiguration {

    @Bean
    @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true)
    public ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties,
            ObjectProvider<List<ActiveMQConnectionFactoryCustomizer>> factoryCustomizers) {
        return new ActiveMQConnectionFactoryFactory(properties,
                factoryCustomizers.getIfAvailable())
                        .createConnectionFactory(ActiveMQConnectionFactory.class);
    }

    @Configuration
    @ConditionalOnClass(PooledConnectionFactory.class)
    static class PooledConnectionFactoryConfiguration {

        @Bean(destroyMethod = "stop")
        @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true", matchIfMissing = false)
        public PooledConnectionFactory pooledJmsConnectionFactory(
                ActiveMQProperties properties,
                ObjectProvider<List<ActiveMQConnectionFactoryCustomizer>> factoryCustomizers) {
            PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(
                    new ActiveMQConnectionFactoryFactory(properties,
                            factoryCustomizers.getIfAvailable()).createConnectionFactory(
                                    ActiveMQConnectionFactory.class));
            ActiveMQProperties.Pool pool = properties.getPool();
            pooledConnectionFactory.setBlockIfSessionPoolIsFull(pool.isBlockIfFull());
            if (pool.getBlockIfFullTimeout() != null) {
                pooledConnectionFactory.setBlockIfSessionPoolIsFullTimeout(
                        pool.getBlockIfFullTimeout().toMillis());
            }
            pooledConnectionFactory
                    .setCreateConnectionOnStartup(pool.isCreateConnectionOnStartup());
            if (pool.getExpiryTimeout() != null) {
                pooledConnectionFactory
                        .setExpiryTimeout(pool.getExpiryTimeout().toMillis());
            }
            if (pool.getIdleTimeout() != null) {
                pooledConnectionFactory
                        .setIdleTimeout((int) pool.getIdleTimeout().toMillis());
            }
            pooledConnectionFactory.setMaxConnections(pool.getMaxConnections());
            pooledConnectionFactory.setMaximumActiveSessionPerConnection(
                    pool.getMaximumActiveSessionPerConnection());
            pooledConnectionFactory
                    .setReconnectOnException(pool.isReconnectOnException());
            if (pool.getTimeBetweenExpirationCheck() != null) {
                pooledConnectionFactory.setTimeBetweenExpirationCheckMillis(
                        pool.getTimeBetweenExpirationCheck().toMillis());
            }
            pooledConnectionFactory
                    .setUseAnonymousProducers(pool.isUseAnonymousProducers());
            return pooledConnectionFactory;
        }

    }

}

该类2.1.*版本与2.0.*版本的源码差异较大。2.1.*需要使用JmsPoolConnectionFactoryPooledObject,而2.0.*则是直接使用PooledConnectionFactory2.1.*增加了JMS缓存连接工厂,默认采用的连接池不再是ActiveMQ的连接池,而是采用了PooledJMS Library。当配置spring.activemq.pool.enabledtrue时使用的是JmsPoolConnectionFactoryProperties而不是Pool。相比2.0.*代码有较大的改动。

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/24/problems-caused-by-version-when-integration-of-activemq-in-spring-boot-2-x/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Spring Boot 2.x 整合ActiveMQ时版本引起的问题
之前项目使用的是一直是Spring Boot 2.0.*的版本整合ActiveMQ。本来以为Spring Boot 2的版本之间改动应该不会太大,所以闲来无事试着更改成Spring Boot 2.1.*……
<<上一篇
下一篇>>
文章目录
关闭
目 录