設定値の外部化
このレッスンでは、接続先やポート番号といった環境ごとに変わる値を、コードから追い出す方法を身につけます。
ハードコードすると環境を移せません
データベースの接続先を Java のコードに直接書いてしまうと、開発機と本番で別々のソースを持つことになります。パスワードを書けば、そのままリポジトリに残って外へ出ます。環境によって変わる値は、コードではなく設定として外に置く。これが基本です。
Spring Boot は src/main/resources の下に置いた application.properties または application.yml を起動時に自動で読み込みます。同じ内容を 2 つの書き方で表すと次のようになります。
Properties
server.port=8080
app.name=shop
app.mail.from=noreply@example.com
app.mail.retry-count=3YAML
server:
port: 8080
app:
name: shop
mail:
from: noreply@example.com
retry-count: 3properties はキーが全部書かれていて検索しやすく、yml は階層が視覚的で長いキーの重複が減ります。どちらを選んでも読み込まれる中身は同じです。1 つのプロジェクトではどちらかにそろえてください。
取り出し方は @Value と @ConfigurationProperties
1 つの値をぱっと取り出したいときは @Value が手軽です。
Java
@Component
public class MailSender {
@Value("${app.mail.from}")
private String from;
@Value("${app.mail.retry-count:3}")
private int retryCount; // 設定が無ければ 3 になる
}角括弧ではなく波括弧とドル記号で囲む記法で、中に既定値も書けます。ただし値が増えてくると @Value は散らかります。関連する値をまとめて 1 つの型に束ねるのが @ConfigurationProperties です。
Java
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String from, int retryCount) {}この 1 行で app.mail.from と app.mail.retry-count が対応するフィールドへ入ります。ハイフンつなぎのキーがキャメルケースのフィールドへ結び付く点も便利です。型が付くので誤りに早く気づけますし、この型を注入すれば設定がどこで使われているかも追えます。設定が 3 つ以上あるなら、迷わずこちらを選んでください。
プロファイルで環境ごとに切り替えます
環境ごとの差分は、プロファイルという仕組みで分けます。application-dev.properties と application-prod.properties を用意し、起動時に有効なプロファイル名を指定すると、共通の application.properties に加えてその環境用のファイルが重ねて読み込まれます。同じキーがあれば、プロファイル側が勝ちます。
最後に勝つのは環境変数です
設定値の出どころは 1 つではありません。優先順位は大まかに、コマンドライン引数、環境変数、プロファイル別のファイル、共通のファイル、コード内の既定値の順で、上にあるものが下を上書きします。この順序があるおかげで、コンテナで動かすときにファイルを書き換えることなく、環境変数だけで接続先を差し替えられます。
キーの対応にも決まりがあります。app.mail.from に対する環境変数は APP_MAIL_FROM です。ドットをアンダースコアにして大文字にする、と覚えてください。パスワードや API キーは、ファイルではなくこの経路で渡すのが定石です。
演習で書くもの
Spring がやっている「設定ファイルを読んでキーで引き、無ければ既定値を使う」という処理を、自分で書きます。コメント行と空行を飛ばす部分が、実際のファイルを読むときに必ず要る処理です。
要件
- 空行と井桁で始まるコメント行を読み飛ばすこと
- 最初のイコールで区切り、キーと値の前後の空白を取り除くこと
- 同じキーはあとに書かれた方を採用し、見つからなければ既定値を返すこと
入出力例
lookupProperty("# app settings
server.port=8080
app.name=shop
# mail
app.mail.from=noreply@example.com
", "server.port", "8000") → "8080"
lookupProperty("# app settings
server.port=8080
app.name=shop
# mail
app.mail.from=noreply@example.com
", "app.mail.from", "none") → "noreply@example.com"
lookupProperty("# app settings
server.port=8080
app.name=shop
", "app.timeout", "30") → "30"
lookupProperty("#server.port=9999
", "server.port", "8080") → "8080"
lookupProperty("app.name = shop
", "app.name", "x") → "shop"
lookupProperty("app.name=first
app.name=second
", "app.name", "x") → "second"
lookupProperty("", "app.name", "fallback") → "fallback"