R e i - D r e a m
for Laravel
Guest
login

最終投稿日:2026年08月23日

動的環境設定

動的環境設定とは?

開発なくして本番はありませんよね。
本番にリリースするためには開発でテストをする必要があります。
その際に、各種設定値が本番を向いていたら怖すぎます。
例えばAPIのエンドポイントが本番用になっていたらどうでしょう。
そのAPIが商品発注用だった場合、テストの度にガチに商品を発注してしまいます。
そうならないためにも環境設定ファイルは最低でも『本番用』『開発用』が必要です。
しかもそれが動的に切り替われば安心ですよね。
仕様としては本番・開発用でそれぞれ『appinfo.properties』ファイルを用意します。
各ファイルは、環境によって動的に参照するファイルを切り替えます。

appinfo.properties 作成

そこでまずは以下の様な同名のファイルを3個作成します。
src/main/resources/META-INF/appinfo.properties

APP_ENV=${DEPLOY_ENV:develop}
#APP_ENV=develop

src/main/resources/META-INF/develop/appinfo.properties

APP_DOMAIN=${APP_DOMAIN:localhost:8080/}
APP_NAME=Jakaruta-PJ

src/main/resources/META-INF/production/appinfo.properties

APP_DOMAIN=https://reidream.net/
APP_NAME=Laravel_12

それぞれの『META-INF』以降のパスに注目して下さい。
○ 無印
環境を振り分けるための『appinfo.properties』となります。
《書式》
KEY=${変数名:初期値}
または
KRY=値
『${値①:値②}』の場合は、環境変数に『値①』が定義されていればその値を、なければ『値②』(コロン以降)を使用します。
○ develop
開発用の『appinfo.properties』となります。
○ production
本番用の『appinfo.properties』となります。
例えば、任意のクラスから『appinfo.properties』の項目『APP_DOMAIN』を参照した場合、
本番なら『https://reidream.net/』開発なら『localhost:8080/』となったら便利ですよね。

appinfo.properties を動的に参照する

動的に『appinfo.properties』を参照する仕組みを作っていきましょう。
設定関連となりますので、パッケージ名は『config』とし、クラス名は『AppConfig』とします。
AppConfig.java

package config;

import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AppConfig {
    private static final ResourceBundle configBundle;
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("^\\$\\{([^:]+)(?::([^}]*))?\\}$");
    // 初期化処理
    public static void init() {}
    static {
        // 「META-INF/appinfo.properties」読込み
        ResourceBundle envBundle = ResourceBundle.getBundle("META-INF.appinfo");
        String env = envBundle.getString("APP_ENV");
        String targetBundlePath = "META-INF." + getValue(env, "") + ".appinfo";
        
        try {
            configBundle = ResourceBundle.getBundle(targetBundlePath);
        } catch (Exception e) {
            throw new RuntimeException("[appinfo.properties]のパスが不明です。");
        }
    }

    // 1つのキーを取得
    public static String get(String key) {
        String val = configBundle.containsKey(key) ? configBundle.getString(key) : null;
        return getValue(val, null);
    }
    // デフォルト値付きで取得
    public static String get(String key, String defaultValue) {
        if (!configBundle.containsKey(key)) {
            return defaultValue;
        }
        String val = configBundle.getString(key);
        return getValue(val, null);
    }
    // 「appinfo」プロパティファイルのvalueを取得する
    private static String getValue(String val, String defaultValue) {
        if (val == null || val.trim().isEmpty()) {
         return val;
        }
        val = val.trim();
        // 環境変数読込み設定「${}」の場合
        Matcher matcher = PLACEHOLDER_PATTERN.matcher(val);
        if (matcher.matches()) {
            if (matcher.group(2) == null) {
                throw new RuntimeException("「appinfo.properties」を[${}]形式にした場合は「:」は必須です。");
            }
            String systemEnv = System.getenv(matcher.group(1));
            if (systemEnv == null || systemEnv.trim().isEmpty()) {
                return matcher.group(2);
            }
            return systemEnv;
        }
        return val;
    }
}

○ 処理の流れ
① static {~}の部分
static初期化ブロック(スタティックイニシャライザ)という書式となります(イタリック文字部分)
publicメソッド『get(String key)』『get(String key, String defaultValue)』が初めて呼ばれた際にロードされます。
② static初期化ブロックがロードされると、以下設定ファイルが読込まれます。
    src/main/resources/META-INF/appinfo.properties
対象ファイル内にあるキー『APP_ENV』の値を参照します。
    ※つまり「APP_ENV」は必須のキーとなります
値は動的な『${}』で記述されている場合と、『develop』の様な固定値の場合があります。
それを判別し、適切な値を返却する非公開関数(private)『getValue』で評価します。
③『getValue』の挙動
引数の値が『null』『ブランク』の場合は、確実に固定値なのでそのまま返却します。
正規表現にて『${}』形式の場合は動的と断定し処理を以下の内容に変更します。
①' 正規表現の返却結果より『:』で分解した値を取得します。
②'『System.getenv』関数を使い、最初の文字列が環境変数に登録されているか確認します。
③' 環境変数に登録されていない場合は、分解した2つ目の文字列を返却します。
④' 環境変数に登録されている場合は、その値を返却します。
④ 変数『targetBundlePath』に2つある『appinfo.properties』までのパスを作成します。
書式 ⇒ "META-INF." + getValue(env, "") + ".appinfo";
『getValue(env, "")』の部分が①' ~ ④'までの処理結果となります。
つまり今回のサンプルの例で言えば『develop』『production』以外が返却された場合はエラーとなります。
⑤ 最後に作成した『targetBundlePath』の内容に従い『appinfo.properties』をロードします。
○ 呼び出し方
任意のクラス内で『AppConfig.get("APP_DOMAIN","")』の様に呼び出すだけです。
これで環境に適した値が動的に取得できます。
ポイント

『src/main/resources/META-INF/appinfo.properties』の内容から動的にファイルを参照しますが、
上記ファイルを固定値とした場合は、事故る可能性もありますよね。(開発なのに本番向いてた等)

もしGitHubで管理している場合は、このファイルをGit管理対象外にする事で事故を防げます。
つまりローカルのファイルは常に『APP_ENV=develop』とします。
で、本番の『main』ブランチの中身は当然『APP_ENV=production』と記述しておけば、双方の中身は変わりません。

AppConfigをアプリの初期設定クラスとして利用する

『AppConfig』はアプリ全体の『各種設定値』を管理する大事なクラスとなりました。
であればもっと大事なクラスに成長させましょう。
例えば『ログ深度』は現在『logback.xml』で初期値を決めています。
でも『本番=info』『開発=debug』と、動的に設定したいですよね。
折角の設定クラス(Config)なんだから、ここで制御しちゃいましょう。
しかし1つ大きな問題があります。それは。
    『appinfo.properties』の内容が参照されないとAppConfigクラスは『発火』しないのです。
う~ん。それではまずは『アプリ起動時に発火するクラス』を作成しましょう。
config/AppInitializer.java

package config;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class AppInitializer implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //「AppConfig」を初期化する
        AppConfig.init();
    }
}

非常にシンプルですね。
『@WebListener』を付与した事で、本クラスはアプリ起動時に1度発火します。
そしてメソッド内に注目して下さい。     『AppConfig.init();』
何処かで見ましたね。
そうです。『AppConfig』クラスのコンストラクタです。
これで『AppConfig』は起動時に発火する事ができます。
    ※試しに『static {}』内に「System.out.println("aaa");」を記述してビルドしてみてください
次に『AppConfig』でログ深度制御処理を追加します。
config/AppConfig.java

package config;

import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;

public class AppConfig {
    private static final ResourceBundle configBundle;
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("^\\$\\{([^:]+)(?::([^}]*))?\\}$");
    // 初期化処理

    public static void init() {
        // ログレベル設定
        String logLevel = get("LOG_LEVEL", "INFO");
        try {
            // 文字列 ("DEBUG", "INFO", "WARN" など) をLogbackのLevelオブジェクトに変換
            Level level = Level.toLevel(logLevel.toUpperCase(), Level.INFO);
            // Logback の LoggerContext を取得
            LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
            // ルートロガー (全体) のログレベルを変更
            context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).setLevel(level);
            System.out.println("[AppConfig] ログ深度を改定しました: " + level);
        } catch (Exception e) {
            System.err.println("[AppConfig] ログレベルの設定に失敗したため、デフォルト(INFO)を維持します: " + e.getMessage());
        }
    }
    static {
        // 「META-INF/appinfo.properties」読込み
        ResourceBundle envBundle = ResourceBundle.getBundle("META-INF.appinfo");
        String env = envBundle.getString("APP_ENV");
        String targetBundlePath = "META-INF." + getValue(env, "") + ".appinfo";
        
        try {
            configBundle = ResourceBundle.getBundle(targetBundlePath);
        } catch (Exception e) {
            throw new RuntimeException("[appinfo.properties]のパスが不明です。");
        }
    }
    // 1つのキーを取得
    public static String get(String key) {
        String val = configBundle.containsKey(key) ? configBundle.getString(key) : null;
        return getValue(val, null);
    }
    // デフォルト値付きで取得
    public static String get(String key, String defaultValue) {
        if (!configBundle.containsKey(key)) {
            return defaultValue;
        }
        String val = configBundle.getString(key);
        return getValue(val, null);
    }
    // 「appinfo」プロパティファイルのvalueを取得する
    private static String getValue(String val, String defaultValue) {
        if (val == null || val.trim().isEmpty()) {
         return val;
        }
        val = val.trim();
        // 環境変数読込み設定「${}」の場合
        Matcher matcher = PLACEHOLDER_PATTERN.matcher(val);
        if (matcher.matches()) {
            if (matcher.group(2) == null) {
                throw new RuntimeException("「appinfo.properties」を[${}]形式にした場合は「:」は必須です。");
            }
            String systemEnv = System.getenv(matcher.group(1));
            if (systemEnv == null || systemEnv.trim().isEmpty()) {
                return matcher.group(2);
            }
            return systemEnv;
        }
        return val;
    }
}

ログ深度制御を追加しました。
最後に『appinfo.properties』に以下のコードを追加しましょう。
    LOG_LEVEL=DEBUG
これでログが『DEBUG』から出力される様になります。
ログインしてコメントを残そう!!


きっぷる