環境の抽象化
Environment
(Javadoc) インターフェースは、アプリケーション環境の 2 つの重要な側面(プロファイルとプロパティ)をモデル化するコンテナーに統合された抽象化です。
プロファイルは、指定されたプロファイルがアクティブな場合にのみ、コンテナーに登録される Bean 定義の名前付きの論理グループです。Bean は、XML で定義されているかアノテーション付きで定義されているかに関係なく、プロファイルに割り当てることができます。プロファイルに関連する Environment
オブジェクトのロールは、現在アクティブなプロファイル(存在する場合)、およびデフォルトでアクティブにするプロファイル(存在する場合)を決定することです。
プロパティは、ほとんどすべてのアプリケーションで重要なロールを果たし、プロパティファイル、JVM システムプロパティ、システム環境変数、JNDI、サーブレットコンテキストパラメーター、アドホック Properties
オブジェクト、Map
オブジェクトなど、さまざまなソースに由来する場合があります。プロパティに関連する Environment
オブジェクトのロールは、プロパティソースを設定し、プロパティソースからプロパティを解決するための便利なサービスインターフェースをユーザーに提供することです。
Bean 定義プロファイル
Bean 定義プロファイルは、さまざまな環境でさまざまな Bean を登録できるコアコンテナーのメカニズムを提供します。「環境」という言葉は、ユーザーごとに異なることを意味し、この機能は次のような多くのユースケースに役立ちます。
開発中のメモリ内データソースに対して作業することと、QA または本番環境で JNDI から同じデータソースを検索すること。
アプリケーションをパフォーマンス環境にデプロイするときにのみ、監視インフラストラクチャを登録します。
顧客 A と顧客 B デプロイの Bean のカスタマイズされた実装を登録します。
DataSource
を必要とする実用的なアプリケーションでの最初のユースケースを考えてください。テスト環境では、構成は次のようになります。
Java
Kotlin
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
次に、アプリケーションのデータソースが本番アプリケーションサーバーの JNDI ディレクトリに登録されていると仮定して、このアプリケーションを QA または本番環境にデプロイする方法を検討します。dataSource
Bean は、次のようになりました。
Java
Kotlin
@Bean(destroyMethod = "")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
問題は、現在の環境に基づいて、これら 2 つのバリエーションの使用を切り替える方法です。時間の経過とともに、Spring ユーザーはこれを実現するためのいくつかの方法を考案しました。通常、システム環境変数と、環境変数の値に応じて正しい構成ファイルパスに解決される ${placeholder}
トークンを含む XML <import/>
ステートメントの組み合わせに依存しています。Bean 定義プロファイルは、この問題の解決策を提供するコアコンテナー機能です。
上記の環境固有の Bean 定義の例に示されているユースケースを一般化すると、特定のコンテキストでは特定の Bean 定義を登録する必要がありますが、他のコンテキストでは登録しません。状況 A で Bean 定義の特定のプロファイルを登録し、状況 B で別のプロファイルを登録すると言うことができます。このニーズを反映するように構成を更新することから始めます。
@Profile
を使用する
@Profile
(Javadoc) アノテーションを使用すると、1 つ以上の指定されたプロファイルがアクティブであるときに、コンポーネントが登録に適格であることを示すことができます。前述の例を使用して、dataSource
構成を次のように書き換えることができます。
Java
Kotlin
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("development")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
Java
Kotlin
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod = "") (1)
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | @Bean(destroyMethod = "") は、デフォルトの destroy メソッドの推論を無効にします。 |
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "") (1)
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
1 | @Bean(destroyMethod = "") は、デフォルトの destroy メソッドの推論を無効にします。 |
前述のように、@Bean メソッドでは、通常、プログラムによる JNDI ルックアップを使用することを選択します。これには、Spring の JndiTemplate /JndiLocatorDelegate ヘルパーまたは前に示したストレート JNDI InitialContext の使用箇所を使用しますが、JndiObjectFactoryBean バリアントは使用しないため、戻り値の型を FactoryBean 型。 |
プロファイル文字列には、単純なプロファイル名(たとえば、production
)またはプロファイル式を含めることができます。プロファイル式を使用すると、より複雑なプロファイルロジックを表現できます(たとえば、production & us-east
)。プロファイル式では次の演算子がサポートされています。
!
: プロファイルの論理NOT
&
: プロファイルの論理AND
|
: プロファイルの論理OR
括弧を使用しないと、& 演算子と | 演算子を混在させることはできません。例: production & us-east | eu-central は有効な式ではありません。production & (us-east | eu-central) として表現する必要があります。 |
カスタム合成アノテーションを作成する目的で、@Profile
をメタアノテーションとして使用できます。次の例では、@Profile("production")
のドロップイン置換として使用できるカスタム @Production
アノテーションを定義します。
Java
Kotlin
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
@Configuration クラスが @Profile でマークされている場合、そのクラスに関連付けられている @Bean メソッドと @Import アノテーションはすべて、指定されたプロファイルの 1 つ以上がアクティブでない限りバイパスされます。@Component または @Configuration クラスが @Profile({"p1", "p2"}) でマークされている場合、そのクラスは、プロファイル "p1" または "p2" がアクティブ化されていない限り、登録または処理されません。特定のプロファイルの前に NOT 演算子(! )が付いている場合、アノテーション付き要素は、プロファイルがアクティブでない場合にのみ登録されます。例: @Profile({"p1", "!p2"}) を指定すると、プロファイル 'p1' がアクティブな場合、またはプロファイル 'p2' がアクティブでない場合に登録が行われます。 |
次の例に示すように、@Profile
はメソッドレベルで宣言して、構成クラスの特定の Bean を 1 つだけ含めることもできます(たとえば、特定の Bean の代替バリアント)。
Java
Kotlin
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | standaloneDataSource メソッドは、development プロファイルでのみ使用可能です。 |
2 | jndiDataSource メソッドは、production プロファイルでのみ使用可能です。 |
@Configuration
class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
fun standaloneDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
@Bean("dataSource")
@Profile("production") (2)
fun jndiDataSource() =
InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 | standaloneDataSource メソッドは、development プロファイルでのみ使用可能です。 |
2 | jndiDataSource メソッドは、production プロファイルでのみ使用可能です。 |
異なるプロファイル条件で代替 Bean を定義する場合は、前の例に示すように、 |
XML Bean 定義プロファイル
対応する XML は、<beans>
要素の profile
属性です。上記のサンプル構成は、次のように 2 つの XML ファイルに書き換えることができます。
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
次の例に示すように、同じファイル内で <beans/>
要素を分割およびネストすることを回避することもできます。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
spring-bean.xsd
は、そのような要素をファイルの最後の要素としてのみ許可するように制限されています。これにより、XML ファイルが乱雑になることなく、柔軟性が得られます。
対応する XML は、前述のプロファイル式をサポートしていません。ただし、
上記の例では、 |
プロファイルの有効化
構成を更新したため、Spring にアクティブなプロファイルを指示する必要があります。サンプルアプリケーションをすぐに開始すると、コンテナーが dataSource
という名前の Spring Bean を見つけられなかったため、NoSuchBeanDefinitionException
がスローされます。
プロファイルのアクティブ化はいくつかの方法で実行できますが、最も簡単なのは、ApplicationContext
を介して利用可能な Environment
API に対してプログラムで実行することです。次の例は、その方法を示しています。
Java
Kotlin
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
さらに、spring.profiles.active
プロパティを使用してプロファイルを宣言的にアクティブにすることもできます。このプロパティは、システム環境変数、JVM システムプロパティ、web.xml
のサーブレットコンテキストパラメーター、または JNDI のエントリとして指定できます ( PropertySource
の抽象化を参照)。統合テストでは、spring-test
モジュールの @ActiveProfiles
アノテーションを使用してアクティブなプロファイルを宣言できます ( 「環境プロファイルによるコンテキスト構成」を参照)。
プロファイルは「どちらか一方」の命題ではないことに注意してください。複数のプロファイルを一度にアクティブ化できます。プログラムにより、String…
可変引数を受け入れる setActiveProfiles()
メソッドに複数のプロファイル名を提供できます。次の例では、複数のプロファイルをアクティブにします。
Java
Kotlin
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
宣言的に、spring.profiles.active
は、次の例に示すように、プロファイル名のコンマ区切りリストを受け入れる場合があります。
-Dspring.profiles.active="profile1,profile2"
デフォルトプロファイル
デフォルトのプロファイルは、アクティブなプロファイルがない場合に有効になるプロファイルを表します。次の例を考えてみましょう。
Java
Kotlin
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
アクティブなプロファイルがない場合は、dataSource
が作成されます。これは、1 つ以上の Bean にデフォルトの定義を提供する方法として見ることができます。いずれかのプロファイルが有効になっている場合、デフォルトのプロファイルは適用されません。
デフォルトのプロファイルの名前は default
です。デフォルトのプロファイルの名前は、Environment
で setDefaultProfiles()
を使用するか、spring.profiles.default
プロパティを宣言的に使用することによって変更できます。
PropertySource
の抽象化
Spring の Environment
抽象化は、プロパティソースの構成可能な階層に対する検索操作を提供します。次のリストを検討してください。
Java
Kotlin
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
上記のスニペットでは、my-property
プロパティが現在の環境に定義されているかどうかを Spring に確認する高レベルの方法を示しています。この質問に答えるために、Environment
オブジェクトは PropertySource
(Javadoc) オブジェクトのセットに対して検索を実行します。PropertySource
はキーと値のペアのソースに対する単純な抽象化であり、Spring の StandardEnvironment
(Javadoc) は 2 つの PropertySource オブジェクトで構成されます。1 つは JVM システムプロパティのセット(System.getProperties()
)を表し、もう 1 つはシステム環境変数のセット(System.getenv()
)を表します
これらの既定のプロパティソースは、スタンドアロンアプリケーションで使用するために StandardEnvironment に存在します。StandardServletEnvironment (Javadoc) には、サーブレット構成、サーブレットコンテキストパラメーター、および JNDI が使用可能な場合は JndiPropertySource (Javadoc) を含む追加のデフォルトプロパティソースが取り込まれます。 |
具体的には、StandardEnvironment
を使用すると、my-property
システムプロパティまたは my-property
環境変数が実行時に存在する場合、env.containsProperty("my-property")
の呼び出しは true を返します。
実行される検索は階層的です。デフォルトでは、システムプロパティは環境変数よりも優先されます。そのため、 一般的な
|
最も重要なことは、メカニズム全体を構成できることです。おそらく、この検索に統合したいプロパティのカスタムソースがあります。これを行うには、独自の PropertySource
を実装およびインスタンス化し、現在の Environment
の PropertySources
のセットに追加します。次の例は、その方法を示しています。
Java
Kotlin
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
上記のコードでは、MyPropertySource
が検索で最高の優先度で追加されています。my-property
プロパティが含まれている場合、他の PropertySource
の my-property
プロパティを優先して、プロパティが検出されて返されます。MutablePropertySources
(Javadoc) API は、プロパティソースのセットの正確な操作を可能にする多くのメソッドを公開します。
@PropertySource
を使用する
@PropertySource
(Javadoc) アノテーションは、PropertySource
を Spring の Environment
に追加するための便利で宣言的なメカニズムを提供します。
キーと値のペア testbean.name=myTestBean
を含む app.properties
というファイルがある場合、次の @Configuration
クラスは @PropertySource
を使用して、testBean.getName()
の呼び出しが myTestBean
を返すようにします。
Java
Kotlin
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
次の例に示すように、@PropertySource
リソースの場所に存在する ${…}
プレースホルダーは、環境に対してすでに登録されているプロパティソースのセットに対して解決されます。
Java
Kotlin
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
my.placeholder
がすでに登録されているプロパティソース(システムプロパティや環境変数など)のいずれかに存在すると仮定すると、プレースホルダーは対応する値に解決されます。そうでない場合は、default/path
がデフォルトとして使用されます。デフォルトが指定されておらず、プロパティを解決できない場合、IllegalArgumentException
がスローされます。
@PropertySource は反復可能なアノテーションとして使用できます。@PropertySource は、属性オーバーライドを持つカスタム合成アノテーションを作成するためのメタアノテーションとしても使用できます。 |
ステートメントのプレースホルダー解決
従来、要素内のプレースホルダーの値は、JVM システムプロパティまたは環境変数に対してのみ解決できました。これはもはや事実ではありません。Environment
抽象化はコンテナー全体に統合されているため、プレースホルダの解決をコンテナー全体に簡単にルーティングできます。これは、任意の方法で解決プロセスを構成できることを意味します。システムプロパティと環境変数を検索する優先順位を変更したり、完全に削除したりできます。必要に応じて、独自のプロパティソースをミックスに追加することもできます。
具体的には、Environment
で使用可能な限り、customer
プロパティが定義されている場所に関係なく、次のステートメントが機能します。
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>