🎉

【1日1zenn - day11】Spring Bootをざっくりキャッチアップ

2024/12/26に公開

day10に続き、基礎知識の仕入れに使います。
BEのコードを見ていると、Kotlin特有の書き方もあれば、Spring Boot特有の書き方もあり、なんとなく読むのはできたりしますがそのせいで処理を見落として沼ったりしてました。
前回はkotlinのドキュメントをざっくりキャッチアップしたので、今回はSpring Bootをキャッチアップした上で、次回以降は実際に処理をどれくらい早く理解できるか、ボトルネックになっているところは何か特定していきます。
そもそもSpiring Bootが何であって、何ではないのかがわかってないという感じ。

一旦記事を読む

ドキュメントを読もうとしたら概要の把握が少し難しそうだったので、一旦記事でざっくり掴んでいきます。
いつも通り思ったことを自分用に箇条書き。

1記事目

https://qiita.com/ist-a-ku/items/1d278619f241f4800bb2

  • システムの開発効率・保守性を高める「DI」と「AOP」が特徴らしい
    • DIは依存性の注入。クリーンアーキテクチャを一瞬かじったときとかに聞いた
      • クラスやインターフェースの依存関係において、普通は片方を変えたらもう片方も修正しなきゃいけないし、特に使う側が複数ある場合は色んな場所を修正する必要がある
      • これを、使われる側のクラスに@Componentと付けて、使う側のクラスに@Autowiredとつけるだけで、DIコンテナにインスタンスを作って、使う側は自動で修正されるらしい
        • 要はnew hoge()とかしなくて良くなるから、newの方も直さなきゃ的な二度手間がないイメージかな。
        • 生成されたインスタンスはBeanといって、コードで見たことあるな。他リポジトリのAPIとの繋ぎ込みでWebClientインスタンスみたいなのを作ってた気がする。
      • どう紐付ける?
        • https://www.shookuro.com/entry/2016/08/09/175801
          • Springがコンテナの中からそのプロパティの型に合うクラスを見つけて、インスタンスを裏でnewしてくれるとのこと
          • 確かにコードで見たのも型でインスタンス名っぽいのを指定してたから、納得感ある
    • AOPは、共通する処理を抜き出してまとめて管理する仕組みらしい
      • Commonみたいなのをまとめるイメージかな。
      • こっちは実務で使ってる場所なさそうだったから一旦ペンド

2記事目

よく使うアノテーションを軸に知っていきたい。
https://qiita.com/ist-a-ku/items/c20d67140402634cd5db

  • @Autowired
    • Beanを使う側につけることで、使っているクラスが変わっても修正しなくていいようにする
    • 実務だとコンストラクタで使われたりしてる
      • @Configrationの中の@Beanでインスタンス化して使ってたり。
    • https://qiita.com/bluespoon/items/6060389eab983c2e045e
      • 注入するのは、コンストラクタの前(コンストラクタインジェクション)と、セッターメソッドの前(セッターインジェクション)と、変数の前(フィールドインジェクション)らしい
      • コンストラクタインジェクションがおすすめで、しかもアノテーションは省略しても良くなってるらしい
    • まだ腹落ちはしてない(便利さを体感してない)が、まあ一旦先に進む
  • @Configuration
    • DIコンテナへのBean登録が複雑な場合に、JavaConfigというクラスに付与することでBeanを作れるらしい
  • @Bean
    • @Configurationが付与されたJavaConfig内のインスタンスを生成するメソッドに付与するとのこと
    • 複雑なときはConfigrationを付けて、newする代わりにBeanをつけるという
  • @RestController
    • そのクラスのメソッドの戻り値がHTTPのレスポンスボディで返されるらしい
      • これそんな機能があったんか。よく見る。
  • @RequestMapping
    • 実務だと@RequestMapping("/api/cart")みたいに使ってる
    • 呼び出しに対してこのクラスが実行しますよと定義する感じ
    • @GetMappingも同じ感じ
  • @RequestParam
    • これもよく見る
      • URLに含まれるクエリパラメータや、リクエストボディに含まれるパラメータを受け取るために使用するアノテーションらしい
        • Reactでいうquery.getとかURLSearchParamsとかと同じイメージかな
      • パラメータを必須にしないときにrequire=falseと指定する
      • nameやvalueを指定する
      • パラメータが指定されなかったときのdefaultValueも指定できるらしい

他に実務で出てくるアノテーションめも

  • @Value
    • https://spring.pleiades.io/spring-framework/reference/core/beans/annotation-config/value-annotations.html
      • 外部化されたプロパティを注入するために使用されるらしい
      • @Value("\${catalog.name}")みたいに書くといいっぽいんだけど、ドキュメントわかりにくいので調べる
      • https://qiita.com/kazuki43zoo/items/0ce92fce6d6f3b7bf8eb
        • データベースへの接続情報などの環境に依存する値など、ソースコード上に書きたくない値を使う手段として@Valueがあったりする
          • 実務でも他リポジトリのAPIのURLを@Valueで書いたりしてる
          • applivation.yamlに書かれているものを引っ張ってきたり。
        • DIコンテナで管理するクラスのフィールド、setterメソッド、コンストラクタ引数などに@Value(${設定名:デフォルト値})の形式で指定すると、指定した設定の値が注入されるらしい
@Value("${app.timezone:UTC}") 
private TimeZone timezone; // ← "app.timezone"の設定値がインジェクションされる
  - timezone型のTimeZoneに、 app.timezoneで書かれてる内容が入ってくる感じか

kotlinとSpringの記事を読む

https://akkinoc.dev/posts/2021/05/23/java-to-kotlin/

  • これが結構読みたかったやつかも。感じたメリット
    • 同じ処理をするときにコード量を2-3割減らせる
      • 確かに依存注入とか、@Valueとか、使わないと記述量増えそう
      • 2-3割も変わるかな?とは思うが、これはKotlinの力な感じか。
    • Kotlinのdata class を使うとtoStringとかサクッと作れてLombokが不要になる
      • Javaで定義したことないけど確かに便利そう
    • 文字列に変数を埋め込みやすい
      • これは確かに便利に感じてる。
    • null安全にできる
      • これはTypeScriptもそうだからあまり革新性を感じてない
    • コレクション操作が充実してる
  • テストしやすさは確かにありそうだけどまだそれほど体感してない
  • application.yamlから設定値を引っ張って来れるのは感じた
    • マイクロサービスアーキテクチャだと特に楽みたいな話あるんかな
  • @Configurationは、Springの色々な設定をJavaコード上で行うためのアノテーションらしい。そうなんだ。

実務のコードを読んで気になったのを調べる

companion object

これはkotlin
https://qiita.com/tkhs0604/items/261e94a42b7097dfd204

  • 1クラス内に1つだけ宣言可能
  • クラスに属するSingletonが作成できる
  • そもそもSingletonって?
    • https://magazine.techacademy.jp/magazine/18939
    • 複数のインスタンスが作られると困るときに制限する
    • 何かを実行したことでインスタンスが変わったりしないようにするっぽい?
    • GPTに聞いた
      • Javaのstaticみたいにインスタンス化しないでもアクセスできるメンバーを定義できる
        • クラスの初期化のタイミングでcompanion objectとして作っておけば、クラス名.オブジェクト名とかで呼び出せる
        • クラス内のグローバル変数的なテンションで使えそうな雰囲気

WebSession attributes

これはSpring
https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/web/server/WebSession.html
https://www.memory-lovers.blog/entry/2018/02/04/171013
ドンピシャなドキュメント見つからなかったけど、WebSession.getAttributesとかでセッションに保存されている属性名を取り出せる
https://www.tsuzukanai.com/spring-boots-save-session/

    @GetMapping("/setSessionData")
    public String setSessionData(HttpSession session) {
        // セッションにデータを保存
        session.setAttribute("key", "value");
        return "Data set in session";
    }
```とかでセットできる

@GetMapping("/getSessionData")
public String getSessionData(HttpSession session) {
    // セッションからデータを取得
    String value = (String) session.getAttribute("key");
    return "Data from session: " + value;
}
```

とかでゲットできる。

suspend

https://qiita.com/duke105/items/b5be074c79c6bed4d560
非同期処理が含まれる場合はsuspendで指定しないとエラーになる

runCatching

https://jp-seemore.com/app/15138/
以下みたいに書ける

fun main() {
    val result = runCatching {
        // エラーが発生する可能性のある処理
        10 / 2
    }

    result.onSuccess { 
        println("成功時の結果: $it") 
    }.onFailure { 
        println("エラー発生: ${it.message}") 
    }
}

結果はResult型で取得されて、onSuccessとかonFailureとかで簡単に分岐させられる。

fold

kotlin。runCatchingとかと一緒に出てくる
https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/fold.html
再帰的な処理を書けるとのことで、
要は成功した場合と失敗した場合の処理を一つのブロックで書けるとか。
ドキュメントのサンプルは以下。

val fruits = listOf("cherry", "blueberry", "citrus", "apple", "apricot", "banana", "coconut")

val evenFruits = fruits.groupingBy { it.first() }
    .fold({ key, _ -> key to mutableListOf<String>() },
          { _, accumulator, element ->
              accumulator.also { (_, list) -> if (element.length % 2 == 0) list.add(element) }
          })

val sorted = evenFruits.values.sortedBy { it.first }
println(sorted) // [(a, []), (b, [banana]), (c, [cherry, citrus])]

これをfold使わないと以下とかになるっぽい

val fruits = listOf("cherry", "blueberry", "citrus", "apple", "apricot", "banana", "coconut")

// グループ化して初期化
val grouped = mutableMapOf<Char, Pair<Char, MutableList<String>>>()
for (fruit in fruits) {
    val key = fruit.first()
    grouped.putIfAbsent(key, key to mutableListOf())
}

// 条件に応じてリストを更新
for (fruit in fruits) {
    if (fruit.length % 2 == 0) {
        val key = fruit.first()
        val (_, list) = grouped[key] ?: continue
        list.add(fruit)
    }
}

// ソート
val sorted = grouped.values.sortedBy { it.first }
println(sorted) // [(a, []), (b, [banana]), (c, [cherry, citrus])]

初期値の生成と更新処理を一気に行うことができるのは確かに便利だ。
けどエラーハンドリングの場合は初期値とかじゃない雰囲気だから、もうちょい知る必要ありそう。

LinkedMultiValueMap

queryParamsを作るときによく見る。
GPTに聞いたら、順番を保持できたり、各キーに対してListっぽく複数の値を格納したり、.addとか簡単に行ったりできるらしい。
https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/util/LinkedMultiValueMap.html
確かに色々書いてある。一旦addしてパラメ作るのを今見てるけど、それ以外にも色々ありそう

おわりに

なんか今日やったことが合っていたのかわからん
とりあえず色々調べたという感じで、無限の沼にただ沈んでいった気もする。。
眠いので寝ます。

Discussion