Dev Study
← Spring コース

23. Spring が決めていること、決めていないこと

アプリのソースの置き方について、 が既定で決めているのは2つだけです。( を持ち、 を付けた)のとその配下から部品を探すことと、 や などの既定の置き場所です。controller や service というフォルダ名は見ず、(クラスに付ける印)で見分けます。

同じ5クラスを、層別、機能別、1か所に平置き、banana や x/y/z という名前、の4通りに置いても応答は同じでした。Next.js ではフォルダの位置が URL になり、page.tsx というファイル名に意味があります。 では URL は の引数で決まり、ファイルの場所は関係しません。NestJS はに部品を列挙しますが、Spring Boot は列挙せず配下を走査します。

落とし穴は起点です。起動クラスだけを隣のパッケージへ移すと、起動は成功し、エラーも警告も出ないまま、リクエストが 404 になりました。起点の配下に部品が無くなったからです。起点は指定で変えられるので、これは既定の話です。また2つだけというのは Web と設定ファイルの範囲のことで、データベース用のクラスの探索など、場所に意味がある箇所は他にもあります。

点線の付いた語はタップすると説明が出ます。用語集を開く →

Spring Boot が部品を見つける道筋(フォルダ名を banana などに変えた変種。Boot 4.1.1 / JDK 25)
com.example.shop.ShopApplication起動クラス。@SpringBootApplication が付いている
このパッケージの配下を走査する
banana/OrderController@RestController
x/y/z/OrderService@Service
misc/stuff/OrderRepository@Repository
フォルダ名ではなくアノテーションで見分けて登録する
GET /orders/1 → 200層別・機能別・平置きの配置と同じ応答
URL は @GetMapping の引数で決まる
  • 起動クラスだけを com.example.shop.app に移した変種では、起動は成功し、/orders/1 は 404 でした(各変種1回の実行)。
  • Boot のリファレンスは Spring Boot does not require any specific code layout to work. と書き、起動クラスを他のクラスより上のパッケージに置くことを勧めています。例に挙げている配置は機能別です。
  • 設定ファイルはクラスパス直下と config/ の下が読まれ、settings/ の下は読まれませんでした。static/ や public/ のファイルは配信され、assets/ は 404 でした。
  • Next.js の公式ドキュメントは Folders define URL segments. と書いています。ルーティングに使う場所以外の整理の仕方については unopinionated だとしています。

サンプルコード(フレームワーク環境が必要なため表示のみ)

// フォルダ名(パッケージ名)を banana にした変種。Spring Boot 4.1.1 / JDK 25(ログ出力の1行は省略)
package com.example.shop.banana;

import com.example.shop.things.Order;
import com.example.shop.x.y.z.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

// Spring が見るのはこの印。controller というフォルダ名は見ていない
@RestController
public class OrderController {

    private final OrderService service;

    public OrderController(OrderService service) {
        this.service = service;
    }

    // URL を決めるのはこの引数。ファイルの置き場所は URL に関係しない
    @GetMapping("/orders/{id}")
    public Order get(@PathVariable long id) {
        return service.find(id);
    }
}

// 起動クラスは com.example.shop.ShopApplication(このパッケージの上)
// $ curl http://localhost:8080/orders/1
// {"id":1,"item":"apple","quantity":3}

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(5本) →

学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。

公式ドキュメントで詳しく ↗