优雅的解决SpringBoot集成RabbitMQ序列化和反序列化的思路2

本文介绍如何在SpringBoot项目中,通过注解扫描动态生成Class与__TypeId__的映射关系,实现消息类型自动转换,提升开发效率并遵循开闭原则。

我是最棒的,加油,博客不能停,要不断的吸收消化然后再输出。

思路

咱们本文先解决上文的问题1, 为了满足 开闭原则(OCP),对扩展开放,对修改关闭,这样可以减少维护带来的风险。咱们就要 动态加载、自动加载 Class__TypeId__ 的映射关系,而不是在配置类中手动维护。
那怎么样动态加载呢?这毕竟是个 SpringBoot 项目,在日常开发潜移默化中,咱们应该很快就会想到 注解扫描Spring 就是通过 注解扫描,来生成 BeanName 跟对应实例的映射关系,通过这种方式取缔 Xml 的配置方式,方便大家的开发。那咱们要的是 __TypeId__Class 之间的映射关系, 这两个映射关系的生成逻辑有什么区别吗?那我就直接说实现方案了,没有太多可以分析了,只是 方案1 的加强。

  1. 参考 Spring 的源码,找到ClassPathScanningCandidateComponentProvider(与本次逻辑无关,省略了,以后再说,再挖个坑),用于注解扫描。
  2. 定义 一个注解 @TypeId, 跟 @Component 的作用类似,标识需要 自动转换成 MessageBean Class,这个注解有个值 value,配置 对应的 __TypeId__ 是什么。
  3. 扫描@TypeId 注解的 Class , 生成 Classvalue 的映射关系
  4. 方案1 一样,调用 JsonMessageConverter setIdClassMapping 设置映射关系。

具体实现

@Slf4j
@Configuration
public class RabbitConfig {
	@Bean
	public MessageConverter messageConverter(ObjectMapper mapper) {
		Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter(mapper);
		DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
		// 跟方案1 一样,只不过这个Map是通过注解扫描自动生成的
		javaTypeMapper.setIdClassMapping(getTypeIds());
		messageConverter.setJavaTypeMapper(javaTypeMapper);
		return messageConverter;
	}

	/**
	* 扫描注解,自动生成映射关系
	*/
	@SneakyThrows
	private Map<String, Class<?>> getTypeIds() {
		// 不使用默认的TypeFilter
		ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
		// 获取 @TypeId 标识的类
		provider.addIncludeFilter(new AnnotationTypeFilter(TypeId.class));
		// 修改为你项目 需要扫描的包名
		Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("com.XXX"); 

		Map<String, Class<?>> idClassMapping = Maps.newHashMap();
		for (BeanDefinition beanDefinition : beanDefinitionSet) {
			// 根据类名获取 Class对象
			Class<?> cls = ClassUtils.forName(beanDefinition.getBeanClassName(), RabbitConfig.class.getClassLoader());
			TypeId an = cls.getAnnotation(TypeId.class);
			// 因为默认实现, Class和 __TypeId__ 必须一一对应,所以当有重复的映射时,报错,省的真正使用的时候出现 奇怪的问题
			if (idClassMapping.containsKey(an.value())) {
				throw TypeIdMultipleValueException.of(an.value());
			}
			// 注解的value中就是 对应的 __TypeId__
			idClassMapping.put(an.value(), cls);
		}

		log.info("RabbitMQ {}的映射关系{}", AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, idClassMapping);
		return idClassMapping;
	}

	/**
	* 自定义注解,标注需要转换的类
	*/
	@Target(ElementType.TYPE)
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	public static @interface TypeId {
		// __TypeId__ 的值
		String value();
	}

	// 跟其他异常区分开
	static class TypeIdMultipleValueException extends Exception {
		private static final long serialVersionUID = -3933351751673071122L;

		public TypeIdMultipleValueException(String message) {
			super(message);
		}

		static TypeIdMultipleValueException of(String value) {
			return new TypeIdMultipleValueException("@TypeId的value 为" + value + "在这个工程中多个");
		}
	}
}

使用案例

生产者

@TypeId("bean1")
public class ProducerBean1 {
 // ……省略无关代码……
}

// 生产者直接发送 对应实例就行了,不需要手动 构建 Message
rabbitTemplate.convertAndSend("queue.bean1", new ProducerBean1())

消费者

@TypeId("bean1")
public class ConsumerBean1 {
 // ……省略无关代码……
}

// 消费者 直接接受对应实例就行了,不需要手动解析 Message
@RabbitListener(queues = "queue.bean1")
public void listener(@Payload ConsumerBean1 bean) {
	 // ……省略具体业务代码……
}

总结

这个方案已经比较好了,生产者消费者 只需有一个相同的配置类, 可以封装成 Jar 后,放到 Maven 私服上,通过 SpringBoot starter 机制自动加载(这个我也想讲,再挖个坑)。剩下的 生产者消费者 就可以通过增加 注解 @TypeId, 随意的配置 Bean,只要_生产者_ 和 _消费者_对应的 __TypeId__ 相同 就可以。
但是这个方案, Class__TypeId__ 还是必须一一对应,没有解决,上文中所说的 第二个问题,这个坑,我下次在填。
我列一下,我现在挖了多少坑

  1. MessageBean 如何实现 多对多映射
  2. Spring 通过注解扫描 Bean 的源码分析
  3. starter 机制,Maven 私服,Maven 发布,Maven 中央仓库。
  4. 我构建MessageConverter的时候,为什么 ObjectMapper 是注入的,而不是自己构建。
  5. ……我还有其他想写,但是不能同时挖太多的坑,那会让我跳不出来的,先填了再挖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值