美文网首页微服务
spring boot2.* 整合ActiveMQ时版本引起的问

spring boot2.* 整合ActiveMQ时版本引起的问

作者: wingedsnake | 来源:发表于2019-01-11 10:21 被阅读0次

    之前项目使用的是一直是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>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    

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

    测试用service层接口
    public interface MessageService {
        //发送消息
        void sendMessage(Object message);
        //发送消息对象
        void sendMessageObj(Object message);
        //接收消息
        void receiveMessage(String message);
    }
    
    测试用service层实现
    
    @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 {
                    System.err.println("------->"+ac.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }else if(message instanceof ActiveMQObjectMessage) {
                ActiveMQObjectMessage s = (ActiveMQObjectMessage) message;
                try {
                    MessageCustom object = (MessageCustom)s.getObject();
                    System.err.println(object);
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }*/
            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
    

    测试用例的代码解释不在此记录的讨论范围内,所以相关配置等详细解释看官方给出的文档
    此时启动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自动配置该内部类的条件是类路径下必须存在JmsPoolConnectionFactory和PooledObject两个类。
    查看了依赖包中并未找到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 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.*需要使用JmsPoolConnectionFactory和PooledObject,而2.0.*则是直接使用PooledConnectionFactory。2.1.*增加了JMS缓存连接工厂,默认采用的连接池不再是activeMQ的连接池,而是采用了PooledJMS Library。当配置spring.activemq.pool.enabledtrue时使用的是JmsPoolConnectionFactoryProperties而不是Pool。相比2.0.*代码有较大的改动。
    因个人水平有限,如有什么错误,欢迎留言指正。

    相关文章

      网友评论

        本文标题:spring boot2.* 整合ActiveMQ时版本引起的问

        本文链接:https://www.haomeiwen.com/subject/qvvtdqtx.html