罗美琪和春波特的故事,Java这些高端技术只有你还不知道

通过研究发现,Spring-Boot 最核心的实现是自动化配置(auto configuration),它需要分为三个部分:

  • AutoConfiguration 类,它由 @Configuration 标注,用来创建 RocketMQ 客户端所需要的 SpringBean,如上面所提到的 RocketMQTemplate 和能够处理消费回调 Listener 的容器,每个 Listener 对应一个容器 SpringBean 来启动 MQPushConsumer,并将来将监听到的消费消息并推送给 Listener 进行回调。可参考 RocketMQAutoConfiguration.java  (编者注: 这个是最终发布的类,没有 review 的痕迹啦)。

  • 上面定义的 Configuration 类,它本身并不会“自动”配置,需要由 META-INF/spring.factories 来声明,可参考 spring.factories 使用这个 META 配置的好处是上层用户不需要关心自动配置类的细节和开关,只要 classpath 中有这个 META-INF 文件和 Configuration 类,即可自动配置。

  • 另外,上面定义的 Configuration 类,还定义了 @EnableConfiguraitonProperties 注解来引入 ConfigurationProperties 类,它的作用是定义自动配置的属性,可参考 RocketMQProperties.java,上层用户可以根据这个类里定义的属性来配置相关的属性文件(即 META-INF/application.properties 或 META-INF/application.yaml)。

故事的发展

==========================================================================

罗美琪美眉按照这个思路开发完成了 RocketMQ SpringBoot 封装并形成了 starter 交给社区的小伙伴们试用,nice~大家使用后反馈效果不错。但是还是想请教一下专业的春波特小哥哥,看看他的意见。

春波特小哥哥相当负责地对罗美琪的代码进行了 Review, 首先他抛出了两个链接:

  • https://github.com/spring-projects/spring-boot/wiki/Building-On-Spring-Boot

  • https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html

然后解释道:

“在 Spring Boot 中包含两个概念 - auto-configuration 和 starter-POMs,它们之间相互关联,但是不是简单绑定在一起的:

  • auto-configuration 负责响应应用程序的当前状态并配置适当的 Spring Bean。它放在用户的 CLASSPATH 中结合在 CLASSPATH 中的其它依赖就可以提供相关的功能。

  • Starter-POM 负责把 auto-configuration 和一些附加的依赖组织在一起,提供开箱即用的功能,它通常是一个 maven project,里面只是一个 POM 文件,不需要包含任何附加的 classes 或 resources。

换句话说,starter-POM 负责配置全量的 classpath,而 auto-configuration 负责具体的响应(实现);前者是 total-solution,后者可以按需使用。

你现在的系统是单一的一个 module 把 auto-configuration 和 starter-POM 混在了一起,这个不利于以后的扩展和模块的单独使用。”

罗美琪了解到了区分确实对日后的项目维护很重要,于是将代码进行了模块化:

|— rocketmq-spring-boot-parent  父 POM

|— rocketmq-spring-boot              auto-configuraiton 模块

|— rocketmq-spring-stater           starter 模块(实际上只包含一个 pom.xml 文件)

|— rocketmq-spring-samples         调用 starter 的示例样本

“很好,这样的模块结构就清晰多了”,春波特小哥哥点头,“但是这个 AutoConfiguration 文件里的一些标签的用法并不正确,帮你注释一下,另外,考虑到 Spring 官方到 2020 年 8 月 Spring Boot 1.X 不再提供支持,所以建议实现直接支持 Spring Boot 2.X。”

@Configuration

@EnableConfigurationProperties(RocketMQProperties.class)

@ConditionalOnClass(MQClientAPIImpl.class)

@Order ~~春波特: 这个类里使用Order很不合理呵,不建议使用,完全可以通过其他方式控制runtime是Bean的构建顺序

@Slf4j

public class RocketMQAutoConfiguration {

@Bean

@ConditionalOnClass(DefaultMQProducer.class) ~~春波特: 属性直接使用类是不科学的,需要用(name=“类全名”) 方式,这样在类不在classpath时,不会抛出CNFE

@ConditionalOnMissingBean(DefaultMQProducer.class)

@ConditionalOnProperty(prefix = “spring.rocketmq”, value = {“nameServer”, “producer.group”}) ~~春波特: nameServer属性名要写成name-server [1]

@Order(1) ~~春波特: 删掉呵 public DefaultMQProducer mqProducer(RocketMQProperties rocketMQProperties) {

}

@Bean

@ConditionalOnClass(ObjectMapper.class)

@ConditionalOnMissingBean(name = “rocketMQMessageObjectMapper”) ~~春波特: 不建议与具体的实例名绑定,设计的意图是使用系统中已经存在的ObjectMapper, 如果没有,则在这里实例化一个,需要改成

@ConditionalOnMissingBean(ObjectMapper.class)

public ObjectMapper rocketMQMessageObjectMapper() {

return new ObjectMapper();

}

@Bean(destroyMethod = “destroy”)

@ConditionalOnBean(DefaultMQProducer.class)

@ConditionalOnMissingBean(name = “rocketMQTemplate”) ~~春波特: 与上面一样

@Order(2) ~~春波特: 删掉呵

public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer,

@Autowired(required = false) ~~春波特: 删掉

@Qualifier(“rocketMQMessageObjectMapper”) ~~春波特: 删掉,不要与具体实例绑定

ObjectMapper objectMapper) {

RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();

rocketMQTemplate.setProducer(mqProducer);

if (Objects.nonNull(objectMapper)) {

rocketMQTemplate.setObjectMapper(objectMapper);

}

return rocketMQTemplate;

}

@Bean(name = RocketMQConfigUtils.ROCKETMQ_TRANSACTION_ANNOTATION_PROCESSOR_BEAN_NAME)

@ConditionalOnBean(TransactionHandlerRegistry.class)

@Role(BeanDefinition.ROLE_INFRASTRUCTURE) ~~春波特: 这个bean(RocketMQTransactionAnnotationProcessor)建议声明成static的,因为这个RocketMQTransactionAnnotationProcessor实现了BeanPostProcessor接口,接口里方法在调用的时候(创建Transaction相关的Bean的时候)可以直接使用这个static实例,而不要等到这个Configuration类的其他的Bean都构建好 [2]

public RocketMQTransactionAnnotationProcessor transactionAnnotationProcessor(

TransactionHandlerRegistry transactionHandlerRegistry) {

return new RocketMQTransactionAnnotationProcessor(transactionHandlerRegistry);

}

@Configuration ~~春波特: 这个内嵌的Configuration类比较复杂,建议独立成一个顶级类,并且使用

@Import在主Configuration类中引入

@ConditionalOnClass(DefaultMQPushConsumer.class)

@EnableConfigurationProperties(RocketMQProperties.class)

@ConditionalOnProperty(prefix = “spring.rocketmq”, value = “nameServer”) ~~春波特: name-server

public static class ListenerContainerConfiguration implements ApplicationContextAware, InitializingBean {

@Resource ~~春波特: 删掉这个annotation, 这个field injection的方式不推荐,建议使用setter或者构造参数的方式初始化成员变量

private StandardEnvironment environment;

@Autowired(required = false) ~~春波特: 这个注解是不需要的

public ListenerContainerConfiguration(

@Qualifier(“rocketMQMessageObjectMapper”) ObjectMapper objectMapper) { ~~春波特: @Qualifier 不需要

this.objectMapper = objectMapper;

}

注[1]:在声明属性的时候不要使用驼峰命名法,要使用-横线分隔,这样才能支持属性名的松散规则(relaxed rules)。

注[2]:BeanPostProcessor 接口作用是:如果需要在 Spring 容器完成 Bean 的实例化、配置和其他的初始化的前后添加一些自己的逻辑处理,就可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。为什么建议声明成 static的,春波特的英文原文:

If they don’t we basically register the post-processor at the same “time” as all the other beans in that class and the contract of BPP is that it must be registered very early on. This may not make a difference for this particular class but flagging  it as static as the side effect to make clear your BPP implementation is not supposed to drag other beans via dependency injection.

AutoConfiguration 里果真很有学问,罗美琪迅速的调整了代码,一下看起来清爽了许多。不过还是被春波特提出了两点建议:

@Configuration

public class ListenerContainerConfiguration implements ApplicationContextAware, SmartInitializingSingleton {

private ObjectMapper objectMapper = new ObjectMapper(); ~~春波特: 性能上考虑,不要初始化这个成员变量,既然这个成员是在构造/setter方法里设置的,就不要在这里初始化,尤其是当它的构造成本很高的时候。

private void registerContainer(String beanName, Object bean) { Class<?> clazz = AopUtils.getTargetClass(bean);

if(!RocketMQListener.class.isAssignableFrom(bean.getClass())){

throw new IllegalStateException(clazz + " is not instance of " + RocketMQListener.class.getName());

}

RocketMQListener rocketMQListener = (RocketMQListener) bean; RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class);

validate(annotation); ~~春波特: 下面的这种手工注册Bean的方式是Spring 4.x里提供能,可以考虑使用Spring5.0 里提供的 GenericApplicationContext.registerBean的方法,通过supplier调用new来构造Bean实例 [3]

BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultRocketMQListenerContainer.class);

beanBuilder.addPropertyValue(PROP_NAMESERVER, rocketMQProperties.getNameServer());

beanBuilder.setDestroyMethodName(METHOD_DESTROY);

String containerBeanName = String.format(“%s_%s”, DefaultRocketMQListenerContainer.class.getName(), counter.incrementAndGet());

DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();

beanFactory.registerBeanDefinition(containerBeanName, beanBuilder.getBeanDefinition());

DefaultRocketMQListenerContainer container = beanFactory.getBean(containerBeanName, DefaultRocketMQListenerContainer.class); ~~春波特: 你这里的启动方法是通过 afterPropertiesSet() 调用的,这个是不建议的,应该实现SmartLifecycle来定义启停方法,这样在ApplicationContext刷新时能够自动启动;并且避免了context初始化时由于底层资源问题导致的挂住(stuck)的危险

if (!container.isStarted()) {

try {

container.start();

} catch (Exception e) {

log.error(“started container failed. {}”, container, e); throw new RuntimeException(e);

}

}

}

}

注[3]:使用 GenericApplicationContext.registerBean 的方式。

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

腾讯T3大牛总结的500页MySQL实战笔记意外爆火,P8看了直呼内行

腾讯T3大牛总结的500页MySQL实战笔记意外爆火,P8看了直呼内行

93)]
[外链图片转存中…(img-qYQiZ0iA-1711189267093)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-dj20NvkQ-1711189267094)]

最后

[外链图片转存中…(img-nRc3hkHJ-1711189267094)]

[外链图片转存中…(img-Keug6drt-1711189267095)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值