罗美琪和春波特的故事

@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 的方式。

public final < T > void registerBean(

Class< T > beanClass, Supplier< T > supplier, BeanDefinitionCustomizer… ustomizers)

“还有,还有”,在罗美琪采纳了春波特的意见比较大地调整了代码之后,春波特哥哥又提出了 Spring Boot 特有的几个要求:

  • 使用 Spring 的 Assert 在传统的 Java 代码中我们使用 assert 进行断言,Spring Boot 中断言需要使用它自有的 Assert 类,如下示例:

import org.springframework.util.Assert;

Assert.hasText(nameServer, “[rocketmq.name-server] must not be null”);

  • Auto Configuration 单元测试使用 Spring 2.0 提供的 ApplicationContextRunner:

public class RocketMQAutoConfigurationTest {

private ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RocketMQAutoConfiguration.class));

@Test(expected = NoSuchBeanDefinitionException.class) public void testRocketMQAutoConfigurationNotCreatedByDefault() {

runner.run(context -> context.getBean(RocketMQAutoConfiguration.class)); }

@Test

public void testDefaultMQProducerWithRelaxPropertyName() {

runner.withPropertyValues(“rocketmq.name-server=127.0.0.1:9876”, “rocketmq.producer.group=spring_rocketmq”).

run((context) -> {

assertThat(context).hasSingleBean(DefaultMQProducer.class); assertThat(context).hasSingleBean(RocketMQProperties.class); });

}

  • 在 auto-configuration 模块的 pom.xml 文件里,加入 spring-boot-configuration-processor 注解处理器,这样它能够生成辅助元数据文件,加快启动时间。

详情见这里:

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-custom-starter-module-autoconfigure

最后,春波特还相当专业地向罗美琪美眉提供了如下两方面的意见:

1. 通用的规范,好的代码要易读易于维护


1)注释与命名规范

我们常用的代码注释分为多行(/** … */)和单行(// …)两种类型,对于需要说明的成员变量,方法或者代码逻辑应该提供多行注释; 有些简单的代码逻辑注释也可以使用单行注释。在注释时通用的要求是首字母大写开头,并且使用句号结尾;对于单行注释,也要求首字母大写开头;并且不建议行尾单行注释。

在变量和方法命名时尽量用词准确,并且尽量不要使用缩写,如: sendMsgTimeout,建议写成 sendMessageTimeout;包名 supports,建议改成 support。

2)是否需要使用 Lombok

使用 Lombok 的好处是代码更加简洁,只需要使用一些注释就可省略 constructor,setter 和 getter 等诸多方法(bolierplate code);但是也有一个坏处就是需要开发者在自己的 IDE 环境配置 Lombok 插件来支持这一功能,所以 Spring 社区的推荐方式是不使用 Lombok,以便新用户可以直接查看和维护代码,不依赖 IDE 的设置。

3)对于包名(package)的控制

如果一个包目录下没有任何 class,建议要去掉这个包目录。例如:org.apache.rocketmq.spring.starter 在 spring 目录下没有具体的 class 定义,那么应该去掉这层目录(编者注: 我们最终把 package 改为 org.apache.rocketmq.spring,将 starter 下的目录和 classes 上移一层)。我们把所有 Enum 类放在包 org.apache.rocketmq.spring.enums 下,这个包命名并不规范,需要把 Enum 类调整到具体的包中,去掉 enums 包;类的隐藏,对于有些类,它只被包中的其它类使用,而不需要把具体的使用细节暴漏给最终用户,建议使用 package private 约束,例如:TransactionHandler 类。

4)不建议使用 Static Import, 虽然使用它的好处是更少的代码,坏处是破坏程序的可读性和易维护性。

2. 效率,深入代码的细节


  • static + final method:一个类的 static 方法不要结合 final,除非这个类本身是 final 并且声明 private 构造(ctor),如果两者结合以为这子类不能再(hiding)定义该方法,给将来的扩展和子类调用带来麻烦。

  • 在配置文件声明的 Bean 尽量使用构造函数或者 Setter 方法设置成员变量,而不要使用@Autowared,@Resource等方式注入。

  • 不要额外初始化无用的成员变量。

  • 如果一个方法没有任何地方调用,就应该删除;如果一个接口方法不需要,就不要实现这个接口类。

注[4]:下面的截图是有 FieldInjection 转变成构造函数设置的代码示例。

1.png

转换成:

2.png

故事的结局

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

罗美琪根据上述的要求调整了代码,使代码质量有了很大的提高,并且总结了 Spring Boot 开发的要点:

  • 编写前参考成熟的 spring boot 实现代码。
  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值