rabbitmq-spring-boot-start2.0.0重磅重构升级

文章目录

1.前言

由于之前手写了rabbitmq-spring-boot-start.1.x版本,经过两次优化之后,用是可以用的,已经在生产集成使用了快一年了没啥问题,后面还是移除了,以免有啥潜的问题没有暴露出来,最终影响系统的性能和稳定性。毕竟是生产,还是要谨慎小心稳妥一点,后面移除之后换成之前很大众的那种方式了,但是不建议生产上使用,后面还有一些疑惑,又去带着问题去翻源码,最后突然间恍然大悟,感觉像是打通了任督二脉,把之前的零零散散的那些点全部穿成一条线,瞬间思维视野更加的开阔了,后面经过不断地思考、尝试、探索,最终才把它搞出来。

2.原理源码解析

2.1前置知识好文分享

复制代码
https://developer.aliyun.com/profile/dg5rf4zqvuxpe
https://mp.weixin.qq.com/s/cStik0RHjQQnNi6eVDmriw

入口:

java 复制代码
 SpringApplication.run(xxxx.class, args);

重点看这个refresh()中registerBeanPostProcessors()里面会有这个:

java 复制代码
beanFactory.getBean(xxx,xxxx)--->AbstractBeanFactory的doGetBean--->createBean(xxx, xx, xxx)--->AbstractAutowireCapableBeanFactory的createBean--->AbstractAutowireCapableBeanFactory的doCreateBean

refresh方法

java 复制代码
@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

AbstractBeanFactory的doGetBean

java 复制代码
protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}

			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			try {
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

				// Create bean instance.
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
                      //这个方法则是对应的FactoryBean#getObject()来一个对象,这个FactoryBean#getObject中返回的是一个已经new好的一个对象了的,xxxxFactoryBean是用于某一类特定对象的bean的创建的
					beaFactoryBeann = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}

		// Check if required type matches the type of the actual bean instance.
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

AbstractAutowireCapableBeanFactory的createBean

java 复制代码
@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
		RootBeanDefinition mbdToUse = mbd;

		// Make sure bean class is actually resolved at this point, and
		// clone the bean definition in case of a dynamically resolved Class
		// which cannot be stored in the shared merged bean definition.
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}

		// Prepare method overrides.
		try {
			mbdToUse.prepareMethodOverrides();
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
					beanName, "Validation of method overrides failed", ex);
		}

		try {
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			// A previously detected exception with proper bean creation context already,
			// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

AbstractAutowireCapableBeanFactory的doCreateBean

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            //这里则是会使用反射来new了一个对象,返回了一个包装的instanceWrapper对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
             //属性填充
			populateBean(beanName, mbd, instanceWrapper);
             //初始化bean(做增强处理的逻辑)
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

通过以上的源码流程一直跟下去,基本上可以把这个主线理清楚的,主要是把这个顺序和时间点理清楚,全局的一个视角来看,不至于鼠目寸光,多看几遍或者是debug跟一下代码基本上是可以搞懂的.

2.2 1.0.x的问题

1.x在ZlfRabbitMqRegistrar的registerBeanDefinitions方法里面使用了如下的bean的注册方式:

java 复制代码
((ConfigurableBeanFactory) this.beanFactory).registerSingleton(beanName, object);

这种方式注入的是一个已经new的对象,该对象直接就塞到了容器中(Map),脱离springBoot容器的bean的生命周期管理,就相当于自己new了一个来玩,有可能一些需要在bean的生命周期的点上做一写拓展处理的逻辑没有衔接上,有一些衔接逻辑就是在springBoot的拓展点上,所以就需要对容器启动的流程,bean的生命周期、三级缓存解决循环依赖,spingBoot的拓展点要非常饿熟悉,还有就是对springBoot提供那个amqp的自动装配的源码属性,在springBoot体系下的自动装配IOC/AOP要熟悉,官方那个是spring-boot-autoconfigure的RabbitAutoConfiguration自动装配解析了配置文件,然后在通过编程的方式注入了一些bean【连接工厂的bean、RabbitTemplate的bean、RabbitAdmin的bean】--->RabbitAnnotationDrivenConfiguration驱动的导入【Simple/Direct监听容器工厂bean的注入】--->spring-rabbit中的EnableRabbit.class【@EnableRabbit自动装配--->导入@Import(RabbitListenerConfigurationSelector.class)--->RabbitBootstrapConfiguration的注入:作用是消费者标记的bean解析注入和消费者监听断点注册(RabbitListenerAnnotationBeanPostProcessor和RabbitListenerEndpointRegistry】---->最后则是@RabbitListener和@RabbitHandler标记的bean的方法解析流程(RabbitListenerAnnotationBeanPostProcessor解析会对者两个注解标记的bean生成代理对象,当消费者收到生产者的onMessage的时候会回调这个消息的监听方法,在回调这个消息监听方法的时候会做一些代理增强,比如说,监听消费异常、消息回退、拒绝要如何处理,就有了消费者监听重试,异常消息要发到哪里,这些都是通过代理增强来做的),这里我只是提一个我看到的大概的一个流程思路,具体的源码细节非常的多,使用官方的那个启动器,第一步一来就是配置好RabbitProperties配置,接着要声明交换机队列及绑定关系【这个bean的注入会被自动创建--有两个地方会被自动创建:第一个是在RabbitAdmin的afterPropertiesSet方法中,会把容器中所有的交换、队列、绑定关系的bean找出来,然后自动去创建,第二个地方是在消费者的@RabbitListener的注解的元数据字段里面可以指定这个关系的,然后在RabbitListenerAnnotationBeanPostProcessor解析的时候会去自动创建】,在这里先提一下@Configuration的自动装配的原理可以去看ConfigurationClassPostProcessor类的源码,其它那些注解也是都会有一个对应的xxxProcessor类来解析注册bean的定义和元数据解析对其做增强就会用到xxxFactoryBean做代理逻辑处理【jdk代理、cglib代理、advice的AOP的逻辑等】,我在这里只是说一个大概的流程和思路,至于里面的很多细节可以自己去翻阅源码。

2.3 2.0.0升级点

针对1.x存在的问题,2.x做了一下的升级重构调整:

2.3.1 bean定义及注入逻辑完全交给springBoot容器管理

在这里需要注意最重要的一个点就是之前使用@Configuration,像官方那个amqp的自动装配的就只能搞一对一的关系,想要搞多个就只能自己手写一个了,并且实现了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法中都不能new对象,这种new出来的容器接管不来,它不认识,只有实现定义好bean的定义,使用构造方法注入或set注入bean的定义依赖到事先定义好的bean的名字就可以了,只要把这些依赖关系定义好,springBoot容器根据这些对象的创建说明书在创建对象的时候会去把需要依赖的零部件实例化出来,然后进行组装生产提供给我们使用。

2.3.2配置做了简化

2.3.3bean定义顺序做了调整

2.3.4RabbitAdmin中自动创建交换机队列绑定关系逻辑做了调整

所以使用这个2.0.0的启动器的时候就只用在消息监听方法代码格式:

java 复制代码
 @RabbitListener(queues = "delay.test1", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)

只用给个队列和用那个监听工厂bean的名字就行,避免@RabbitListener指定交换机、队列和绑定关系,这个关系在配置文件里面配置了,本框架会自动去关联建立对应的交换、队列和绑定关系,所以在@RabbitListener中就不要写交换机、队列和绑定关系了,以免相互影响。

2.3.5消息监听异常兜底实现

两种类型的RabbitListenerContainerFactory的MessageRecoverer的代理增强逻辑调整容器注入托管而不是直接new的方式,该方式可以实现将一个普通队列或者延迟队列升级为一个有RepublishMessageRecoverer在消息监听失败的时候开启了监听重试就可以将消息投递到错误队列或者是默认队列上,全局搞了一个兜底的队列来收那些其它队列监听失败的消息【消费异常没有try/catch也没有确认消息,防止死循环】,这个就相当于一个全局的死信队列的功能,该功能跟单个普通队列绑定一个私信队列的功能互不影响的。

2.3.6publisherConfirmType模式默认使用CORRELATED方式

CORRELATED方式的channel是PublisherCallbackChannel对应的实现类是PublisherCallbackChannelImpl,自动确认方式的channel应该是AutorecoveringChannel,CachingConnectionFactory中会对建立的连接的channel在获取的时候会做一层代理ChannelProxy【对这个信道的操作做了缓存代理增强】。

ConfirmType有以下几种类型:

复制代码
public enum ConfirmType {
    NONE,        // 不启用 Publisher Confirms
    SIMPLE,      // 启用简单确认(无 CorrelationData 关联) 
    CORRELATED   // 启用关联确认(支持 CorrelationData)
}

这个配置要开启:

yaml 复制代码
publisher-returns: true

发消息的时候需要带上CorrelationData信息

ConfirmType的优缺点:

ConfirmType.NONE:

  • 不启用 Publisher Confirms。
  • 发送消息后不会收到任何 ack/nack 回调
  • 性能最高,但无法知道消息是否成功到达 Broker
  • 适用于对可靠性要求不高的场景(如日志、监控数据)。

ConfirmType.SIMPLE:

  • 启用 Publisher Confirms,但不支持 CorrelationData
  • 回调时无法知道是哪条消息被确认/拒绝,只能知道"有消息被 ack/nack"。
  • 使用场景极少,一般不推荐。

ConfirmType.CORRELATED(最常用):

  • 启用 Publisher Confirms,并支持 CorrelationData
  • 发送消息时可传入 CorrelationData 对象(通常包含唯一 ID);
  • 在 confirm 回调中可通过 correlationData.getId() 精确识别是哪条消息被确认或拒绝。
  • 推荐用于所有需要可靠投递的生产环境

📌 对比总结

类型 是否启用 Confirms 支持 CorrelationData 能否追踪具体消息 适用场景
NONE ❌ 否 非关键消息(日志、心跳)
SIMPLE ✅ 是 极少使用(不推荐)
CORRELATED ✅ 是 关键业务消息(订单、支付等)

3.使用教程

3.1依赖

xml 复制代码
<dependency>
    <groupId>io.gitee.bigbigfeifei</groupId>
    <artifactId>rabbitmq-spring-boot-start</artifactId>
    <version>2.0.0</version>
</dependency>

或者

xml 复制代码
<dependency>
    <groupId>io.github.bigbigfeifei</groupId>
    <artifactId>rabbitmq-spring-boot-start</artifactId>
    <version>2.0.0</version>
</dependency>

上面两个依赖随便引入一个即可

3.2yaml配置

yaml 复制代码
## 配置需要保证唯一不重复(eqps中的每一的index唯一,一般配置成递增的,队列交换机绑定关系的bean注入都是根据rps的List下标+eqps中index下标注入保证了唯一性)
zlf:
  rabbit:
    rps:
      ## 如果virtual-host不同,在配置一个即可,addresses不同也是可以在配置,eqps的下标以之对应上即可
      - rabbitmq:
        virtual-host: /dyict-uat
        addresses: ip
        port: 5672
        username: "admin"
        password: "admin"
        # 启用 returns 监听
        # publisherConfirmType: CORRELATED(默认是这个) 发布确认模式需要publisher-returns: true 和 template的retry 还有listener 还需要配置confirmCallbackBeanName确认回到的beanName 发消息需要带上这个CorrelationData
        publisher-returns: true
        # 全局 mandatory
        template:
          exchange: zlf.dfe1
          routingKey: zlf.dfe1.key1
          defaultReceiveQueue: zlf.df.q1
          # mandatory: true   # ← 关键配置 消息回退开启强制回调 和 publisher-returns: true 要配置使用 retry 还有配置messageRecovererClassName没有错误队列则会重试3次往默认队列上发消息
          retry:
            enabled: true
        listener:
          simple:
            retry:
              enabled: true
            acknowledge-mode: manual   # 手动确认关键配置
            concurrency: 1
            prefetch: 1
        cache:
          channel:
            size: 10
            checkout-timeout: 10000
        confirmCallbackBeanName: confirmCallback1
        # returnCallbackBeanName: returnCallback1
        hasErrorExchangeQueue: true
        messageRecovererClassName: RepublishMessageRecoverer
        eqs:
          - function-type: Delay
            delay-type: 1
            exchange-type: custom
            exchange-name: zlf.delay.test1
            exchange-durable: true
            exchange-autoDelete: false
            queue-name: delay.test1
            queue-durable: true
            queue-exclusive: false
            queue-auto-delete: false
            routing-key: delay.test1.key
            exchange-args:
              x-delayed-type: direct
            queue-args: { }
          - function-type: Normal
            delay-type: 0
            exchange-type: direct
            exchange-name: zlf.normal.test1
            queue-name: normal.test1
            routing-key: normal.test1.key
            exchange-args: {}
            queue-args: {}
          - function-type: Delay
            delay-type: 2
            exchange-type: direct
            exchange-name: zlf.delay.test2
            queue-name: delay.test2
            ## 不用监听正常的队列,直接根据同一个路由键去路由,然后监听死信队列
            routing-key: zlf.delay-test2-key
            dlx-exchange-name: zlf.dlx-test1
            dlx-exchange-type: direct
            dlx-queue-name: dlx-test1
            dlx-key: zlf.dlx-test1-key
            exchange-args: {}
            queue-args: {}
              # queue-args:的x-dead-letter-exchange和x-dead-letter-routing-key代码里会自动关联绑定的
              # x-dead-letter-exchange: zlf.dlx-test1
            # x-dead-letter-routing-key: zlf.dlx-test1-key
            ## 单位毫秒 30s
            # x-message-ttl: 30000 这个参数是非必要参数,可以设置也可以设置

3.3使用

java 复制代码
@Slf4j
@RestController
@RequestMapping("rabbit")
public class RabbitMqTestController {

       /**
         * 延迟插件实现延迟消息发送
         *
         * @return
         */
        @GetMapping("/sendDelayMsg2")
        public String sendDelayMsg2() {
            Student student = new Student();
            student.setName("张三");
            student.setSex("男");
            student.setAge(18);
            RabbitTemplate rabbitTemplate = (RabbitTemplate) ZlfMqSpringUtils.getBean(ZlfMqRegistrarBeanNamePrefix.rabbitTemplatePrefix + 0);
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            rabbitTemplate.convertAndSend("zlf.delay.test1", "delay.test1.key", student, (message) -> {
                Long expTime = 1000L * 3;
                message.getMessageProperties().setHeader("x-delay", expTime);
                return message;
            }, correlationData);
            return "ok";
        }
    
        /**
         * 延迟插件消息发送,有返回值的消息发送
         *
         * @return
         */
        @GetMapping("/sendDelayMsg3")
        public String sendDelayMsg3() {
            Student student = new Student();
            student.setName("张三");
            student.setSex("男");
            student.setAge(18);
            RabbitTemplate rabbitTemplate = (RabbitTemplate) ZlfMqSpringUtils.getBean(ZlfMqRegistrarBeanNamePrefix.rabbitTemplatePrefix + 0);
            //rabbitTemplate.setUserCorrelationId(true);
            rabbitTemplate.setReplyTimeout(15_000);//15s
            //rabbitTemplate.setUseTemporaryReplyQueues(true);//Template中配置的默认队列无效,会被RepublishMessageRecoverer发送到错误队列(相当于一个全局死信队列兜底)
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            Student result = (Student) rabbitTemplate.convertSendAndReceive("zlf.delay.test1", "delay.test1.key", student, (message) -> {
                Long expTime = 1000L * 3;
                message.getMessageProperties().setHeader("x-delay", expTime);
                //message.getMessageProperties().setReplyTo("error.queue0");//无需设置,消息自动设置一个ReplyTo的消息heard的ReplyTo属性,这里设置的值是无效的,随便一个最后都会被覆盖为默认的值
                //message.getMessageProperties().setCorrelationId(correlationData.getId()); rabbitTemplate.setUserCorrelationId(true);这两个配套使用
                return message;
            }, correlationData);
            return JSON.toJSONString(result);
        }
    
        /**
         * 普通消息发送
         *
         * @return
         */
    
        @GetMapping("/sendMsg4")
        public String sendMsg4() {
            Student student = new Student();
            student.setName("张三");
            student.setSex("男");
            student.setAge(18);
            RabbitTemplate rabbitTemplate = (RabbitTemplate) ZlfMqSpringUtils.getBean(ZlfMqRegistrarBeanNamePrefix.rabbitTemplatePrefix + 0);
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            rabbitTemplate.convertAndSend("zlf.normal.test1", "normal.test1.key", student, correlationData);
            return "ok";
        }
    
    
        /**
         * 发送延迟消息到普通队列绑定了死信队列的消息发送
         *
         * @return
         */
        @GetMapping("/sendDelayMsg5")
        public String sendDelayMsg5() {
            Student student = new Student();
            student.setName("张三");
            student.setSex("男");
            student.setAge(18);
            RabbitTemplate rabbitTemplate = (RabbitTemplate) ZlfMqSpringUtils.getBean(ZlfMqRegistrarBeanNamePrefix.rabbitTemplatePrefix + 0);
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            rabbitTemplate.convertAndSend("zlf.delay.test2", "zlf.delay-test2-key", student, (message) -> {
                Long expTime = 1000L * 3;
                message.getMessageProperties().setHeader("x-delay", expTime);
                return message;
            }, correlationData);
            return "ok";
        }

}

项目utils下放入SpringUtils类:

java 复制代码
package xxx.utils;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author zlf
 * @description spring上下文工具类
 * @date 2024/03/11
 **/
@Component
public class SpringUtils implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(SpringUtils.class);
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        logger.info("应用程序上下文 : [{}]", "开始初始化");
        SpringUtils.applicationContext = applicationContext;
        logger.info("应用程序上下文 : [{}]", "初始化完成");
    }

    /**
     * 获取applicationContext
     *发给
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}

以上测试用例MqConsumer都是可以正常消费到对应队列中的消息的

项目中消费消息代码示例:

消费者中只需要指定对应的消费监听工厂即可,监听工厂配置如下:

ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 下标,

然后指定对应的监听工厂配置下标即可,经过测试上面的发送消息,监听消费都是正常的

java 复制代码
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.zlf.constants.ZlfMqRegistrarBeanNamePrefix;
import lombok.extern.slf4j.Slf4j;
import org.example.Student;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;


@Slf4j
@Component
public class MqConsumer {

    /**
     * 延迟插件实现延迟队列监听队列消息
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    @RabbitListener(queues = "delay.test1", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public void mqConsumer1(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumer1=====>student:{}", JSON.toJSONString(student));
            int i = 1;
            i = i / 0;
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("mqConsumer1消费异常:{}", e.getMessage());
            channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));
            throw e;
        }
    }

    /**
     * 延迟插件实现延迟队列监听队列消息(有返回值的监听消费)
     * 正确姿势
     * try{
     * Student student = JSON.parseObject(message.getBody(), Student.class);
     * log.info("mqConsumer1=====>student:{}", JSON.toJSONString(student));
     * int i = 1;
     * i = i / 0;
     * channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
     * student.setName("李四");
     * return student;
     * }catch{
     * throw new xxxxException();
     * }
     *
     * @param message
     * @param channel
     * @return
     * @throws IOException
     */
    /*@RabbitHandler
    @RabbitListener(queues = "delay.test1", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public Student mqConsumer2(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            log.info("mqConsumer2=====>msg:{}", msg);
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumer2=====>student:{}", JSON.toJSONString(student));
            //int i = 1;
            //i = i / 0;
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            student.setName("李四");
            return student;
        } catch (Exception e) {
            log.error("mqConsumer1消费异常:{}", e.getMessage());
            channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));
        }
        return null;
    }*/

    /**
     * 错误队列消息监听消费
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    @RabbitListener(queues = "error.queue0", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public void mqConsumerError0(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            log.info("mqConsumerError0=====>msg:{}", msg);
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumerError0=====>student:{}", JSON.toJSONString(student));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("mqConsumerError0消费异常:{}", e.getMessage());
            channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));
        }
    }

    /**
     * 默认队列消息监听消费
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    @RabbitListener(queues = "zlf.df.q1", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public void mqConsumerDefaultQ(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            //log.info("mqConsumer1=====>msg:{}", msg);
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumerDefaultQ=====>student:{}", JSON.toJSONString(student));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("mqConsumerDefaultQ:{}", e.getMessage());
            channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));
        }
    }

    /**
     * 普通队列消息消费监听
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    @RabbitListener(queues = "normal.test1", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public void mqConsumerNormalQ(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            log.info("mqConsumerNormalQ=====>msg:{}", msg);
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumerNormalQ=====>student:{}", JSON.toJSONString(student));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("mqConsumerNormalQ:{}", e.getMessage());
            channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));
        }
    }

    /**
     * 普通队列消息消费监听(绑定了死信队列)
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    @RabbitListener(queues = "delay.test2", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public void mqConsumerDelayNormalQ(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            log.info("mqConsumerDelayNormalQ=====>msg:{}", msg);
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumerDelayNormalQ=====>student:{}", JSON.toJSONString(student));
            //int i = 1;
            //i = i / 0;
            //channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
        } catch (Exception e) {
            log.error("mqConsumerDelayNormalQ:{}", e.getMessage());
            /*channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));*/
        }
    }

    /**
     * 死信队列消息监听
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    @RabbitListener(queues = "dlx-test1", containerFactory = ZlfMqRegistrarBeanNamePrefix.simpleRabbitListenerContainerFactory + 0)
    public void mqConsumerDlxQ(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody(), "UTF-8");
        try {
            log.info("mqConsumerDlxQ=====>msg:{}", msg);
            Student student = JSON.parseObject(message.getBody(), Student.class);
            log.info("mqConsumerDlxQ=====>student:{}", JSON.toJSONString(student));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("mqConsumerDlxQ:{}", e.getMessage());
            /*channel.basicNack(
                    message.getMessageProperties().getDeliveryTag(), // deliveryTag
                    false,  // multiple = false:只拒绝当前消息
                    false   // requeue = false:不重试,走死信队列(没有死信队列则消息直接被丢弃)
            );
            log.error("mqConsumer1消费异常异常消息msg:{}", JSON.toJSONString(message));*/
        }
    }

}

使用的话就涉及到个生产者发消息和消费者监听消费消息。

4.总结

通过将近半个多月的思考、摸索、尝试之后,终于把它整出来了,全网给有一个这种搞的?本次rabbitmq-spring-boot-start2.0.0重磅重构升级基本上满足了业务开发使用,只需要引入一个依赖,然后简单的配置,写一下业务的收发消息的代码,更加专注于业务,而不需要去每次都写一些重复性的代码,纵享丝滑,真的是太难了,老外写的东西真的是花里胡哨的,各种炫技,直接让你看不懂,更何况是去debug了,debug都不好debug,有些都是启的一些后台线程在run这的,像RabbitListenerContainerFactory的启动就有后台线程去监听端口,处理心跳啥的,这种client和server的模式,基本上都是用了一款网络的工具包,比如netty在这些中间件上用的就非常的多,用它来实现http的远程调用、用它实现Grpc、用它实现dubbo协议的远程方法调用等等的一些网络协议实现等,还有当消费者的@RabbitListener解析完成之后也会根据队列是否存在来启动消费者线程,消息的发和收都丢到线程池里面去执行,所以它底层会有一个线程池的,这些大佬写的东西就是将什么反射了、各种设计模式了,线程线程池都玩出花来了,写的太高度抽象了,公共逻辑抽象复用,没有啥重复代码的,这个就是功力,写了让你看不懂,所以不要天天搞CRUD,多去搞源码,看多了自然就领悟到其中的精髓,发现新大陆开阔眼界,手写几个轮子,复杂的事情简单做、简单的事情重复做、重复的事情用心做,你就是专家,没有天赋那就重复,本次分享到此结束,懂的都懂,只可意会不可言传,都是实践经验总结,请尊重原创,写文章是一种记录的乐趣也是一种总结的沉淀更是一种分享的情怀,希望我的分享对你有所启发、思考和帮助,请一键三连,么么么哒!

相关推荐
qq_5895681037 分钟前
Maven学习记录
java·开发语言
2501_9419820538 分钟前
高可靠API架构的三大核心支柱
java·大数据·spring
努力也学不会java39 分钟前
【docker】Docker Image(镜像)
java·运维·人工智能·机器学习·docker·容器
苦学编程的谢41 分钟前
RabbitMQ_3_RabbitMQ应用
分布式·rabbitmq
豐儀麟阁贵42 分钟前
9.2连接字符串
java·开发语言·算法
浩瀚地学42 分钟前
【Java】方法
java·开发语言·经验分享·笔记
E_ICEBLUE42 分钟前
使用 Java 将 PowerPoint 转换为 PDF 的完整指南
java·开发语言·pdf·powerpoint·ppt
卿雪1 小时前
MySQL【数据类型】:CHAR 和 VARCHAR 的对比、VATCHAR(n) 和 INT(n) 里的 n 一样吗?
android·java·数据库·python·mysql·adb·golang
李贺梖梖1 小时前
day03 流程控制语句结构、输入
java