日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

讲讲我和Spring创始级程序员共同review代码的故事

發(fā)布時間:2025/3/21 javascript 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 讲讲我和Spring创始级程序员共同review代码的故事 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

RocketMQ-Spring畢業(yè)了。

作為Apache RocketMQ的子項(xiàng)目,經(jīng)過6個多月的孵化,RocketMQ-Spring發(fā)布了第一個Release版本v2.0.1,通過使用Spring Boot的方式把RocketMQ的客戶端進(jìn)行封裝,幫助用戶通過簡單的Annotation和標(biāo)準(zhǔn)的Spring Messaging API編寫代碼,來進(jìn)行消息的發(fā)送和消費(fèi),以降低開發(fā)復(fù)雜度。

本文將以故事的形式,還原RocketMQ社區(qū)開發(fā)者和Spring社區(qū)創(chuàng)始工程師一同Review代碼以及對代碼進(jìn)行改進(jìn)的全過程,希望對做Spring Boot開發(fā)的同學(xué)有所幫助。

羅美琪:RocketMQ社區(qū)開發(fā)者

春波特小哥:Spring社區(qū)創(chuàng)始工程師

故事的開始

羅美琪有一套RocketMQ的客戶端代碼,負(fù)責(zé)發(fā)送消息和消費(fèi)消息。聽說春波特小哥善于消息發(fā)送,通過Spring Boot可以把自己客戶端調(diào)用變得非常簡單,只需要一些簡單的注解(Annotation)和代碼就可以使用獨(dú)立應(yīng)用的方式來啟動,省去了復(fù)雜的代碼編寫和參數(shù)配置。

羅美琪參考了業(yè)界已經(jīng)實(shí)現(xiàn)的消息組件的Spring實(shí)現(xiàn)了一個RocketMQ Spring客戶端,它有兩部分組成:

  • 消息的發(fā)送客戶端:這是一個自動創(chuàng)建的Spring Bean,相關(guān)屬性能夠根據(jù)配置文件的配置自動設(shè)置,命名它為: RocketMQTemplate, 同時用來封裝發(fā)送消息的各種同步和異步的方法。
  • @Resourceprivate RocketMQTemplate rocketMQTemplate;...SendResult sendResult = rocketMQTemplate.syncSend(xxxTopic, \u0026quot;Hello, World!\u0026quot;);
  • 消息的接收客戶端:這是一個能夠被應(yīng)用回調(diào)的Listener, 用于將消費(fèi)消息回調(diào)給用戶進(jìn)行相關(guān)的處理。
  • @Service@RocketMQMessageListener(topic = \u0026quot;xxx\u0026quot;, consumerGroup = \u0026quot;xxx_consumer\u0026quot;)public class StringConsumer implements RocketMQListener\u0026lt;String\u0026gt; { @Override public void onMessage(String message) { System.out.printf(\u0026quot;------- StringConsumer received: %s \\u0026quot;, message); }}

    特別說明一下:這個消費(fèi)客戶端Listener需要通過一個自定義的注解@RocketMQMessageListener來標(biāo)注,這個注解的作用有兩個:

    • 定義消息消費(fèi)的配置參數(shù)(如: 消費(fèi)的Topic, 是否順序消費(fèi),消費(fèi)組等);

    • 可以讓spring-boot在啟動過程中發(fā)現(xiàn)標(biāo)注了這個注解的所有Listener,并進(jìn)行初始化,詳見ListenerContainerConfiguration類及其實(shí)現(xiàn)SmartInitializingSingleton的接口方法afterSingletonsInstantiated()。

    羅美琪發(fā)現(xiàn),Spring-Boot最核心的實(shí)現(xiàn)是自動化配置(Auto Configuration),它分為三個部分:

    • 由@Configuration標(biāo)注,用來創(chuàng)建RocketMQ客戶端所需要的SpringBean,如上面所提到的RocketMQTemplate和能夠處理消費(fèi)回調(diào)Listener的容器,每個Listener對應(yīng)一個容器SpringBean,來啟動MQPushConsumer,并能將監(jiān)聽到的消費(fèi)消息推送給Listener進(jìn)行回調(diào)。參考:RocketMQAutoConfiguration.java (編者注: 這個是最終發(fā)布的類,沒有review的痕跡)

    • 實(shí)現(xiàn)“自動”配置,還需要由META-INF/spring.factories來聲明。參考:spring.factories。使用這個META配置的好處是上層用戶不需要關(guān)心自動配置類的細(xì)節(jié)和開關(guān),只要classpath中有這個META-INF文件和Configuration類,就能實(shí)現(xiàn)自動配置。

    • 定義了@EnableConfiguraitonProperties注解,來引入ConfigurationProperties類,它的作用是定義自動配置的屬性。參考:RocketMQProperties.java。上層用戶可以根據(jù)這個類里定義的屬性,配置相關(guān)的屬性文件(即 META-INF/application.properties 或 META-INF/application.yaml)

    故事的發(fā)展

    羅美琪按照這個思路完成了RocketMQ SpringBoot的封裝并形成了starter,提交給社區(qū)的小伙伴們試用,nice,大家使用后反饋效果不錯。但是還是想請教一下專業(yè)的春波特小哥哥,看看他的建議。

    春波特小哥相當(dāng)?shù)呢?fù)責(zé)地對羅美琪的代碼進(jìn)行了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, 它們之間相互關(guān)聯(lián),但并非簡單綁定在一起的:

    a. auto-configuration負(fù)責(zé)響應(yīng)應(yīng)用程序的當(dāng)前狀態(tài),并配置適當(dāng)?shù)腟pring Bean。它放在用戶的CLASSPATH中,結(jié)合在CLASSPATH中的其它依賴,就可以提供相關(guān)的功能;

    b. Starter-POM負(fù)責(zé)把a(bǔ)uto-configuration和一些附加的依賴組織在一起,提供開箱即用的功能,它通常是一個maven project, 里面只是一個POM文件,不需要包含任何附加的classes或resources;

    “換句話說,starter-POM負(fù)責(zé)配置全量的classpath, 而auto-configuration負(fù)責(zé)具體的響應(yīng)(實(shí)現(xiàn));前者是total-solution, 后者可以按需使用。你現(xiàn)在的系統(tǒng)是單一的一個module把a(bǔ)uto-configuration和starter-POM混在了一起,這個不利于以后的擴(kuò)展和模塊的單獨(dú)使用。”

    羅美琪明白區(qū)分對項(xiàng)目維護(hù)的重要性,于是將代碼進(jìn)行了模塊化:

    • rocketmq-spring-boot-parent:父POM

    • rocketmq-spring-boot:auto-configuraiton模塊

    • rocketmq-spring-stater:starter模塊 (實(shí)際上只包含一個pom.xml文件)

    • rocketmq-spring-samples:調(diào)用starter的示例樣本

    “很好,這樣的模塊結(jié)構(gòu)就清晰多了”,春波特小哥哥點(diǎn)頭,“但是這個AutoConfiguration文件里的一些標(biāo)簽的用法并不正確,我來注釋一下,另外,考慮到明年8月Spring Boot 1.X將不再提供支持,所以建議實(shí)現(xiàn)直接支持Spring Boot 2.X”。

    @Configuration@EnableConfigurationProperties(RocketMQProperties.class)@ConditionalOnClass(MQClientAPIImpl.class)@Order ~~春波特: 這個類里使用Order很不合理呵,不建議使用,完全可以通過其他方式控制runtime是Bean的構(gòu)建順序@Slf4jpublic class RocketMQAutoConfiguration { @Bean @ConditionalOnClass(DefaultMQProducer.class) ~~春波特: 屬性直接使用類是不科學(xué)的,需要用(name=\u0026quot;類全名\u0026quot;) 方式,這樣在類不在classpath時,不會拋出CNFE @ConditionalOnMissingBean(DefaultMQProducer.class) @ConditionalOnProperty(prefix = \u0026quot;spring.rocketmq\u0026quot;, value = {\u0026quot;nameServer\u0026quot;, \u0026quot;producer.group\u0026quot;}) ~~春波特: nameServer屬性名要寫成name-server [1] @Order(1) ~~春波特: 刪掉呵 public DefaultMQProducer mqProducer(RocketMQProperties rocketMQProperties) { ... } @Bean @ConditionalOnClass(ObjectMapper.class) @ConditionalOnMissingBean(name = \u0026quot;rocketMQMessageObjectMapper\u0026quot;) ~~春波特: 不建議與具體的實(shí)例名綁定,設(shè)計(jì)的意圖是使用系統(tǒng)中已經(jīng)存在的ObjectMapper, 如果沒有,則在這里實(shí)例化一個,需要改成 @ConditionalOnMissingBean(ObjectMapper.class) public ObjectMapper rocketMQMessageObjectMapper() { return new ObjectMapper(); } @Bean(destroyMethod = \u0026quot;destroy\u0026quot;) @ConditionalOnBean(DefaultMQProducer.class) @ConditionalOnMissingBean(name = \u0026quot;rocketMQTemplate\u0026quot;) ~~春波特: 與上面一樣 @Order(2) ~~春波特: 刪掉呵 public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer, @Autowired(required = false) ~~春波特: 刪掉 @Qualifier(\u0026quot;rocketMQMessageObjectMapper\u0026quot;) ~~春波特: 刪掉,不要與具體實(shí)例綁定 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的,因?yàn)檫@個RocketMQTransactionAnnotationProcessor實(shí)現(xiàn)了BeanPostProcessor接口,接口里方法在調(diào)用的時候(創(chuàng)建Transaction相關(guān)的Bean的時候)可以直接使用這個static實(shí)例,而不要等到這個Configuration類的其他的Bean都構(gòu)建好 [2] public RocketMQTransactionAnnotationProcessor transactionAnnotationProcessor( TransactionHandlerRegistry transactionHandlerRegistry) { return new RocketMQTransactionAnnotationProcessor(transactionHandlerRegistry); } @Configuration ~~春波特: 這個內(nèi)嵌的Configuration類比較復(fù)雜,建議獨(dú)立成一個頂級類,并且使用 @Import在主Configuration類中引入 @ConditionalOnClass(DefaultMQPushConsumer.class) @EnableConfigurationProperties(RocketMQProperties.class) @ConditionalOnProperty(prefix = \u0026quot;spring.rocketmq\u0026quot;, value = \u0026quot;nameServer\u0026quot;) ~~春波特: name-server public static class ListenerContainerConfiguration implements ApplicationContextAware, InitializingBean { ... @Resource ~~春波特: 刪掉這個annotation, 這個field injection的方式不推薦,建議使用setter或者構(gòu)造參數(shù)的方式初始化成員變量 private StandardEnvironment environment; @Autowired(required = false) ~~春波特: 這個注解是不需要的 public ListenerContainerConfiguration( @Qualifier(\u0026quot;rocketMQMessageObjectMapper\u0026quot;) ObjectMapper objectMapper) { ~~春波特: @Qualifier 不需要 this.objectMapper = objectMapper; }

    注[1]:在聲明屬性的時候不要使用駝峰命名法,要使用-橫線分隔,這樣才能支持屬性名的松散規(guī)則(relaxed rules)。

    注[2]:BeanPostProcessor接口作用是:如果需要在Spring容器完成Bean的實(shí)例化、配置和其他的初始化的前后添加一些自己的邏輯處理,就可以定義一個或者多個BeanPostProcessor接口的實(shí)現(xiàn),然后注冊到容器中。為什么建議聲明成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里果真有很多學(xué)問,羅美琪迅速的調(diào)整了代碼,一下看起來清爽了許多。不過還是被春波特提出了兩點(diǎn)建議:

    @Configurationpublic class ListenerContainerConfiguration implements ApplicationContextAware, SmartInitializingSingleton { private ObjectMapper objectMapper = new ObjectMapper(); ~~春波特: 性能上考慮,不要初始化這個成員變量,既然這個成員是在構(gòu)造/setter方法里設(shè)置的,就不要在這里初始化,尤其是當(dāng)它的構(gòu)造成本很高的時候。 private void registerContainer(String beanName, Object bean) { Class\u0026lt;?\u0026gt; clazz = AopUtils.getTargetClass(bean); if(!RocketMQListener.class.isAssignableFrom(bean.getClass())){ throw new IllegalStateException(clazz + \u0026quot; is not instance of \u0026quot; + RocketMQListener.class.getName()); } RocketMQListener rocketMQListener = (RocketMQListener) bean; RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class); validate(annotation); ~~春波特: 下面的這種手工注冊Bean的方式是Spring 4.x里提供能,可以考慮使用Spring5.0 里提供的 GenericApplicationContext.registerBean的方法,通過supplier調(diào)用new來構(gòu)造Bean實(shí)例 [3] BeanDefinitionBuilder beanBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultRocketMQListenerContainer.class); beanBuilder.addPropertyValue(PROP_NAMESERVER, rocketMQProperties.getNameServer()); ... beanBuilder.setDestroyMethodName(METHOD_DESTROY); String containerBeanName = String.format(\u0026quot;%s_%s\u0026quot;, DefaultRocketMQListenerContainer.class.getName(), counter.incrementAndGet()); DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); beanFactory.registerBeanDefinition(containerBeanName, beanBuilder.getBeanDefinition()); DefaultRocketMQListenerContainer container = beanFactory.getBean(containerBeanName, DefaultRocketMQListenerContainer.class); ~~春波特: 你這里的啟動方法是通過 afterPropertiesSet() 調(diào)用的,這個是不建議的,應(yīng)該實(shí)現(xiàn)SmartLifecycle來定義啟停方法,這樣在ApplicationContext刷新時能夠自動啟動;并且避免了context初始化時由于底層資源問題導(dǎo)致的掛住(stuck)的危險 if (!container.isStarted()) { try { container.start(); } catch (Exception e) { log.error(\u0026quot;started container failed. {}\u0026quot;, container, e); throw new RuntimeException(e); } } ... }}

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

    public final \u0026lt; T \u0026gt; void registerBean(Class\u0026lt; T \u0026gt; beanClass, Supplier\u0026lt; T \u0026gt; supplier, BeanDefinitionCustomizer… ustomizers)

    “還有,還有”,羅美琪按照春波特的建議調(diào)整完代碼后,春波特哥哥提出了Spring Boot特有的幾個要求:

    • 使用Spring的Assert在傳統(tǒng)的Java代碼中我們使用assert進(jìn)行斷言,Spring Boot中斷言需要使用它自有的Assert類,如下示例:
    import org.springframework.util.Assert;...Assert.hasText(nameServer, \u0026quot;[rocketmq.name-server] must not be null\u0026quot;);
    • 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 -\u0026gt; context.getBean(RocketMQAutoConfiguration.class)); } @Test public void testDefaultMQProducerWithRelaxPropertyName() { runner.withPropertyValues(\u0026quot;rocketmq.name-server=127.0.0.1:9876\u0026quot;, \u0026quot;rocketmq.producer.group=spring_rocketmq\u0026quot;). run((context) -\u0026gt; { assertThat(context).hasSingleBean(DefaultMQProducer.class); assertThat(context).hasSingleBean(RocketMQProperties.class); }); }

    在auto-configuration模塊的pom.xml文件里,加入spring-boot-configuration-processor注解處理器。這樣它能夠生成輔助元數(shù)據(jù)文件,加快啟動時間。詳情見這里(https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-custom-starter-module-autoconfigure)。

    最后,春波特還向羅美琪分享了一些實(shí)踐經(jīng)驗(yàn):

    通用的規(guī)范,好的代碼要易讀易于維護(hù)

    a. 注釋與命名規(guī)范

    我們常用的代碼注釋分為多行(/** … */)和單行(// …)兩種類型,對于需要說明的成員變量,方法或者代碼邏輯應(yīng)該提供多行注釋; 有些簡單的代碼邏輯注釋也可以使用單行注釋。在注釋時通用的要求是首字母大寫開頭,并且使用句號結(jié)尾;對于單行注釋,也要求首字母大寫開頭; 并且不建議行尾單行注釋。

    在變量和方法命名時盡量用詞準(zhǔn)確,并且盡量不要使用縮寫,如: sendMsgTimeout, 建議寫成sendMessageTimeout;包名supports,建議改成support。

    **b. 是否需要使用Lombok **

    使用Lombok的好處是代碼更加簡潔,只需要使用一些注釋就可省略constructor, setter和getter等諸多方法(bolierplate code);但是也有一個壞處就是需要開發(fā)者在自己的IDE環(huán)境配置Lombok插件來支持這一功能,所以Spring社區(qū)的推薦方式是不使用Lombok,以便新用戶可以直接查看和維護(hù)代碼,不依賴IDE的設(shè)置。

    c. 對于包名(package)的控制

    如果一個包目錄下沒有任何class,建議要去掉這個包目錄。例如:org.apache.rocketmq.spring.starter 在spring目錄下沒有具體的class定義,那么應(yīng)該去掉這層目錄(編者注: 我們最終把package改為org.apache.rocketmq.spring,將starter下的目錄和classes上移一層)。

    我們把所有Enum類放在包org.apache.rocketmq.spring.enums下,這個包命名并不規(guī)范,需要把Enum類調(diào)整到具體的包中,去掉enums包;類的隱藏,對于有些類,它只被包中的其它類使用,而不需要把具體的使用細(xì)節(jié)暴漏給最終用戶,建議使用package private約束,例如: TransactionHandler類。

    d. 不建議使用Static Import

    雖然使用它的好處是更少的代碼,壞處是破壞程序的可讀性和易維護(hù)性。

    效率,深入代碼的細(xì)節(jié)

    a. static + final method,一個類的static方法不要結(jié)合final,除非這個這個類本身是final并且聲明private構(gòu)造(ctor),如果兩者結(jié)合以為這子類不能再(hiding)定義該方法,給將來的擴(kuò)展和子類調(diào)用帶來麻煩。

    b. 在配置文件聲明的Bean盡量使用構(gòu)造函數(shù)或者Setter方法設(shè)置成員變量,而不要使用@Autowared,@Resource等方式注入。[4]

    c. 不要額外初始化無用的成員變量。

    d. 如果一個方法沒有任何地方調(diào)用,就應(yīng)該刪除;如果一個接口方法不需要,就不要實(shí)現(xiàn)這個接口類

    注[4]:下面的截圖是有 FieldInjection 轉(zhuǎn)變成構(gòu)造函數(shù)設(shè)置的代碼示例:

    轉(zhuǎn)換成

    故事的結(jié)局

    羅美琪按照春波特小哥的建議,進(jìn)一步調(diào)整了代碼,大幅度提高了代碼質(zhì)量,并且總結(jié)了Spring Boot開發(fā)的要點(diǎn):

    a. 編寫前參考成熟的spring boot實(shí)現(xiàn)代碼;

    b. 要注意模塊的劃分,區(qū)分autoconfiguration 和 starter;

    c. 在編寫autoconfiguration Bean的時候,注意@Conditional注解的使用;盡量使用構(gòu)造器或者setter方法來設(shè)置變量,避免使用Field Injection方式;多個Configuration Bean可以使用@Import關(guān)聯(lián);使用Spring 2.0提供的AutoConfigruation測試類;

    d. 注意一些細(xì)節(jié): static與BeanPostProcessor; Lifecycle的使用;不必要的成員屬性的初始化等;

    后記

    開源軟件不僅要關(guān)注產(chǎn)品的易用性,更要在乎代碼質(zhì)量和代碼風(fēng)格。

    活躍的社區(qū)貢獻(xiàn)者羅美琪繼續(xù)在與RocketMQ社區(qū)的小伙伴們不斷完善Spring的代碼,并邀請春波特的Spring社區(qū)進(jìn)行更多的技術(shù)分享。下一步他們將rocketmq-spring-starter推進(jìn)到Spring Initializr,讓用戶可以在start.spring.io上像使用其它starter(如: Tomcat starter)一樣使用rocketmq-spring。

    作者簡介

    遼天,社區(qū)ID walking98,阿里巴巴技術(shù)專家,Apache RocketMQ內(nèi)核控,擁有多年分布式系統(tǒng)研發(fā)經(jīng)驗(yàn),對Microsoft Messaging、Storage等領(lǐng)域有深刻理解。

    總結(jié)

    以上是生活随笔為你收集整理的讲讲我和Spring创始级程序员共同review代码的故事的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。