動的環境設定
動的環境設定とは?
本番にリリースするためには開発でテストをする必要があります。
その際に、各種設定値が本番を向いていたら怖すぎます。 例えばAPIのエンドポイントが本番用になっていたらどうでしょう。
そのAPIが商品発注用だった場合、テストの度にガチに商品を発注してしまいます。 そうならないためにも環境設定ファイルは最低でも『本番用』『開発用』が必要です。
しかもそれが動的に切り替われば安心ですよね。 仕様としては本番・開発用でそれぞれ『appinfo.properties』ファイルを用意します。
各ファイルは、環境によって動的に参照するファイルを切り替えます。
appinfo.properties 作成
APP_ENV=${DEPLOY_ENV:develop}
#APP_ENV=develop
APP_DOMAIN=${APP_DOMAIN:localhost:8080/}
APP_NAME=Jakaruta-PJ
APP_DOMAIN=https://reidream.net/
APP_NAME=Laravel_12
《書式》
または
KRY=値
本番なら『https://reidream.net/』開発なら『localhost:8080/』となったら便利ですよね。
appinfo.properties を動的に参照する
設定関連となりますので、パッケージ名は『config』とし、クラス名は『AppConfig』とします。
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;
}
}
publicメソッド『get(String key)』『get(String key, String defaultValue)』が初めて呼ばれた際にロードされます。
※つまり「APP_ENV」は必須のキーとなります
値は動的な『${}』で記述されている場合と、『develop』の様な固定値の場合があります。
それを判別し、適切な値を返却する非公開関数(private)『getValue』で評価します。
正規表現にて『${}』形式の場合は動的と断定し処理を以下の内容に変更します。
②'『System.getenv』関数を使い、最初の文字列が環境変数に登録されているか確認します。
③' 環境変数に登録されていない場合は、分解した2つ目の文字列を返却します。
④' 環境変数に登録されている場合は、その値を返却します。
『getValue(env, "")』の部分が①' ~ ④'までの処理結果となります。 つまり今回のサンプルの例で言えば『develop』『production』以外が返却された場合はエラーとなります。
これで環境に適した値が動的に取得できます。
AppConfigをアプリの初期設定クラスとして利用する
であればもっと大事なクラスに成長させましょう。 例えば『ログ深度』は現在『logback.xml』で初期値を決めています。
でも『本番=info』『開発=debug』と、動的に設定したいですよね。 折角の設定クラス(Config)なんだから、ここで制御しちゃいましょう。
しかし1つ大きな問題があります。それは。
『appinfo.properties』の内容が参照されないとAppConfigクラスは『発火』しないのです。 う~ん。それではまずは『アプリ起動時に発火するクラス』を作成しましょう。
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』でログ深度制御処理を追加します。
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』から出力される様になります。





『src/main/resources/META-INF/appinfo.properties』の内容から動的にファイルを参照しますが、
もしGitHubで管理している場合は、このファイルをGit管理対象外にする事で事故を防げます。上記ファイルを固定値とした場合は、事故る可能性もありますよね。(開発なのに本番向いてた等)
つまりローカルのファイルは常に『APP_ENV=develop』とします。 で、本番の『main』ブランチの中身は当然『APP_ENV=production』と記述しておけば、双方の中身は変わりません。