首頁 >Java >java教程 >在Java中使用註解來制定策略

在Java中使用註解來制定策略

Susan Sarandon
Susan Sarandon原創
2025-01-10 12:13:43276瀏覽

Usando annotations em Java para fazer um strategy

我在工作中遇到了一個非常有趣的情況,我想在這裡分享解決方案。

想像一下您需要處理一組資料。為了處理這組數據,您有幾種不同的策略。例如,我需要建立如何從 S3 取得資料集合、本機儲存庫中的範例或作為輸入傳遞的策略。

決定這項策略的人就是提出請求的人:

我想取得S3中的資料。取 X 天 H1 和 H2 之間產生的數據,該數據來自 Abóbora 用戶端。取得最近3000條符合此條件的數據。

或:

拿你那裡的範例數據,複製10000次來進行壓力測試。

或甚至:

我有這個目錄,你也可以存取它。取得該目錄中的所有內容並遞歸到子目錄中。

最後:

取得輸入中的這個資料單元並使用它。

如何實施?

我的第一個想法是:「如何在 Java 中定義輸入的形狀?」

我得出了第一個結論,這對於該項目非常重要:「你知道嗎?我不會定義形狀。添加一個可以處理它的 Map。」

最重要的是,由於我沒有在 DTO 中放置任何形狀,因此我可以完全自由地嘗試輸入。

因此,在建立概念驗證後,我們遇到了這樣的情況:我們需要擺脫 POC 壓力,轉向接近實際使用的東西。

我所做的服務是驗證規則。基本上,當更改規則時,我需要採用該規則並將其與生產應用程式中發生的事件進行比對。或者,如果應用程式發生變更且沒有錯誤,則期望對相同資料的相同規則的決策將保持相同;現在,如果使用相同資料集的相同規則的決策發生了變化……那麼,這就是潛在的麻煩。

所以,我需要這個應用程式來運行規則的回測。我需要點擊真實的應用程式發送資料以進行評估和相關規則。它的用途相當多元:

  • 驗證更新應用程式時的潛在偏差
  • 驗證更改後的規則是否保持相同的行為
    • 例如,最佳化規則執行時間
  • 檢查規則的變化是否產生了預期的決策變化
  • 驗證應用程式中的變更確實提高了效率
    • 例如,在啟用 JVMCI 的情況下使用新版本的 GraalVM 會增加我可以發出的請求數量嗎?

因此,為此,我需要一些關於事件起源的策略:

  • 從S3取得真實數據
  • 取得儲存庫中作為樣本的資料並複製多次
  • 從本機上的特定位置取得資料

而且我還需要與我的規則不同的策略:

  • 透過輸入傳遞
  • 使用快速運轉的存根
  • 使用以生產規則為基礎的樣本
  • 在我的機器上使用此路徑

如何處理這個問題?好吧,讓用戶提供數據吧!

策略 API

你知道關於 json-schema 的一些總是引起我注意的事情嗎?這裡:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}

這些欄位以 $ 開頭。在我看來,它們是用來表示元資料的。那麼為什麼不在資料輸入中使用它來指示正在使用哪種策略的元資料呢?

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}

例如,我可以訂購 15,000 份我擁有的資料作為樣本。或從 S3 請求一些東西,在 Athena 中查詢:

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "Abóbora"
    },
    //...
}

或是在本地路徑中?

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}

這樣我就可以委託選擇未來的策略。

程式碼審查和外觀

我處理策略的第一個方法是:

public DataLoader getDataLoader(Map<String, Object> inputDados) {
    final var strategy = (String) inputDados.get("$strategy");
    return switch (strategy) {
        case "localpath" -> new LocalpathDataLoader();
        case "sample" -> new SampleDataLoader(resourcePatternResolver_spring);
        case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client);
        default -> new AthenaQueryDataLoader(athenaClient, s3Client);
    }
}

所以我的架構師在程式碼審查期間問了兩個問題:

  • 「為什麼你實例化所有東西而不讓 Spring 為你工作?」
  • 他在程式碼中建立了一個DataLoaderFacade並放棄了它半成品

從中我明白了什麼?使用外觀將處理委託給正確的角落並......放棄手動控制是一個好主意?

嗯,很多魔法都是因為春天而發生的。既然我們在一個擁有 Java 專業知識的 Java 之家,為什麼不使用慣用的 Java/Spring,對吧?僅僅因為作為一個個體覺得有些事情難以理解並不一定意味著它們很複雜。那麼,讓我們擁抱 Java 依賴注入魔法的世界。

創建 façade 對象

曾經是:

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);

變成:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}

所以我的控制器層不需要管理這個。把它留給門面。

那麼,我們要如何做立面呢?好吧,首先,我需要將所有物件注入其中:

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}

好的,對於主 DataLoader,除了 @Service 之外,我還將其寫成 @Primary。剩下的我就用@Service寫下來。

在這裡測試一下,設定 getDataLoader 傳回 null 只是為了嘗試 Spring 如何呼叫建構函式...它起作用了。現在我需要記下元資料每個服務他們使用什麼策略...

如何做到這一點...

嗯,看!在 Java 中,我們有註解!我可以建立一個 runtime 註釋,其中包含該元件使用的策略!

所以我可以在 AthenaQueryDataLoader 中擁有類似的東西:

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "Abóbora"
    },
    //...
}

我也可以有別名,為什麼不呢?

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}

並展示!

但是要如何建立這個註解呢?好吧,我需要它有一個字串向量的屬性(Java 編譯器已經處理提供一個單獨的字串並將其轉換為具有 1 個位置的向量)。預設值為值。看起來像這樣:

public DataLoader getDataLoader(Map<String, Object> inputDados) {
    final var strategy = (String) inputDados.get("$strategy");
    return switch (strategy) {
        case "localpath" -> new LocalpathDataLoader();
        case "sample" -> new SampleDataLoader(resourcePatternResolver_spring);
        case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client);
        default -> new AthenaQueryDataLoader(athenaClient, s3Client);
    }
}

如果註解欄位沒有值,我需要將其明確化,這看起來很難看,如 EstrategiaFeia 註解中所示:

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);

我認為這聽起來不太自然。

好吧,有鑑於此,我們仍然需要:

  • 從傳遞的物件中提取類別註解
  • 建立字串映射 右箭頭 🎜 > 資料載入器(或字串 右箭頭

🎜 >

T)


提取註釋並組裝地圖
dataLoaderFacade.loadData(inputDados, workingPath);

要提取註釋,我需要存取物件類別:

@Service // para o Spring gerenciar esse componente como um serviço
public class DataLoaderFacade implements DataLoader {

    public DataLoaderFacade(DataLoader primaryDataLoader,
                            List<DataLoader> dataLoaderWithStrategies) {
        // armazena de algum modo
    }

    @Override
    public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) {
        return getDataLoader(input).loadData(input, workingPath);
    }

    private DataLoader getDataLoader(Map<String, Object> input) {
        final var strategy = input.get("$strategy");
        // magia...
    }
}

除此之外,我可以問一下這個類別是否帶有像Strategy這樣的註解:

@Service
@Primary
@Estrategia("athena-query")
public class AthenaQueryDataLoader implements DataLoader {
    // ...
}

你還記得它有values欄位嗎?好吧,這個欄位回傳一個字串向量:


表演!但我有一個挑戰,因為之前我有一個 T 類型的對象,現在我想將同一物件映射到 (T, String)[]。在流程中,執行此操作的經典操作是 flatMap。 Java 也不允許我突然回傳這樣的元組,但我可以用它來建立一筆記錄。 它看起來像這樣:
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
    "$vocabulary": {
        //...
    }
}

如果有一個物件沒有標註策略怎麼辦?會給NPE嗎?最好不要,我們在 NPE 之前過濾掉它:

{
    "dados": {
        "$strategy": "sample",
        "copias": 15000
    },
    //...
}

有鑑於此,我還需要整理一張地圖。而且,好吧,請看:Java 已經為此提供了一個收集器! Collector.toMap(keyMapper, valueMapper)

{
    "dados": {
        "$strategy": "athena-query",
        "limit": 15000,
        "inicio": "2024-11-25",
        "fim": "2024-11-26",
        "cliente": "Abóbora"
    },
    //...
}

到目前為止,還好。但 flatMap 特別困擾我。有一個名為 mapMulti 的新 Java API,它具有倍增的潛力:

{
    "dados": {
        "$strategy": "localpath",
        "cwd": "/home/jeffque/random-project-file",
        "dir": "../payloads/esses-daqui/top10-hard/"
    },
    //...
}

美麗。我為 DataLoader 獲取了它,但我還需要為 RuleLoader 做同樣的事情。或者也許不是?如果您注意到,此程式碼中沒有任何特定於 DataLoader 的內容。我們可以抽象化這段程式碼! !

public DataLoader getDataLoader(Map<String, Object> inputDados) {
    final var strategy = (String) inputDados.get("$strategy");
    return switch (strategy) {
        case "localpath" -> new LocalpathDataLoader();
        case "sample" -> new SampleDataLoader(resourcePatternResolver_spring);
        case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client);
        default -> new AthenaQueryDataLoader(athenaClient, s3Client);
    }
}

立面之下

純粹出於功利的原因,我將這個演算法放在註解中:

final var dataLoader = getDataLoader(inputDados)
dataLoader.loadData(inputDados, workingPath);

立面呢?好吧,工作也這麼說。我決定抽像一下:

dataLoaderFacade.loadData(inputDados, workingPath);

立面看起來像這樣:

@Service // para o Spring gerenciar esse componente como um serviço
public class DataLoaderFacade implements DataLoader {

    public DataLoaderFacade(DataLoader primaryDataLoader,
                            List<DataLoader> dataLoaderWithStrategies) {
        // armazena de algum modo
    }

    @Override
    public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) {
        return getDataLoader(input).loadData(input, workingPath);
    }

    private DataLoader getDataLoader(Map<String, Object> input) {
        final var strategy = input.get("$strategy");
        // magia...
    }
}

以上是在Java中使用註解來制定策略的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn