Loading

SpringBoot中Rabbit的基本使用和自动配置原理

1 一个简单的示例

Spring Boot项目中使用spring-rabbit时,需要经过以下几个步骤:

  1. 引入依赖。
  2. 配置基本连接信息。
  3. 创建消息发布者,并发送消息。
  4. 创建消息消费者,监听消息并处理。

我们以一个简单的例子开始,展示这个基本过程。

1.1 引入依赖

如果是Maven项目,需要在pom.xml文件中引入基本依赖如下:

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.3.10</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.5.5</version>
</dependency>

其中:

  • spring-rabbit用于与RabbitMQ服务器交互的工具包
  • spring-boot-autoconfigure用于自动配置RabbitMQ客户端服务器连接等基本信息。

1.2 配置连接信息

由于spring-boot-autoconfigure的自动配置功能,我们仅需要在application.yml文件中配置连接信息即可。以下是一个例子:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /

其中:

  • host:服务器地址。
  • port:服务器端口。
  • username:用户名。
  • password:密码。
  • virtual-host:交换机/队列所属虚拟主机。

1.3 消息发布者&消费者

我们直接在Spring Boot主程序中简单编写一个发布&接收消息的示例:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Queue myQueue() {
        return new Queue("myQueue");
    }

    @RabbitListener(queues = "myQueue")
    public void listen(String in) {
        System.out.println(in);
    }

    @Bean
    public ApplicationRunner runner(AmqpTemplate template) {
        return args -> template.convertAndSend("myQueue", "Hello World!");
    }
}

我们在这段代码中做了如下工作:

  1. 声明队列myQueue
  2. 创建消息消费者,监听队列myQueue
  3. 使用AmqpTemplate对象,向消息队列myQueue发送消息:Hello World!

1.4 启动项目

如果我们在本地启动了RabbitMQ服务器,并且端口、用户名和密码都没有问题。

那么启动项目,可以从控制台得到如下输出:

Hello World!

1.5 提出问题

不知道大家会不会有这些疑问:

  1. 为什么在application.yml文件中写入这些字符就可以连接到RabbitMQ服务器
  2. AmqpTemplate对象为什么不用声明就可以直接使用?

其实,这一切的功劳都归因于我们引入了spring-boot-autoconfigure。它为我们做了以下基本工作:

  1. application.yml文件中读取基本配置信息。
  2. 使用基本配置信息为我们创建出AmqpTemplate等对象,存放到Spring容器中。

接下来,由我来给大家揭开spring-boot-autoconfigurespring-rabbit自动配置的面纱。

2 RabbitProperties

2.1 看看源码

spring-boot-autoconfigure依赖的org.springframework.boot.autoconfigure.amqp包下,有个RabbitProperties类。

它的作用是:从application.yml文件中读取到spring-rabbit相关配置信息。

IDEA中,我们可以简单使用以下方法进入到这个类。

方法一,从application.yml文件进入:

  • application.yml文件中,按住Ctrl键,鼠标左键点击某个配置信息。

方法二,搜索:

  • 快速连续按两下Shift键,跳出搜索框进行搜索。

RabbitProperties源码简要如下:

package org.springframework.boot.autoconfigure.amqp;

@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
   private static final int DEFAULT_PORT = 5672;
   private static final int DEFAULT_PORT_SECURE = 5671;
   private String host = "localhost";
   private Integer port;
   private String username = "guest";
   private String password = "guest";
   private final Ssl ssl = new Ssl();
   private String virtualHost;
   private String addresses;
   private AddressShuffleMode addressShuffleMode = AddressShuffleMode.NONE;
   @DurationUnit(ChronoUnit.SECONDS)
   private Duration requestedHeartbeat;
   private int requestedChannelMax = 2047;
   private boolean publisherReturns;
   private ConfirmType publisherConfirmType;
   private Duration connectionTimeout;
   private Duration channelRpcTimeout = Duration.ofMinutes(10);
   private final Cache cache = new Cache();
   private final Listener listener = new Listener();
   private final Template template = new Template();
   private List<Address> parsedAddresses;
}

2.2 功能讲解

通过简单阅读RabbitProperties,我们可以发现两点重要信息:

  • 该类添加了@ConfigurationProperties(prefix = "spring.rabbitmq")注解。
  • 该类的部分成员变量名与application.yml中配置信息名一致,例如hostportusernamepasswordvirtual-host

在此我们需要先简单了解@ConfigurationProperties注解的功能:

  1. @ConfigurationProperties可以用来获取外部配置信息,默认是application.ymlSpring Boot配置文件。
  2. 将该注解添加到类上,会通过setter(默认)或constructor方法的方式,将外部配置信息赋值给对应成员变量。
  3. prefix可以指定配置文件中的前缀,用来将外部配置信息与成员变量进行匹配。

回到RabbitProperties源码,我们应该很容易理解RabbitProperties的功能:

  1. application.yml文件中读取前缀为spring.rabbitmq的配置信息。
  2. setter方法将配置信息赋值给对应的成员变量。

通过上述过程,完成了将配置信息从文件读取到缓存(RabbitProperties对象)的过程,以便于后续使用。

2.3 动手实战

我们也可以编写一个类似的MyRabbitProperties,用来从application.yml文件中读取RabbitMQ配置信息。

2.3.1 MyRabbitProperties

代码如下:

@ConfigurationProperties(prefix = "spring.rabbitmq")
public class MyRabbitProperties {
    private String host;
    private Integer port;
    private String username;
    private String password;
    private String virtualHost;

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setVirtualHost(String virtualHost) {
        this.virtualHost = virtualHost;
    }

    @Override
    public String toString() {
        return "MyRabbitProperties{" +
                "host='" + host + '\'' +
                ", port=" + port +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", virtualHost='" + virtualHost + '\'' +
                '}';
    }
}

简要说明:

  • 添加@ConfigurationProperties(prefix = "spring.rabbitmq"),用来从application.yml文件中读取前缀为spring.rabbitmq的配置信息。
  • 添加setter方法,用来为成员变量注入配置信息。
  • 添加toString()方法,便于后续打印信息。

2.3.2 application.yml

我们在application.yml文件中写入如下配置信息:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /

2.3.3 启动类

我们简单编写如下启动类:

@EnableConfigurationProperties(MyRabbitProperties.class)
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean
    public ApplicationRunner runner(MyRabbitProperties properties) {
        return args -> {
            System.out.println(properties);
        };
    }
}

简要说明:

  • 添加SpringBootApplication注解,表明这是一个Spring Boot启动类。
  • 添加@EnableConfigurationProperties(MyRabbitProperties.class)注解,可以将@ConfigurationProperties标注的类声明为Bean。这样Spring容器才能为MyRabbitProperties注入配置信息。
  • 添加main()函数,用来启动Spring Boot项目。
  • 声明ApplicationRunnerBean,项目启动后会执行其中的代码。

需要注意的是:联合@EnableConfigurationProperties只是@ConfigurationProperties注解使用方式的一种。我们也可以直接在MyRabbitProperties类上标注@Configuration@Component等注解,直接声明为Bean

2.3.4 启动项目

启动项目后,可以从控制台中得到如下输出,说明我们成功将配置信息注入到MyRabbitProperteis对象中:

MyRabbitProperties{host='localhost', port=5672, username='guest', password='guest', virtualHost='/'}

3 RabbitAutoConfiguration

通过RabbitProperteis我们已经从application.yml文件中获取到了连接RabbitMQ服务器的配置信息,接下来我们继续揭秘:

  • spring-boot-autoconfigure为我们预先创建了哪些Bean
  • 它是如何创建这些Bean的?

预先小结:这些功能都在RabbitAutoConfiguration中。

3.1 看看源码

spring-boot-autoconfigure依赖的org.springframework.boot.autoconfigure.amqp包下,有个RabbitAutoConfiguration类。

它的作用是,当类路径中存在RabbitMQSpring AMQP客户端类库时,可能会为我们自动创建如下Bean

  • org.springframework.amqp.rabbit.connection.CachingConnectionFactory:创建客户端与RabbitMQ服务器连接的工厂。
  • org.springframework.amqp.core.AmqpAdmin:封装了声明交换机/消息队列/绑定等模板方法。
  • org.springframework.amqp.rabbit.core.RabbitTemplate:封装了与RabbitMQ服务器交互的模板方法,例如:发送消息和接收消息等。
  • org.springframework.amqp.rabbit.core.RabbitMessagingTemplate:功能与RabbitTemplate相同,但底层使用org.springframework.messaging.Message作为消息抽象,较少使用。

IDEA中,我们可以简单使用以下方法进入到这个类:

  • 快速连续按两下Shift键,跳出搜索框进行搜索。

其源码结构如下:

package org.springframework.boot.autoconfigure.amqp;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {

    // 创建连接工厂Bean
   protected static class RabbitConnectionFactoryCreator {}

    // 创建RabbitTemplate和AmqpAdmin
   protected static class RabbitTemplateConfiguration {}

    // 创建RabbitMessagingTemplate
   protected static class MessagingTemplateConfiguration {}
}

3.2 功能讲解

通过简单阅读源码,我们可以将其分成四个部分进行介绍:

  1. RabbitAutoConfiguration标注注解:自动配置主类。
  2. RabbitConnectionFactoryCreator内部类:创建连接工厂,默认为CachingConnectionFactory
  3. RabbitTemplateConfiguration内部类:创建RabbitTemplateAmqpAdmin
  4. MessagingTemplateConfiguration内部类:创建RabbitMessagingTemplate

接下来,我们分别来介绍它们的功能。

3.2.1 配置主类

RabbitAutoConfiguration配置主类标注如以下四个注解:

  • @Configuration(proxyBeanMethods = false)
  • @ConditionalOnClass({ RabbitTemplate.class, Channel.class })
  • @EnableConfigurationProperties(RabbitProperties.class)
  • @Import(RabbitAnnotationDrivenConfiguration.class)

1、@Configuration(proxyBeanMethods = false)

@ConfigurationRabbitAutoConfiguration标注成配置类,可以在其内部声明Bean

proxyBeanMethods = false表示Spring容器不会动态代理内部用@Bean标注的方法,可以提高性能。

2、@ConditionalOnClass({ RabbitTemplate.class, Channel.class })

@ConditionalOnClass注解表示只有当类路径中存在以下类时,才会将RabbitAutoConfiguration注册成Bean

  • org.springframework.amqp.rabbit.core.RabbitTemplate
  • com.rabbitmq.client.Channel

也就是说,只有在类路径中存在RabbitMQSpring AMQP客户端类库,Spring容器才会为我们对RabbitMQ进行自动配置。

4、@EnableConfigurationProperties(RabbitProperties.class)

@EnableConfigurationProperties@ConfigurationProperties注解联用,可以将RabbitProperties注册成Bean,从而将配置信息从application.yml读取到内存中。

5、@Import(RabbitAnnotationDrivenConfiguration.class)

@Import注解可以引入另外的配置类——RabbitAnnotationDrivenConfiguration:用于配置Spring AMQP注解驱动断点

简单来说,它为我们注册了如下Bean

  • org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory:创建SimpleMessageListenerContainer的工厂。
  • DirectRabbitListenerContainerFactory:创建DirectMessageListenerContainer的工厂。

这两个XxxListenerContainer主要用来监听RabbitMQ服务器发送的消息。

因此,RabbitAnnotationDrivenConfiguration配置类主要与监听消息有关。由于篇幅限制,这里就不进行深入讲解了。其源码结构如下:

package org.springframework.boot.autoconfigure.amqp;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {

   @Bean
   @ConditionalOnMissingBean
   SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() {}

   @Bean(name = "rabbitListenerContainerFactory")
   @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
   @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple",
         matchIfMissing = true)
   SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(}

   @Bean
   @ConditionalOnMissingBean
   DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() {}

   @Bean(name = "rabbitListenerContainerFactory")
   @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
   @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "direct")
   DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory(}

   @Configuration(proxyBeanMethods = false)
   @EnableRabbit
   @ConditionalOnMissingBean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
   static class EnableRabbitConfiguration {}

}

3.2.2 RabbitConnectionFactoryCreator内部类

RabbitConnectionFactoryCreator内部类的作用是:注册CachingConnectionFactory作为连接工厂Bean

其头部标注了以下两个注解:

  • @Configuration(proxyBeanMethods = false):声明为配置类。
  • @ConditionalOnMissingBean(ConnectionFactory.class):只有org.springframework.amqp.rabbit.connection.ConnectionFactory类存在时才会生效,即只有类路径中添加了spring-rabbit依赖时才会生效。

其内部默认将CachingConnectionFactory注册为连接工厂Bean,步骤如下:

  1. 实例化CachingConnectionFactory对象。
  2. 将配置文件中的配置信息注入到CachingConnectionFactory对象中。

其源码简要如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ConnectionFactory.class)
protected static class RabbitConnectionFactoryCreator {

    // 注册CachingConnectionFactory作为连接工厂Bean
   @Bean
   public CachingConnectionFactory rabbitConnectionFactory(RabbitProperties properties,
         ResourceLoader resourceLoader, ObjectProvider<CredentialsProvider> credentialsProvider,
         ObjectProvider<CredentialsRefreshService> credentialsRefreshService,
         ObjectProvider<ConnectionNameStrategy> connectionNameStrategy,
         ObjectProvider<ConnectionFactoryCustomizer> connectionFactoryCustomizers) throws Exception {
       // 1、实例化CachingConnectionFactory对象
      com.rabbitmq.client.ConnectionFactory connectionFactory = getRabbitConnectionFactoryBean(properties,
            resourceLoader, credentialsProvider, credentialsRefreshService).getObject();
      connectionFactoryCustomizers.orderedStream()
            .forEach((customizer) -> customizer.customize(connectionFactory));
      CachingConnectionFactory factory = new CachingConnectionFactory(connectionFactory);
       // 2、将配置文件中的配置信息注入到CachingConnectionFactory对象中
      PropertyMapper map = PropertyMapper.get();
      map.from(properties::determineAddresses).to(factory::setAddresses);
       // 省略其他map.from().to()方法
      return factory;
   }

    // 实例化RabbitConnectionFactoryBean对象
   private RabbitConnectionFactoryBean getRabbitConnectionFactoryBean(RabbitProperties properties,
         ResourceLoader resourceLoader, ObjectProvider<CredentialsProvider> credentialsProvider,
         ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
       // 省略
      return factory;
   }

}

3.2.3 RabbitTemplateConfiguration内部类

RabbitTemplateConfiguration内部类的作用是:注册RabbitTemplateAmqpAdmin作为交互模板Bean

其头部标注了以下两个注解:

  • @Configuration(proxyBeanMethods = false):声明为配置类。
  • @Import(RabbitConnectionFactoryCreator.class):引入RabbitConnectionFactoryCreator配置类。

其内部默认注册RabbitTemplateAmqpAdmin作为交互模板Bean,本质上就是实例化对象。源码简要如下:

@Configuration(proxyBeanMethods = false)
@Import(RabbitConnectionFactoryCreator.class)
protected static class RabbitTemplateConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public RabbitTemplateConfigurer rabbitTemplateConfigurer(RabbitProperties properties,
         ObjectProvider<MessageConverter> messageConverter,
         ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers) {
      RabbitTemplateConfigurer configurer = new RabbitTemplateConfigurer();
      configurer.setMessageConverter(messageConverter.getIfUnique());
      configurer
         .setRetryTemplateCustomizers(retryTemplateCustomizers.orderedStream().collect(Collectors.toList()));
      configurer.setRabbitProperties(properties);
      return configurer;
   }

    // 注册RabbitTemplate
   @Bean
   @ConditionalOnSingleCandidate(ConnectionFactory.class)
   @ConditionalOnMissingBean(RabbitOperations.class)
   public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {
      RabbitTemplate template = new RabbitTemplate();
      configurer.configure(template, connectionFactory);
      return template;
   }

    // 注册AmqpAdmin
   @Bean
   @ConditionalOnSingleCandidate(ConnectionFactory.class)
   @ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
   @ConditionalOnMissingBean
   public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
      return new RabbitAdmin(connectionFactory);
   }

}

3.2.4 MessagingTemplateConfiguration内部类

MessagingTemplateConfiguration内部类的作用是:注册RabbitMessagingTemplate作为交互模板Bean

RabbitMessagingTemplateRabbitTemplate的功能没有本质差别,它们的差别在于继承结构不同:

  • RabbitMessagingTemplate:继承自org.springframework.messaging.core.AbstractMessageSendingTemplate抽象类。
  • RabbitTemplate:继承自org.springframework.amqp.rabbit.connection.RabbitAccessor抽象类。

项目中通常使用的是RabbitTemplate

MessagingTemplateConfiguration内部类的源码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RabbitMessagingTemplate.class)
@ConditionalOnMissingBean(RabbitMessagingTemplate.class)
@Import(RabbitTemplateConfiguration.class)
protected static class MessagingTemplateConfiguration {

   @Bean
   @ConditionalOnSingleCandidate(RabbitTemplate.class)
   public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) {
      return new RabbitMessagingTemplate(rabbitTemplate);
   }
}

4 总结

通过以上的简单介绍,想必大家对Spring Boot项目spring-rabbit的自动配置有了大概的了解。

Spring Boot对其他工具,如:spring-webspring-securityspring-datasourcespring-transactionspring-kafka以及spring.jackson等都采用类似的自动配置方式,大家可以采用本文类似的步骤阅读相关源码。

本篇文章就到这里了,希望大家身体健康,工作顺利!

posted @ 2021-10-17 00:00  Xianuii  阅读(2698)  评论(0编辑  收藏  举报