序
我是最棒的,加油,博客不能停,要不断的吸收消化然后再输出。
思路
咱们本文先解决上文的问题1, 为了满足 开闭原则(OCP),对扩展开放,对修改关闭,这样可以减少维护带来的风险。咱们就要 动态加载、自动加载 Class和 __TypeId__ 的映射关系,而不是在配置类中手动维护。
那怎么样动态加载呢?这毕竟是个 SpringBoot 项目,在日常开发潜移默化中,咱们应该很快就会想到 注解扫描 。 Spring 就是通过 注解扫描,来生成 BeanName 跟对应实例的映射关系,通过这种方式取缔 Xml 的配置方式,方便大家的开发。那咱们要的是 __TypeId__ 跟 Class 之间的映射关系, 这两个映射关系的生成逻辑有什么区别吗?那我就直接说实现方案了,没有太多可以分析了,只是 方案1 的加强。
- 参考 Spring 的源码,找到
ClassPathScanningCandidateComponentProvider(与本次逻辑无关,省略了,以后再说,再挖个坑),用于注解扫描。 - 定义 一个注解
@TypeId, 跟@Component的作用类似,标识需要 自动转换成Message的 BeanClass,这个注解有个值 value,配置 对应的 __TypeId__ 是什么。 - 扫描
@TypeId注解的Class, 生成Class与 value 的映射关系 - 跟 方案1 一样,调用
JsonMessageConvertersetIdClassMapping 设置映射关系。
具体实现
@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__ 还是必须一一对应,没有解决,上文中所说的 第二个问题,这个坑,我下次在填。
我列一下,我现在挖了多少坑
Message与 Bean 如何实现 多对多映射- Spring 通过注解扫描 Bean 的源码分析
- starter 机制,Maven 私服,Maven 发布,Maven 中央仓库。
- 我构建
MessageConverter的时候,为什么ObjectMapper是注入的,而不是自己构建。 - ……我还有其他想写,但是不能同时挖太多的坑,那会让我跳不出来的,先填了再挖。
本文介绍如何在SpringBoot项目中,通过注解扫描动态生成Class与__TypeId__的映射关系,实现消息类型自动转换,提升开发效率并遵循开闭原则。
2000

被折叠的 条评论
为什么被折叠?



