SpringBoot で起動引数による環境切替を行う

## はじめに

SpringBoot でバックエンドを開発しているときにある話。

昨今、Dockerが出てきてから結合から本番までワンモジュール、ワンソースで最後まで管理する傾向にある。

各環境の区別は、環境引数を外から渡すだけ、という設計方針にした際に、
では、中身のSpringBootで作ったアプリケーションのモジュールはどういう風にすればよいのかをまとめる



## 環境の数が少ないなら

#### SpringBootのProfileで切替すればよい。


下記に公式情報があるため、こちらで実現できます。
[公式](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-profiles)

起動引数に下記を含めれば、
```-Dspring.profiles.active=dev1
```
下記ファイルが読み込まれる仕様である。
```src/main/resource/application-dev1.properties」
```


## ビルドでプロパティファイルを切替

#### mavenのprofile切替が簡単。


モジュールを固める、jar やwar などにするときに、maven の機能で設定ファイルの切り替えを行う。
ビルド時にファイル移送を実施するため、設定ファイルの切替で最も簡単な方法。


```mvn package -P dev01
```


pom.xml

```xml


<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-war-plugin</artifactId>
	<configuration>
		<webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>
		<webResources>
			<resource>
				<directory>${basedir}/release/${project.stage}</directory>
			</resource>
		</webResources>
		<useCache>false</useCache>
<!-- 警告エラーが発生するため追加-->					<failOnMissingWebXml>false</failOnMissingWebXml>
		<archive>
			<manifest>
<!-- main クラス指定（必要に応じて）-->							<mainClass>jp.co.●●.●●.Main</mainClass>
			</manifest>
		</archive>
	</configuration>
</plugin>

<profiles>
	<profile>
		<id>dev1</id>
		<activation>
			<activeByDefault>true</activeByDefault>
		</activation>
		<properties>
			<project.stage>dev01</project.stage>
		</properties>
	</profile>

	<profile>
		<id>dev02</id>
		<activation>
			<activeByDefault>false</activeByDefault>
		</activation>
		<properties>
			<project.stage>dev02</project.stage>
		</properties>
	</profile>
</profiles>
```
シェルコマンドを内部に組み込むことも可能なため、
maven に慣れている人にとっては、なじみ深い。


##  ワンモジュールで環境切替したい

#### 今までの問題点
1つ目、springboot のprofile 切替方法は、プロパティファイルの形式が固定されており、複数のファイルの読み込みなどの設定が非常にやりづらい。特に環境が増えた場合に柔軟性に欠ける。

2つ目、maven のprofile 切替方法は、複数ファイルの読み込みは、特定の環境には特定のモジュールしか動かない設定のため、デプロイシェル等に誤りや手順ミスがあった場合に、障害が発生する可能性が高い。


#### spring bootのprofile 機能拡張を利用する。

ApplicationContextInitializerを利用して起動時にprofileを設定するようにする。

Javaの起動引数指定 → ApplicationContextInitializerでprofileに指定
この方法の場合、profile 指定するときに、グルーピングできるようになり、
dev01で実行すると、dev とdev01の二つの情報を含めることができる。

java -Dproject.stage=dev01  hoge.war 

```java 
public class SpringActiveProfileInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
	/** ロガー */
	private static final Logger LOG = LoggerFactory.getLogger(SpringActiveProfileInitializer.class);

	@Override
	public void initialize(final ConfigurableApplicationContext applicationContext) {

		// 起動引数を取得（dev01）
		String projectStage = System.getProperty("project.stage");

		// 起動グループを取得（dev）
		String prjectStageGroup = projectStage.replaceAll("[0-9]+", "");
		ConfigurableEnvironment env = applicationContext.getEnvironment();
		if (StringUtils.equals(projectStage, prjectStageGroup)) {
			env.setActiveProfiles(projectStage);
		} else {
			env.setActiveProfiles(prjectStageGroup);
			env.addActiveProfile(projectStage);
		}

		if (LOG.isDebugEnabled()) {
			//  setting spring.profiles.active = [dev, dev01]
			LOG.debug("setting spring.profiles.active = {}", Arrays.asList(env.getActiveProfiles()));
		}
	}
}

```

web-fragment.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:webfragment="http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_0.xsd" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-fragment_3_0.xsd"
	version="3.0">
	<!-- JVMシステムパラメータで設定したProjectStageをSpringActiveProfileへ設定 -->
	<context-param>
		<param-name>contextInitializerClasses</param-name>
		<param-value>jp.co.future.project.palette.common.context.SpringActiveProfileInitializer</param-value>
	</context-param>

</xml>

```

#### spring bootのprofile はxml でも利用可能

xml 内部でもprofile を記載でき、
環境ごとに定義ファイルの切り分けられる

サンプルで記載しているが、共通化や外部ファイルの読み込みなどの設定も可能。

```xml

	<beans profile="dev">
		<!-- データソース -->
		<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
			<property name="jdbcUrl" value=""/>
		</bean>
	</beans>

	<beans profile="stg">
		<!-- データソース -->
		<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
			<property name="jdbcUrl" value=""/>
		</bean>
	</beans>

	<!-- カンマ区切りで複数ステージの指定も可能 -->
	<beans profile="dev,stg">
		<bean id="redis" >
		</bean>
	</beans>


	<beans profile="dev01">
		<!-- xmlからプロパティファイルの読み込みも可能 -->
		<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
		    <property name="location" value="classpath:config/connect-config-dev01.properties" />
		</bean>
	</beans>

	<!-- xmlそのものをインポートすることも可能 -->
	<import resource="classpath:META-INF/common-context.xml" />

```



## おわりに

以上、spring boot で環境切替を行う方法でした。
ワンモジュール、ワンソース！