iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0

前言

昨天把 Redis Pub/Sub 的功能微調後,測試已經可以在分布式的環境下透過 WebSocket Session 收到通知了,基本上我們要完成的業務邏輯差不多就這樣,接下來第三階段會來把這些內容優化、測試跟部署,算是填一下前面的坑,今天先把前幾天的功能做一點改善,把使用頻繁的 ObjectMapper 用工具類包裝起來,作為一個單例以降低系統的開銷。

實作:ObjectMapper 單例優化

今天要來把在我們系統中使用頻率很高的 ObjectMapper 單例化,在諸如 Kafka Consumer、Redis Listener 等等類別都需要 ObjectMapper 來序列化或反序列化 Java 物件,這個實例的使用率很高,每次在一個類別都要重複創建的話,對效能、JVM 記憶體空間會有一定程度的開銷。

特別是 ObjectMapper 這東西創建的成本還頗高,需要掃描和緩存類別的反射信息,又要初始化各種序列化跟反序列化器、建立一些配置等等。既然在各個類別的使用邏輯都相同的話,就統一在第一次類加載時,就創建 ObjectMapper 就好。

單例模式確保一個類別在整個應用程式中就只有一個實例,並提供全域的訪問點。簡單實作如下:

public class JsonUtils {

    private static final ObjectMapper OM;

    static {
        OM = new ObjectMapper();
        OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        OM.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        OM.registerModule(new JavaTimeModule());
    }

    public static String toJson(Object object) {
        try {
            return OM.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new BaseException(StatusCode.UNKNOW_ERR, "Json parse failed: " + e.getMessage());
        }
    }

    public static <T> T fromJson(String json, Class<T> clazz) {
        try {
            return OM.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            throw new BaseException(StatusCode.UNKNOW_ERR, "Json parse failed " + e.getMessage());
        }
    }

}

如此一來我們就可以在系統中的任何一個地方,在不用創建實例的情況下調用這個工具類別:

@KafkaListener(topics = Topic.NOTIFICATION)
    public void processMailNotification(String content) {
        log.info("Receive topic [{}] and message=[{}]", Topic.NOTIFICATION, content);
        try {
        
		        // 直接調用包裝好的方法,將字串轉換成 Json 物件
            var notification = JsonUtils.fromJson(content, NotificationTO.class);
            notificationService.pushToUsers(notification);
        } catch (Exception e) {
            log.warn("Failed to process mail task", e);
        }
    }

趁著這個機會來複習一下 Spring 的生命週期跟 Java 的基本常識,寫到這可能會出現一個疑問:為何 JsonUtils 不用注入呢?

這是因為在這類別裡用 static 來初始化方法、屬性跟配置,它的生命週期由 JVM 管理,不需要經過 Spring 容器,當某個類別第一次在裡面調用 JsonUtils 方法時,JVM 才會第一次進行類加載(Lazy loading),把這些東東初始化出來,如果沒有任何地方用到 JsonUtils,它的 static 初始化塊永遠不會執行,所以 JsonUtils 上不需要添加 @Component。

但像是那些由 Spring 容器管理的 Bean 也是一種單例的類別,在容器啟動時,Spring 會去掃描 class 上有任何 @Component、@Service 等註解的類別,將他們放到容器內,等到有其他地方要用時就直接注入即可,不用再創建新的實例。

總結

今天比較開心的是可以藉由一些簡單的實作來複習、釐清一些以前不太熟悉的概念,包含 Spring 生命週期、JVM 實例的生命週期等等,這也是我覺得自己在鐵人賽一個蠻大的收穫,繼續努力吧。


上一篇
Day 20 | Redis Pub/Sub 分布式 WebSocket 實作:測試
系列文
系統設計一招一式:最基本的功練到爛熟就是殺手鐧,從單體架構到分布式系統的 Lab 實作筆記21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言