iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
Software Development

六邊形戰士程式設計系列 第 19

D19 - 樹狀搜尋問題 Spring 版 Kotlin篇

  • 分享至 

  • xImage
  •  

要是說 AOP 這個概念在哪裡發光發熱,我第一個想到的就是 Spring,在處理繁瑣重複的 Web 服務時實在太需要 AOP 的輔助來降低複雜度了,所以我們今天會把討論了好久的樹狀搜尋問題搬到 Spring 上,借助工具展示 AOP 功能有多強大。

今天會有比較多環境設定的部分,可以直接跳到實作章節

環境設定

  1. 下載 Intellij IDEA (community 版本即可)
  2. https://start.spring.io/ 下載範本,設定如下
  3. 使用 IDE 打開專案,等待專案初始化成功 (右下角會有進度條)
  4. 在 pom 裡面新增以下依賴
    <!-- AOP 工具包 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- Web 工具包 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 前面使用過的 arrow-kt,強化錯誤處理 -->
    <dependency>
        <groupId>io.arrow-kt</groupId>
        <artifactId>arrow-core</artifactId>
        <version>1.2.4</version>
    </dependency>
    <dependency>
        <groupId>io.arrow-kt</groupId>
        <artifactId>arrow-fx-coroutines</artifactId>
        <version>1.2.4</version>
    </dependency>
    <!-- 使 spring 支援 kotlin coroutine -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.kotlinx</groupId>
        <artifactId>kotlinx-coroutines-reactor</artifactId>
        <version>1.6.0</version>
    </dependency>
    
  5. 參考以下資料夾結構新增檔案

程式實作

NodeService.kt

D15 - 樹狀搜尋問題 非同步版 Kotlin 篇 的內容包裝成 Spring Service

package com.example.demo

import arrow.core.*
import arrow.fx.coroutines.parMap
import org.springframework.stereotype.Service

data class Node(
    val id: String,
    val text: String,
    val childrenIds: List<String>,
)

class NodeNotFoundError(
    val id: String,
) : Exception()

@Service
class NodeService {
    private suspend fun getNode(id:String): Either<NodeNotFoundError, Node> {
        return when (id) {
            "a" -> Node("a", "a", childrenIds = listOf("a1", "a2", "a33")).right()
            "a1" -> Node("a1", "a1", childrenIds = listOf("a11")).right()
            "a11" -> Node("a11", "a11", childrenIds = listOf()).right()
            "a2" -> Node("a2", "a2", childrenIds = listOf()).right()
            "a3" -> Node("a3", "a3", childrenIds = listOf()).right()
            else -> NodeNotFoundError(id).left()
        }
    }

    suspend fun getNameById(
        id: String,
        target: String,
    ): String =
        getNode(id)
            .fold(
                ifRight = { result ->
                    if (result.id == target) {
                        result.text
                    } else {
                        result.childrenIds
                            .parMap { getNameById(it, target) }
                            .find { it == target }
                    }
                },
                ifLeft = { null },
            ).toOption().getOrElse { "not found" }
}

NodeController.kt

建立一個控制器,讓我們能透過 HTTP GET 請求觸發 getNameById 服務。

package com.example.demo

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
class NodeController {
    private val nodeService = NodeService()

    @GetMapping("getNode")
    suspend fun getNode(
        @RequestParam("id")
        id: String,
        @RequestParam("target")
        target: String
    ): String {
        return nodeService.getNameById(id,target)
    }
}

LogAspect.kt

實際建立一個切面類別,利用 @Beforeˋ指定切入時機、利用 "execution(* com.example.demo.*.*(..))" 指定切入點是 demo package 中的所有 method

package com.example.demo

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.stereotype.Component

@Component
@Aspect
class LogAspect {

    @Before("execution(* com.example.demo.*.*(..))")
    fun logBeforeMethod() {
        println("A method is being called...")
    }
}

實際執行

  1. 前往 http://localhost:8080/getNode?id=a&target=a1
  2. 每次重新整理都會印出以下訊息

延伸必讀

有了 Spring 的完整支援在整個 AOP 中,我們唯一所需要學習的就是,如何選擇要切入時機與切入點

切入時機可以選擇的不多,包含以下這些 Declaring Advice

  • Before
  • After
  • AfterThrowing
  • AfterRetruning
  • Around

切入點就複雜多了,建議參考這篇文章 Declaring a Pointcut


上一篇
D18 - 樹狀搜尋問題 監控版 TS優化篇
下一篇
D20 - 樹狀搜尋問題 監控版 Python篇
系列文
六邊形戰士程式設計30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言