主頁 > 後端開發 > 原來Spring能注入集合和Map的computeIfAbsent是這么好用!

原來Spring能注入集合和Map的computeIfAbsent是這么好用!

2023-05-09 07:48:05 後端開發

大家好,我是3y,今天繼續來聊我的開源專案austin啊,但實際內容更新不多,這文章主是想吹下水,主要聊聊我在更新專案中學到的小技巧

今天所說的小技巧可能有很多人都會,但肯定也會有跟我一樣之前沒用過的,

訊息推送平臺??推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別

  • https://gitee.com/zhongfucheng/austin/
  • https://github.com/ZhongFuCheng3y/austin

Spring注入集合

之前我一直不知道,原來Spring是能注入集合的,直到一個pull request被提了過來,

https://gitee.com/zhongfucheng/austin/pulls/31

我之前寫了一個自定義注解,它的作用就是收集自定義注解所標識的Bean,然后最后把這些Bean放到Map

@Component
public class SmsScriptHolder {

    private Map<String, SmsScript> handlers = new HashMap<>(8);

    public void putHandler(String scriptName, SmsScript handler) {
        handlers.put(scriptName, handler);
    }
    public SmsScript route(String scriptName) {
        return handlers.get(scriptName);
    }
}


/**
 * 標識 短信渠道
 *
 * @author 3y
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface SmsScriptHandler {

    /**
     * 這里輸入腳本名
     *
     * @return
     */
    String value();
}

/**
 * sms發送腳本的抽象類
 *
 * @author 3y
 */
@Slf4j
public abstract class BaseSmsScript implements SmsScript {

    @Autowired
    private SmsScriptHolder smsScriptHolder;

    @PostConstruct
    public void registerProcessScript() {
        if (ArrayUtils.isEmpty(this.getClass().getAnnotations())) {
            log.error("BaseSmsScript can not find annotation!");
            return;
        }
        Annotation handlerAnnotations = null;
        for (Annotation annotation : this.getClass().getAnnotations()) {
            if (annotation instanceof SmsScriptHandler) {
                handlerAnnotations = annotation;
                break;
            }
        }
        if (handlerAnnotations == null) {
            log.error("handler annotations not declared");
            return;
        }
        //注冊handler
        smsScriptHolder.putHandler(((SmsScriptHandler) handlerAnnotations).value(), this);
    }
}

結果,pull request提的代碼過來特別簡單就替代了我的代碼了,只要在使用的時候,直接注入Map

@Autowired
private Map<String, SmsScript> smsScripts;

這一行代碼就能夠實作,把SmsScript的實作類都注入到這個Map里,同樣的,我們亦可以使用List<Interface> 把該介面下的實作類都注入到這個List里,

這好奇讓我去看看Spring到底是怎么實作的,但實際上并不難,入口在org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

接著定位到:org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency

深入 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency

最后實作注入的位置: org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveMultipleBeans 陣列 相關實作

access_token存盤到Redis

在接入微信相關渠道時,我就說過austin借助了wxjava這個開源組件庫(該組件庫對接微信相關api,使呼叫變得尤其簡單),

比如,我們呼叫微信的api是需要access_token的引數的,如果是我們自己撰寫代碼呼叫微信api,那我們需要先獲取access_token,然后把該access_token拼接在url上,此時,我們又需要考慮access_token會不會失效了,失效了我們要有重試的策略

wxjava把這些都封裝好了,屏蔽了內部實作細節,只要我們把微信渠道的賬號資訊寫到WxMpConfigStorage里,那該組件就會幫我們去拿到access_token,內部也會有相應的重試策略,

第一版我為了圖方便,我是使用WxMpDefaultConfigImpl實作類把渠道相關資訊存盤在本地記憶體里(包括access_token),而在上周我把渠道相關資訊轉都存盤至Redis

主要是獲取access_token它的呼叫次數是有限的,如果專案集群部署,而access_token又存盤在本地記憶體中,那就很大概率不到一天時間呼叫獲取access_token次數就滿了,要是拿不到access_token,那就沒辦法呼叫微信的介面了,

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

對于wxjava這個組件庫,呼叫微信的api都是通過Wx(xx)Service來使用的,而我是想把Wx(xx)Service做成是單例的,那在實作access_token存盤到Redis的時候,我就很自然就要對舊代碼進行一波重構(因為第一版寫出來的代碼,多多少少都有點不滿意),

歷史背景:

1、WxServiceUtils的邏輯是專案啟動的時檢索資料庫里所有的微信渠道賬號資訊,將Wx(xx)Service寫入到Map里,Wx(xx)Service要做成單例自然就會想到用Map存盤(因為訊息推送平臺很可能會對接很多個服務號或者小程式,這里資料結構肯定優先是Map啦)

如果渠道的賬號通過后臺有存在變更行為,那程式內部會執行refresh()重繪,但這個僅僅是在程式內能監聽到的變更,如果是直接通過SQL修改表的記錄,目前是沒有機制重繪Map的內容的,

2、AccountUtils的邏輯是程式運行時得到發送賬號的Id,通過Id去資料庫檢索賬號配置,實時回傳賬號最新的內容,(除了微信渠道賬號,其他所有的渠道賬號都是在這里獲取資訊)

更新:把原有管理微信賬號資訊的WxServiceUtils類給棄用了,將所有的發送渠道賬號資訊都歸到AccountUtils進行管理,

Map.computeIfAbsent使用

在重構上面所講的邏輯時,我很快地寫出以下的代碼:

if (clazz.equals(WxMaService.class)) {
    if (Objects.nonNull(miniProgramServiceMap.get(channelAccount))) {
        return (T)miniProgramServiceMap.get(channelAccount);
    }
    WxMaService wxMaService = initMiniProgramService(JSON.parseObject(channelAccount.getAccountConfig(), WeChatMiniProgramAccount.class));
    miniProgramServiceMap.put(channelAccount, wxMaService);
    return (T) wxMaService;
} else if (clazz.equals(WxMpService.class)) {
    if (Objects.nonNull(officialAccountServiceMap.get(channelAccount))) {
        return (T)officialAccountServiceMap.get(channelAccount);
    }
    WxMpService wxMpService = initOfficialAccountService(JSON.parseObject(channelAccount.getAccountConfig(), WeChatOfficialAccount.class));
    officialAccountServiceMap.put(channelAccount, wxMpService);
    return (T) wxMpService;
}

等我寫完,然后簡單做了下自測,發現這代碼咋這么丑啊,兩個if的邏輯實際上是一樣的,

我想,這一定會有什么工具類能幫我去優化下這個代碼的,我正準備翻Hutool/Guava這種工具包時,我突然想起:JDK在1.8好像就提供了putIfXXX的方法啦,我還找個毛啊,直接看看JDK的方法能不能用先

很快啊,我就找到了,

我首先看的是putIfAbsent,發現它實作很簡單,就是做了一層封裝,

default V putIfAbsent(K key, V value) {
    V v = get(key);
    if (v == null) {
        v = put(key, value);
    }

    return v;
}

但卻很適合用來優化我上面的代碼,于是,很快啊,我就改成了這樣:

if (clazz.equals(WxMaService.class)) {
    return (T) miniProgramServiceMap.putIfAbsent(channelAccount, initMiniProgramService(JSON.parseObject(channelAccount.getAccountConfig(), WeChatMiniProgramAccount.class)));
} else if (clazz.equals(WxMpService.class)) {
    return (T) officialAccountServiceMap.putIfAbsent(channelAccount, initOfficialAccountService(JSON.parseObject(channelAccount.getAccountConfig(), WeChatOfficialAccount.class)));
}

這看著真簡潔啊,好像已經很完美了,本來有好幾行的代碼,優化了下變成了一行,

但我又思考了下,這個putIfAbsentV我這邊傳入的是一個方法,每次這個方法都會執行的(不論我的Map里有沒有這個K),這又感覺不太優雅了

我又點進去computeIfAbsent看了下,嗯!這就是我想要的了:如果MapV不存在時,才去執行我生成V的邏輯

default V computeIfAbsent(K key,
                          Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    V v;
    if ((v = get(key)) == null) {
        V newValue;
        if ((newValue = https://www.cnblogs.com/Java3y/archive/2023/05/08/mappingFunction.apply(key)) != null) {
            put(key, newValue);
            return newValue;
        }
    }
    return v;
}

(這個其實我在學lambdastream流的時候曾經是體驗過的,我日常也會簡單寫點,只是不知道在JDKMap也有這樣的方法,)于是,最后的代碼就成了:

if (clazz.equals(WxMaService.class)) {
    return (T) miniProgramServiceMap.computeIfAbsent(channelAccount, account -> initMiniProgramService(JSON.parseObject(account.getAccountConfig(), WeChatMiniProgramAccount.class)));
} else if (clazz.equals(WxMpService.class)) {
    return (T) officialAccountServiceMap.computeIfAbsent(channelAccount, account -> initOfficialAccountService(JSON.parseObject(account.getAccountConfig(), WeChatOfficialAccount.class)));
}

又后來,等我發布到Git倉庫后,有人提了pull request來修復ConcurrentHashMapcomputeIfAbsent存在性能的問題,呀,不小心又學到了點東西,

https://bugs.openjdk.java.net/browse/JDK-8161372

微信掃碼登錄實作

我在生產環境下是沒有寫過「用戶登錄」的,導致有些業務功能我也不知道線上是怎么實作的,而「用戶登錄注冊」這個功能之前會聽過和見識過一些技術堆疊「Shiro」、「JWT」、「Spring Security」、「CAS」、「OAuth2.0」等等,

但是,我的需求只是用來做簡單的校驗,不需要那么復雜,如果就給我設計一張user表,對其簡單的增刪改查好像也滿足,但我又不想寫這樣的代碼,因為我在大學的時候實作過類似的,

現在不都流行掃碼登錄嘛?我不是已經接入了微信服務號的模板訊息了嗎,不正好有一個測驗號給我去做嗎?于是就開干了,

首先看看人家是怎么寫的,于是被我找到了一篇博客:https://blog.51cto.com/cxhit/4924932

程序挺好懂的,就按著他給出的時序圖對著實作就完了,后端對我來說實作并不難,花的時間最長的還是在前端的互動上,畢竟我這當時選用的是低代碼平臺啊,不能隨便實作各種邏輯的啊,

在前端,就一個「輪詢」功能,要輪詢查看用戶是否已經訂閱登錄,就耗費了我很多時間在官方檔案上,后來,寫了不少的奇淫技巧,最后也就被我實作出來了,實作程序很糟糕,也不值一提,反正你們也不會從中學到什么好東西,因為我也沒有,

程序還是簡單復述下吧,后期可能也會有同學去實作這個功能,

1、首先我們要有一個介面,給到微信回呼,所以我們一般會稱該介面為回呼介面,微信的一些重要的事件都會回呼給我們,我們做回應的邏輯處理,就比如,用戶關注了服務號,這種訊息微信就呼叫我們的介面,

2、在微信后臺配置我們的定義好的回呼介面,給到微信進行回呼,

(如果介面是通的,按正常的走,那就會配置成功)

3、撰寫一個獲取微信帶引數的二維碼給到前端做展示,

4、前端拿到二維碼做展示,并且得到隨機生成的引數輪詢查看是否已登錄,

5、撰寫檢查是否已登錄的介面給到前端進行判斷,(如果能從Redis里拿到隨機引數,說明已經登錄了)

6、當用戶掃碼關注了服務號,則得到微信的回呼,當用戶關注服務號時,會把隨機引數和openId傳給服務器,我則將資訊存入Redis,

7、前端得知已登錄后,將用戶資訊寫入localStorage

最后

每次代碼存在遇到“優雅”的寫法時,我都會懊惱自己怎么不會,還吭哧吭哧地寫這破代碼這么多年了,特別是Map.computeIfAbsent這個,我感覺沒理由我不知道呀,我從初學到現在作業主要用JDK 1.8,沒道理我現在才知道寫這個玩意,

有的時候都感覺我是不是已經是老古董了,新世界已經沒有承載我的船了,

不過寫開源專案有一大好處是,只要我的專案有人用,能大大提高我獲取“優雅”寫法的概率,這也是我一直推廣自己專案的一個原因之一,

如果想學Java專案的,強烈推薦我的開源專案訊息推送平臺Austin(8K stars) ,可以用作畢業設計,可以用作校招,可以看看生產環境是怎么推送訊息的,開源專案訊息推送平臺austin倉庫地址:

訊息推送平臺??推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別

  • https://gitee.com/zhongfucheng/austin/
  • https://github.com/ZhongFuCheng3y/austin
更多的文章可往:文章的目錄導航

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551905.html

標籤:其他

上一篇:京東面經總結

下一篇:返回列表

標籤雲
其他(158628) Python(38123) JavaScript(25405) Java(18024) C(15222) 區塊鏈(8262) C#(7972) AI(7469) 爪哇(7425) MySQL(7171) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5336) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4567) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1965) Web開發(1951) HtmlCss(1932) python-3.x(1918) 弹簧靴(1913) C++(1912) xml(1889) PostgreSQL(1874) .NETCore(1857) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 原來Spring能注入集合和Map的computeIfAbsent是這么好用!

    大家好,我是3y,今天繼續來聊我的開源專案austin啊,但實際內容更新不多。這文章主是想吹下水,主要聊聊我在更新專案中學到的小技巧。 今天所說的小技巧可能有很多人都會,但肯定也會有跟我一樣之前沒用過的。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息 ......

    uj5u.com 2023-05-09 07:48:05 more
  • 京東面經總結

    非科班,經歷了無數場秋招,現將面試京東的題目記錄如下: 一面 kafka在應用場景以及 專案 里的實作 bitmap底層 object里有哪些方法 hashmap相關 sychronized和reentrantlock相關問題以及鎖升級 cas和volatile 執行緒幾種狀態以及轉化 jvm記憶體模型 ......

    uj5u.com 2023-05-09 07:48:01 more
  • @RequestParam注解引數

    做業務的時候經常忘記@RequestParam注解引數,記錄一下 首先,我們要清楚@RequestParam是干什么的 @RequestParam:將請求引數系結到你控制器的方法引數上,路徑上有個引數+? @RequestParam注解引數: 語法:@RequestParam(value=https://www.cnblogs.com/zwy-yjy/archive/2023/05/08/”引數名” ......

    uj5u.com 2023-05-09 07:47:57 more
  • Django筆記三十八之發送郵件

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記三十八之發送郵件 這一篇筆記介紹如何在 Django 中發送郵件。 在 Python 中,提供了 smtplib 的郵件模塊,而 Django 在這個基礎上對其進行了封裝,我們可以通過 django.core.mail 來呼叫。 以下是本 ......

    uj5u.com 2023-05-09 07:47:50 more
  • 【深入淺出 Yarn 架構與實作】6-3 NodeManager 分布式快取

    不要跳過這部分知識,對了解 NodeManager 本地目錄結構,和熟悉 Container 啟動流程有幫助。 一、分布式快取介紹 主要作用就是將用戶應用程式執行時,所需的外部檔案資源下載快取到各個節點。 YARN 分布式快取作業流程如下: 客戶端將應用程式所需的檔案資源 (外部字典、JAR 包、二 ......

    uj5u.com 2023-05-09 07:47:41 more
  • Java后端真實、靠譜、強大的面試題網站:面試梯

    ? 本文分享一個給力的Java后端面試題網站:面試梯。 網址:https://offer.skyofit.com 這套題真實、高頻、全面、有詳細答案、保你穩過面試,讓你成為offer收割機。題目包括:Java基礎、多執行緒、JVM、資料庫、Redis、Shiro、Spring、SpringBoot、M ......

    uj5u.com 2023-05-09 07:47:35 more
  • spring事務傳播的Propagation.REQUIRES_NEW以及NEVER MANDATORY

    NEVER 不使用事務,如果當前事務存在,則拋出例外 驗證: @Service public class PrService { @Autowired PrDao dao; @Transactional public void savea() { dao.a();//保存第一條資料 saveb(); ......

    uj5u.com 2023-05-09 07:47:31 more
  • 第三章-Java的基本程式設計結構

    3.1一個簡單的Java語言程式 這是程式雖然很簡單,但是所有的Java程式都具有這種結構,因此還是值得花一些時間來研究的。首先,Java區分大小寫。如果出現了大小寫拼寫錯誤(例如:將main拼寫成Main),程式將無法運行。 下面逐行的查看這段源代碼。關鍵字pubilc稱為訪問修飾符(access ......

    uj5u.com 2023-05-09 07:47:23 more
  • python快速直白入門(半新手向,老手復習向)

    主用python做專案有一段時間,這次簡單總結學習下。為后面的專案撰寫,進行一次基礎知識的查缺補漏、 1、變數名和資料型別 """ 變數名,只能由" 數字、大小寫字母、_ " 組成,且不能以數字開頭 """ # 整數 int # hashable,不可變物件 a = 5 # 浮點數 float # ......

    uj5u.com 2023-05-09 07:41:56 more
  • Python第三方庫安裝教程、什么是第三方庫

    Python有一個全球社區:https://pypi.org/,在這里我們可以搜索任何主題的Python第三方庫。PyPI全稱是Python Package Index,指的是Python包的索引,它由PSF(Python Software Foundation)來維護,并且展示全球Python計算 ......

    uj5u.com 2023-05-09 07:41:50 more