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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

使用Spring特性优雅书写业务代码

發(fā)布時間:2024/1/23 javascript 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用Spring特性优雅书写业务代码 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者:阿里巴巴淘系技術(shù)
鏈接:https://www.zhihu.com/question/60761181/answer/1737592739
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
?

分享一套使用Spring特性優(yōu)雅書寫業(yè)務(wù)代碼的方法。

大家在日常業(yè)務(wù)開發(fā)工作中相信多多少少遇到過下面這樣的幾個場景:

  • 當某一個特定事件或動作發(fā)生以后,需要執(zhí)行很多聯(lián)動動作,如果串行去執(zhí)行的話太耗時,如果引入消息中間件的話又太重了;
  • 想要針對不同的傳參執(zhí)行不同的策略,也就是我們常說的策略模式,但10個人可能有10種不同的寫法,夾雜在一起總感覺不那么優(yōu)雅;
  • 自己的系統(tǒng)想要調(diào)用其他系統(tǒng)提供的能力,但其他系統(tǒng)總是偶爾給你一點“小驚喜”,可能因網(wǎng)絡(luò)問題報超時異常或被調(diào)用的某一臺分布式應(yīng)用機器突然宕機,我們想要優(yōu)雅無侵入式地引入重試機制。

?

其實上面提到的幾個典型業(yè)務(wù)開發(fā)場景Spring都為我們提供了很好的特性支持,我們只需要引入Spring相關(guān)依賴就可以方便快速的在業(yè)務(wù)代碼當中使用啦,而不用引入過多的三方依賴包或自己重復造輪子。下面我們就來看看Spring提供的強大魔力吧。

?

使用Spring優(yōu)雅實現(xiàn)觀察者模式

觀察者模式定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新,其主要解決一個對象狀態(tài)改變給其他關(guān)聯(lián)對象通知的問題,保證易用和低耦合。一個典型的應(yīng)用場景是:當用戶注冊以后,需要給用戶發(fā)送郵件,發(fā)送優(yōu)惠券等操作,如下圖所示。

?

使用觀察者模式后:

UserService 在完成自身的用戶注冊邏輯之后,僅僅只需要發(fā)布一個 UserRegisterEvent 事件,而無需關(guān)注其它拓展邏輯。其它 Service 可以自己訂閱 UserRegisterEvent 事件,實現(xiàn)自定義的拓展邏輯。Spring的事件機制主要由3個部分組成。

  • ApplicationEvent:通過繼承它,實現(xiàn)自定義事件。另外,通過它的 source 屬性可以獲取事件源,timestamp 屬性可以獲得發(fā)生時間。
  • ApplicationEventPublisher:通過實現(xiàn)它,來發(fā)布變更事件。
  • ApplicationEventListener:通過實現(xiàn)它,來監(jiān)聽指定類型事件并響應(yīng)動作。這里就以上面的用戶注冊為例,來看看代碼示例。首先定義用戶注冊事件 UserRegisterEvent。
publicclass UserRegisterEvent extends ApplicationEvent {/*** 用戶名*/private String username;public UserRegisterEvent(Object source) {super(source);}public UserRegisterEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;} }

?

然后定義用戶注冊服務(wù)類,實現(xiàn) ApplicationEventPublisherAware 接口,從而將 ApplicationEventPublisher 注入進來。從下面代碼可以看到,在執(zhí)行完注冊邏輯后,調(diào)用了 ApplicationEventPublisher的 publishEvent(ApplicationEvent event) 方法,發(fā)布了 UserRegisterEvent 事件。

?

@Service publicclass UserService implements ApplicationEventPublisherAware { // <1>private Logger logger = LoggerFactory.getLogger(getClass());private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}public void register(String username) {// ... 執(zhí)行注冊邏輯logger.info("[register][執(zhí)行用戶({}) 的注冊邏輯]", username);// <2> ... 發(fā)布applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));} }

?

創(chuàng)建郵箱Service,實現(xiàn) ApplicationListener 接口,通過 E 泛型設(shè)置感興趣的事件,實現(xiàn) onApplicationEvent(E event) 方法,針對監(jiān)聽的 UserRegisterEvent 事件,進行自定義處理。

@Service publicclass EmailService implements ApplicationListener<UserRegisterEvent> { // <1>private Logger logger = LoggerFactory.getLogger(getClass());@Override@Async// <3>public void onApplicationEvent(UserRegisterEvent event) { // <2>logger.info("[onApplicationEvent][給用戶({}) 發(fā)送郵件]", event.getUsername());} }

創(chuàng)建優(yōu)惠券Service,不同于上面的實現(xiàn) ApplicationListener 接口方式,在方法上,添加 @EventListener 注解,并設(shè)置監(jiān)聽的事件為 UserRegisterEvent。這是另一種使用方式。

@Service publicclass CouponService {private Logger logger = LoggerFactory.getLogger(getClass());@EventListener// <1>public void addCoupon(UserRegisterEvent event) {logger.info("[addCoupon][給用戶({}) 發(fā)放優(yōu)惠劵]", event.getUsername());} }

看到這里,細心的同學可能想到了發(fā)布訂閱模式,其實觀察者模式于發(fā)布訂閱還是有區(qū)別的,簡單來說,發(fā)布訂閱模式屬于廣義上的觀察者模式,在觀察者模式的 Subject 和 Observer 的基礎(chǔ)上,引入 Event Channel 這個中介,進一步解耦。圖示如下,可以看出,觀察者模式更加輕量,通常用于單機,而發(fā)布訂閱模式相對而言更重一些,通常用于分布式環(huán)境下的消息通知場景。

使用Spring Retry優(yōu)雅引入重試機制

如今,Spring Retry是一個獨立的包了(早期是Spring Batch的一部分),下面是使用Spring Retry框架進行重試的幾個重要步驟。第一步:加入Spring Retry依賴包

<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.1.2.RELEASE</version> </dependency>

第二步:在應(yīng)用中包含main()方法的類或者在包含@Configuration的類上加上@EnableRetry注解 第三步:在想要進行重試的方法(可能發(fā)生異常)上加上@Retryable注解

@Retryable(maxAttempts=5,backoff = @Backoff(delay = 3000)) public void retrySomething() throws Exception{logger.info("printSomething{} is called");thrownew SQLException(); }

在上面這個案例當中的重試策略就是重試5次,每次延時3秒。詳細的使用文檔看這里,它的主要配置參數(shù)有下面這樣幾個。其中exclude、include、maxAttempts、value幾個屬性很容易理解,比較看不懂的是backoff屬性,它也是個注解,包含delay、maxDelay、multiplier、random四個屬性。

  • delay:如果不設(shè)置的話默認是1秒
  • maxDelay:最大重試等待時間
  • multiplier:用于計算下一個延遲時間的乘數(shù)(大于0生效)
  • random:隨機重試等待時間(一般不用)

?

Spring Retry的優(yōu)點很明顯,第一,屬于Spring大生態(tài),使用起來不會太生硬;第二,只需要在需要重試的方法上加上注解并配置重試策略屬性就好,不需要太多侵入代碼。

但同時也存在兩個主要不足,第一,由于Spring Retry用到了Aspect增強,所以就會有使用Aspect不可避免的坑——方法內(nèi)部調(diào)用,如果被 @Retryable 注解的方法的調(diào)用方和被調(diào)用方處于同一個類中,那么重試將會失效;第二,Spring的重試機制只支持對異常進行捕獲,而無法對返回值進行校驗判斷重試。如果想要更靈活的重試策略可以考慮使用Guava Retry,也是一個不錯的選擇。

?

?

優(yōu)雅使用Spring特性完成業(yè)務(wù)策略模式

策略模式相信大家都應(yīng)該比較熟悉,它定義了一系列的算法,并將每一個算法封裝起來,使每個算法可以相互替代,使算法本身和使用算法的客戶端分割開來,相互獨立。

其適用的場景是這樣的:一個大功能,它有許多不同類型的實現(xiàn)(策略類),具體根據(jù)客戶端來決定采用哪一個策略類。比如下單優(yōu)惠策略、物流對接策略等,應(yīng)用場景還是非常多的。

舉一個簡單的例子,業(yè)務(wù)背景是這樣的:平臺需要根據(jù)不同的業(yè)務(wù)進行鑒權(quán),每個業(yè)務(wù)的鑒權(quán)邏輯不一樣,都有自己的一套獨立的判斷邏輯,因此需要根據(jù)傳入的 bizType 進行鑒權(quán)操作,首先我們定義一個權(quán)限校驗處理器接口如下。

/*** 業(yè)務(wù)權(quán)限校驗處理器*/ publicinterface PermissionCheckHandler {/*** 判斷是否是自己能夠處理的權(quán)限校驗類型*/boolean isMatched(BizType bizType);/*** 權(quán)限校驗邏輯*/PermissionCheckResultDTO permissionCheck(Long userId, String bizCode); } 業(yè)務(wù)1的鑒權(quán)邏輯我們假設(shè)是這樣的: /*** 冷啟動權(quán)限校驗處理器*/ @Component publicclass ColdStartPermissionCheckHandlerImpl implements PermissionCheckHandler {@Overridepublic boolean isMatched(BizType bizType) {return BizType.COLD_START.equals(bizType);}@Overridepublic PermissionCheckResultDTO permissionCheck(Long userId, String bizCode) {//業(yè)務(wù)特有鑒權(quán)邏輯} } 業(yè)務(wù)2的鑒權(quán)邏輯我們假設(shè)是這樣的: /*** 趨勢業(yè)務(wù)權(quán)限校驗處理器*/ @Component publicclass TrendPermissionCheckHandlerImpl implements PermissionCheckHandler {@Overridepublic boolean isMatched(BizType bizType) {return BizType.TREND.equals(bizType);}@Overridepublic PermissionCheckResultDTO permissionCheck(Long userId, String bizCode){//業(yè)務(wù)特有鑒權(quán)邏輯} }

可能還有很多其他的業(yè)務(wù)鑒權(quán)邏輯,這里就不一一列舉了,實現(xiàn)邏輯像上面這樣組織就好了。接著就到了關(guān)鍵的地方了,上面我們定義了這么多策略,應(yīng)該怎么優(yōu)雅的組織起來呢,這就需要用到Spring提供的一些擴展特性了,Spring主要為我們提供了三類擴展點,分別對應(yīng)不同Bean生命周期階段:

  • Aware接口
  • BeanPostProcessor
  • InitializingBean 和 init-method

我們這里用到的主要是 Aware 接口和 InitializingBean 兩個擴展點,其主要用法如下代碼所示,關(guān)鍵點就在于實現(xiàn) ApplicationContextAware 接口的 setApplicationContext 方法和 InitializingBean 接口的 afterPropertiesSet 方法。

實現(xiàn) ApplicationContextAware 接口的目的就是要拿到 Spring 容器的資源,從而方便的使用它提供的 getBeansOfType 方法(該方法返回的是 map 類型,key 對應(yīng) beanName, value 對應(yīng) bean);而實現(xiàn) InitializingBean 接口的目的則是方便為 Service 類的 handlers 屬性執(zhí)行定制初始化邏輯。

?

可以很明顯的看出,如果以后還有一些其他的業(yè)務(wù)需要制定相應(yīng)的鑒權(quán)邏輯,我們只需要編寫對應(yīng)的策略類就好了,無需再破壞當前 Service 類的邏輯,很好的保證了開閉原則。

/*** 權(quán)限校驗服務(wù)類*/ @Slf4j @Service publicclass PermissionServiceImplimplements PermissionService, ApplicationContextAware, InitializingBean {private ApplicationContext applicationContext;//注:這里可以使用Map,偷個懶private List<PermissionCheckHandler> handlers = new ArrayList<>();@Overridepublic PermissionCheckResultDTO permissionCheck(ArtemisSellerBizType artemisSellerBizType, Long userId,String bizCode) {//省略一些前置邏輯PermissionCheckHandler handler = getHandler(artemisSellerBizType);return handler.permissionCheck(userId, bizCode);}private PermissionCheckHandler getHandler(ArtemisSellerBizType artemisSellerBizType) {for (PermissionCheckHandler handler : handlers) {if (handler.isMatched(artemisSellerBizType)) {return handler;}}returnnull;}@Overridepublic void afterPropertiesSet() throws Exception {for (PermissionCheckHandler handler : applicationContext.getBeansOfType(PermissionCheckHandler.class).values()) {handlers.add(handler);log.warn("load permission check handler [{}]", handler.getClass().getName());}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;} }

當然在這里相信不少同學會有疑問,那就是這里在獲取 handler 處理器 bean 的時候,所有的 bean 是不是已經(jīng)初始化好了?會不會存在有的 handler 還沒有初始化好的情況?

?

答案是不會的,Spring Bean 的聲明周期保證了這一點(當然前提是 handler 自身不會有特殊的初始化邏輯)。經(jīng)過實際驗證,所有的 handler 會在 Service 初始化操作前 ready,感興趣的同學可以編寫代碼驗證,可以先在相應(yīng)鉤子處打上日志直接輸出結(jié)果驗證,然后在 Spring 源碼關(guān)鍵處打上斷點 debug,相信會有不少收獲。

?

總結(jié)&思考

公司里的有些代碼有點年齡,有些類寫的又臭又長,很多地方充斥著代碼壞味道,如重復的代碼,過長的參數(shù)列,散彈式修改,基本型偏執(zhí)等等,不一一展開。每天要面對這些代碼進行開發(fā),不僅消磨了我們對技術(shù)的熱情也讓人變得毫無斗志,很多同學會想——反正都已經(jīng)這樣了,那我也就這么來吧,相信不少小伙伴都有這樣的遭遇與困惑。

但唯一不能停下來的就是進步,即使面對惡龍還是不能放棄抵抗。當然,在做需求的時候,很多時候也不能去修改那些代碼,太耗時太費勁,風險太大。那自己起碼也要思考一下如何設(shè)計代碼才能去避免以后出現(xiàn)同樣的情況,讓自己下次不要犯同樣的錯誤。

當我們在實際編寫代碼的時候,需要留意探索一下Spring有沒有為我們提供一些已有的工具類和擴展點。一方面,使用Spring提供的這些特性可以讓我們少造輪子,避免引入其他比較重的類庫;另一方面,Spring對JDK等庫提供的一些類和規(guī)范進行了抽象封裝,易用性更好,更貼合開發(fā)者需求。

總結(jié)

以上是生活随笔為你收集整理的使用Spring特性优雅书写业务代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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