http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。
5.12 Java-based container configuration
5.12.1 Basic concepts: @Bean and @Configuration
Springの新しいJavaコンフィグレーション(Java-configuration)サポートの中心となるアーティファクトは@Configuration
を付与するクラスと@Bean
を付与するメソッドです。
@Bean
を付与するメソッドが意味するのは、Spring IoCコンテナが管理する新規オブジェクトを、そのメソッドがインスタンス化・設定・初期化する、ということです。SpringのXML設定<beans/>
と似ており、@Bean
は<bean/>
要素と同様の役割を担います。任意の@Component
と共にメソッドに@Bean
を付与可能ですが、通常は@Configuration
と共に使います。
@Configuration
を付与するクラスの主要目的はビーン定義のソースとなることです。更に、@Configuration
クラスは、あるメソッドから別の@Bean
メソッドを呼ぶだけで、内部的なビーン依存性(inter-bean dependencies)を定義出来ます。最も単純な@Configuration
クラスは以下のようになります。
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
上記のAppConfig
クラスは以下のSpring<beans/>
XMLと同等です。
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
@Bean
と@Configuration
の詳細は以降のセクションで解説します。とはいえ、まずはJavaコンフィグレーションを使用してSpringコンテナを生成する様々な方法を見ていきます。
Full @Configuration vs lite @Beans mode?
@Configuration
を付与していないクラス内で@Bean
メソッドを宣言する場合、ライト(lite)モードで参照されます。たとえば、@Component
やplain old classで宣言するビーンメソッドはライトと見なされます。
フル@Configuration
とは異なり、ライト@Bean
メソッドでは簡単に内部的なビーン依存性を宣言出来ません。通常、ライトモード時には@Bean
メソッドは別の@Bean
メソッドを呼ぶべきではありません。
@Configuration
クラス内で@Bean
メソッドを使う場合だけが、フルモードが常に使われることを保証する推奨アプローチです。このアプローチは同一の@Bean
メソッドが複数回呼び出されてしまう事故を防ぎ、ライトモード時には追跡困難な微妙なバグを減らす効果があります。
5.12.2 Instantiating the Spring container using AnnotationConfigApplicationContext
以下のセクションのドキュメントのAnnotationConfigApplicationContext
はSpring 3.0で新規導入されたものです。この多目的用途のApplicationContext
実装の機能は、入力として@Configuration
クラスだけでなく、プレーンな@Component
クラスとJSR-330メタデータアノテーションを付与したクラスも受け入れます。
入力に@Configuration
クラスを取ると、@Configuration
クラス自体がビーン定義として登録され、クラス内のすべての@Bean
宣言メソッドもビーン定義として登録されます。@Component
とJSR-330クラスの場合、ビーン定義として登録され、必要な場所のクラス内で使う@Autowired
や@Inject
などのDIメタデータと仮定されます*1。
Simple construction
ClassPathXmlApplicationContext
をインスタンス化する場合に入力としてSpring XMLファイルを使う場合とほぼ同じように、AnnotationConfigApplicationContext
をインスタンス化する場合は入力として@Configuration
クラスを使います。これにより完全にXMLフリーでSpringコンテナを使用可能です。
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
上述の通り、AnnotationConfigApplicationContext
は@Configuration
クラスだけで動かすものではありません。@Component
やJSR-330アノテーション付与クラスもコンストラクタに入力として使用可能です。
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
上記のMyServiceImpl
, Dependency1
, Dependency2
は@Autowired
などのSpring DIアノテーションを使うものと仮定します。
Building the container programmatically using register(Class<?>…)
AnnotationConfigApplicationContext
は引数無しコンストラクタでインスタンス化可能で、register()
メソッドで設定します。この方法はAnnotationConfigApplicationContext
をプログラム的に構築する場合には有用です。
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
Enabling component scanning with scan(String…)
コンポーネントスキャンを有効化するには、@Configuration
クラスに以下のようなアノテーションを付与します。
@Configuration @ComponentScan(basePackages = "com.acme") public class AppConfig { ... }
Springの経験者にはcontext:
名前空間と同等なXML宣言の方が馴染み深いでしょう。
<beans> <context:component-scan base-package="com.acme"/> </beans>
上記の例では、com.acme
パッケージをスキャンし、@Component
アノテーション付与クラスを探し、コンテナ内のSpringビーン定義として登録します。AnnotationConfigApplicationContext
が公開するscan(String...)
メソッドは同様なコンポーネントスキャン機能を提供します。
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
@Configuration
クラスは@Component
でmeta-annotatedされたクラスなことを覚えておいて下さい。そのため、@Configuration
クラスはコンポーネントスキャン候補になります。上記の例では、AppConfig
はcom.acme
パッケージ内(もしくは直下)に宣言すると仮定しており、@Configuration
クラスはscan()
実行中にピックアップされ*2、refresh()
時にすべての@Bean
メソッドの処理およびコンテナへのビーン定義登録を行います。
Support for web applications with AnnotationConfigWebApplicationContext
AnnotationConfigApplicationContext
のバリエーションであるWebApplicationContext
がAnnotationConfigWebApplicationContext
で利用可能です。この実装はSpringContextLoaderListener
サーブレットリスナーやSpring MVCのDispatcherServlet
などの設定時に使います。以下のweb.xml
は一般的なSpring MVC webアプリケーションの設定例です。contextClass
context-paramおよびinit-paramの使い方に注意してください。
<web-app> <!-- デフォルトのXmlWebApplicationContextの代わりに AnnotationConfigWebApplicationContextを使うようContextLoaderListener を設定 --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- Configuration locationsには完全修飾 @Configuration クラスを カンマないし空白デリミタで一つ以上含める。必須。 完全修飾パッケージはコンポーネントスキャンの指定になる。--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- 例によってContextLoaderListenerでルートアプリケーションコンテキスト をブートする。 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 恒例のSpring MVC DispatcherServlet宣言。 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- デフォルトのXmlWebApplicationContextの代わりに AnnotationConfigWebApplicationContextを使うようDispatcherServlet を設定 --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes --> <!-- 再度、Configuration locationsには完全修飾 @Configuration クラスを カンマないし空白デリミタで一つ以上含める。必須。--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- /app/* への全リクエストをdispatcher servletに関連付ける。 --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
5.12.3 Using the @Bean annotation
@Bean
はメソッドレベルのアノテーションで、XMLの<bean/>
要素と直接的に対応するものです。このアノテーションは<bean/>
が提供する属性のいくつか、例えば、init-method, destroy-method, autowiring, name
、などをサポートします。
@Bean
は@Configuration
か@Component
を付与したクラス内で使用可能です。
Declaring a bean
ビーンを宣言するには、ただ単にメソッドに@Bean
を付与します。これにより、型がメソッドの戻り値型のビーン定義をApplicationContext
内に登録します。デフォルトでは、ビーン名はメソッド名と同じになります。以下は@Bean
メソッド宣言の
例です。
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
上記の設定は以下のSpring XMLと同等です。
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
両方の定義ともtransferService
という名前のビーンをApplicationContext
で利用可能で、オブジェクトインスタンスの型はTransferServiceImpl
にバインドします。
transferService -> com.acme.TransferServiceImpl
Receiving lifecycle callbacks
@Bean
で定義したいずれのクラスも標準ライフサイクルコールバックをサポートし、JSR-250由来の@PostConstruct
と@PreDestroy
を使用可能です。詳細についてはJSR-250 annotationsを参照してください。
標準Springライフサイクルコールバックも同様にサポートします。ビーンがInitializingBean
, DisposableBean
, Lifecycle
を実装する場合、対応するメソッドをコンテナが呼び出します。
BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAwareなどの*Aware
標準インターフェースもサポートします。
@Bean
はSpring XMLのinit-method
とdestroy-method
などの任意メソッド指定による初期化および破棄時コールバックをサポートします。
public class Foo { public void init() { // initialization logic } } public class Bar { public void cleanup() { // destruction logic } } @Configuration public class AppConfig { @Bean(initMethod = "init") public Foo foo() { return new Foo(); } @Bean(destroyMethod = "cleanup") public Bar bar() { return new Bar(); } }
デフォルトでは、Javaコンフィグで定義したビーンはpublicなclose
もしくはshutdown
を持ち、このメソッドは破棄時コールバックに自動的に組み込まれます。もしpublicなclose
もしくはshutdown
を持つもののコンテナシャットダウン時に呼ばれたくない場合、デフォルトの(inferred)
モードをオフにするためにビーン定義に@Bean(destroyMethod="")
を追加します。
なお、上記のFoo
の場合、コンストラクタ時にinit()
メソッドを呼ぶ事で同等なことをしても構いません。
@Configuration public class AppConfig { @Bean public Foo foo() { Foo foo = new Foo(); foo.init(); return foo; } // ... }
コンテナ上ではなくJava上で直接実行する場合、オブジェクトには任意の操作が可能ですが、コンテナライフサイクルに依存する必要は常にありません。
Specifying bean scope
Using the @Scope annotation
@Bean
でビーン定義をする際に特定のスコープを持つよう指定できます。Bean Scopesで解説して標準スコープのいずれも使用可能です。
デフォルトスコープはsingleton
ですが、@Scope
でオーバーライド可能です。
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
@Scope and scoped-proxy
Springはscoped proxies経由のスコープ付き依存性(scoped dependencies)を動作する方法を提供します。XMLコンフィグレーション使用時にそうしたプロキシを作成する最も簡単な方法は<aop:scoped-proxy/>
を使うことです。@Scope
ビーンで設定する同等な方法としてproxyMode属性をサポートします。デフォルトはプロキシ無し(ScopedProxyMode.NO
)ですが、ScopedProxyMode.TARGET_CLASS
ないしScopedProxyMode.INTERFACES
を指定可能です。
XMLドキュメント(以前に解説した例を参照)のscoped proxyの例を@Bean
に移植するには、以下のようにします。
// an HTTP Session-scoped bean exposed as a proxy @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public UserPreferences userPreferences() { return new UserPreferences(); } @Bean public Service userService() { UserService service = new SimpleUserService(); // a reference to the proxied userPreferences bean service.setUserPreferences(userPreferences()); return service; }
Customizing bean naming
デフォルトでは、コンフィグレーションクラスはビーン名として@Bean
メソッド名を使います。ただし、この機能はname
属性でオーバーライド可能です。
@Configuration public class AppConfig { @Bean(name = "myFoo") public Foo foo() { return new Foo(); } }
Bean aliasing
Section 5.3.1, “Naming beans”で解説したように、場合によっては、いわゆるビーンエイリアス(bean aliasing)として、一つのビーンに複数の名前を付与可能です。@Bean
のname
属性には文字列配列を指定可能です。
@Configuration public class AppConfig { @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } }
Bean description
時にはビーンの詳細をテキストで説明しておくことは有用です。とりわけ監視用途(JMXなど)で公開するビーンに有用です。@Bean
に説明を追加するには@Description
を使います。
5.12.4 Using the @Configuration annotation
@Configuration
はビーン定義元となるオブジェクトを示すクラスレベルアノテーションで、@Bean
アノテーションを付与するpublicなメソッドでビーン宣言をします。@Configuration
内で@Bean
メソッドを呼ぶことで、内部的なビーン依存性を定義可能です。基本的なコンセプトの解説についてはSection 5.12.1, “Basic concepts: @Bean and @Configuration”を参照してください。
Injecting inter-bean dependencies
@Bean
が別の@Bean
への依存性を持つ場合、ただ単に別の@Bean
メソッドを呼び出すことで依存性を表現します。
@Configuration public class AppConfig { @Bean public Foo foo() { return new Foo(bar()); } @Bean public Bar bar() { return new Bar(); } }
上記の例では、foo
ビーンはコンストラクタインジェクション経由でbar
参照を受け取ります。
内部的なビーン依存性を宣言するこの方法は@Configuration
クラス内に定義した@Bean
メソッドでだけ動作します。@Component
クラスでは内部的な依存性は宣言できません。
Lookup method injection
以前の説明したように、lookup method injectionは頻繁に使うべきではない高度な機能です。この機能はシングルトンスコープビーンがプロトタイプスコープビーンの依存性を持つ場合に役に立ちます。このパターンを自然に実装し、コンフィグレーションをJavaで書きます*3。
public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriate Command interface Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
Javaコンフィグレーションサポートを使い、CommandManager
のサブクラスを作成して抽象createCommand()
メソッドをオーバーライドし、新規(のプロトタイプ)コマンドオブジェクトをルックアップします。
Further information about how Java-based configuration works internally
以下の例は@Bean
メソッドが二度呼ばれる例です。
@Configuration public class AppConfig { @Bean public ClientService clientService1() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientService clientService2() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientDao clientDao() { return new ClientDaoImpl(); } }
clientDao()
は、clientService1()
で一度、clientService2()
で一度、呼び出されます。このメソッドはClientDaoImpl
の新規インスタンスを生成して返すので、二つのインスタンス(各seriviceごとに一つ)を生成すると予測するかもしれません。これは間違いなく問題です。Springでは、デフォルトではインスタンス化するビーンはsingleton
スコープを持ちます。そこで黒魔術の出番です。すべての@Configuration
クラスはCGLIB
がスタートアップ時にサブクラス化します。サブクラスでは、親メソッドを呼んで新規インスタンスを作成する前に、子メソッドはまずキャッシュした(スコープ付き)ビーンの有無をチェックします。Spring 3.2以降では、クラスパスにCGLIBを含める必要はなくなりました。その理由は、CGLIBクラスはorg.springframework下に再編成してspring-core JAR内に直接含まれるようになったためです。
上記の振る舞いはビーンのスコープに応じて異なる可能性があります。ここではシングルトンについて解説しています。
CGLIBがスタートアップ時に動的に機能を追加するという事実により以下のような制限があります。
- Configurationクラスは非finalの必要がある。
- 引数無しコンストラクタを持つ必要がある。
5.12.5 Composing Java-based configurations
Using the @Import annotation
Spring XMLファイル内で使われる<import/>
要素が設定のモジュール化に役立つのと同様に、@Import
で別のコンフィグレーションクラスから@Bean
定義をロード出来ます。
@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
このとき、コンテキストのインスタンス化時にConfigA.class
とConfigB.class
の両方を指定する必要は無く、明示的な指定にはConfigB
のみが必要です。
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }
コンストラクタ時に大量の@Configuration
を開発者が記憶しておく必要を無くし、唯一つのクラスだけを必要にすることでコンテナのインスタンス化を単純にします。
Injecting dependencies on imported @Bean definitions
上記の例は動作しますが、かなり単純化したものに過ぎません。実際上のケースでは、ビーンはコンフィグレーションクラスをまたがって別のビーンへの依存性を持ちます。これはXMLの場合では問題にはなりませんが、その理由は、コンパイラが介在しないのでただ単にref="someBean"
と宣言してコンテナ初期化時にSpringが動作するのを信用すれば良いからです。よって、@Configuration
クラスを使用する場合、Javaコンパイラには他ビーンへの参照がJavaの文法に沿う形でのコンフィグレーションモデルの制約を与えます。
幸運な事に、この問題は解くのは簡単です。@Configuration
クラスはコンテナ上で最終的にはビーンになります。つまり、他のビーン同様に@Autowired
インジェクションメタデータを使用可能です。
複数の@Configuration
クラスを用いて現実世界に沿ったシナリオを考えます。ここでは他のコンフィグレーションで宣言しているビーンに依存するビーンがあります。
@Configuration public class ServiceConfig { @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public DataSource dataSource() { // return new DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes... TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
上記のシナリオでは、@Autowired
の使用は問題なく動作して所望のモジュール性が実現できていますが、オートワイヤするビーン定義が宣言されている場所を探し出すことにはいくらかの曖昧さが見受けられます。たとえば、開発者がServiceConfig
を探す場合、どのようにして@Autowired AccountRepository
を宣言している場所を知ればよいのでしょうか? これはコード上では不明瞭で、あまりよろしくありません。ちなみにSpring Tool Suiteでは、必要なすべてのワイヤリングのグラフを描画可能なツールを提供しています。また、Java IDEではAccountRepository
型を宣言しているすべての箇所を簡単に探す事が可能で、また、該当型を返す@Bean
メソッドの場所を即座に調べることも可能です。
@Configuration
クラスから別のクラスへのダイレクトナビゲーションをIDEで持たせられます。コンフィグレーションクラス自身をオートワイヤすることを考えます。
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { // コンフィグレーションクラス経由で@Beanメソッドにアクセスする return new TransferServiceImpl(repositoryConfig.accountRepository()); } }
上記の場合では、AccountRepository
の定義場所を明示しています。しかし、ServiceConfig
がRepositoryConfig
と密結合してしまっています。これはトレードオフです。この密結合はインタフェースベースもしくは抽象クラスベースの@Configuration
クラスを使うことでいくらか緩和できます。以下を考えます。
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { @Bean public DataSource dataSource() { // return DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
このとき、ServiceConfig
はDefaultRepositoryConfig
に関しては疎結合です。また、IDE内蔵のツールが役に立ち、RepositoryConfig
実装の型階層を開発者は簡単に知ることが出来ます。この方法では、@Configuration
クラスとその依存性間を行き来することと、通常のインタフェースベースのコードで同じことをするのに、差はありません。
Conditionally including @Configuration classes or @Beans
システム環境に基づいて、@Configuration
や@Bean
メソッドを条件付でオフに出来ると便利なことがあります。良くある例としては、Spring Environment
(Section 5.13.1, “Bean definition profiles”を参照)で有効な特定のprofileを持つ場合にだけビーンを有効化するのに@Profile
を使う場合です。
通常、@Profile
は@Conditional
というより柔軟なアノテーションを使用して実装します。@Conditional
には@Bean
登録前に参照されるorg.springframework.context.annotation.Condition
の実装を指定します。
Condition
インタフェースの実装は単にmatches(...)
を提供するのみで、戻り値はtrue
かfalse
です。たとえば、@Profile
用に使う実際のCondition
実装は以下のようになります。
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { // Read the @Profile annotation attributes MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true; } } return false; } } return true; }
詳細は@Conditional
のjavadocsを参照してください。
Combining Java and XML configuration
Springの@Configuration
クラスはXMLを100%完全に置き換えることを目的とはしていません。Spring XML名前空間などいくらかの機能はコンテナを設定する理想的な方法のままです。XMLが都合の良い場所では、"XML中心"("XML-centric")な方法でコンテナをインスタンス化する(例えばClassPathXmlApplicationContext
)か、"Java中心"("Java-centric")な方法(AnnotationConfigApplicationContext
と必要に応じてXMLをインポートする@ImportResource
)か、どちらかを選びます。
XML-centric use of @Configuration classes
XMLからSpringコンテナをブートし、アドホックに@Configuration
クラスを含めるのが望ましい場合があります。たとえば、Spring XMLを使用する既存の巨大なコードベースが存在する場合、必要に応じて@Configuration
を作成して既存のXMLファイルに含めることは簡単です。以下に、XML中心の方法を取っているケースで@Configuration
クラスを部分的に使うやり方を示します。
@Configuration
クラスは最終的にはコンテナ上のビーン定義となることは既に解説しました。この例では、AppConfig
という名前の@Configuration
を生成し、<bean/>
定義としてsystem-test-config.xml
に含めます。<context:annotation-config/>
を有効化しているので、コンテナは@Configuration
を理解してAppConfig
プロパティで宣言されている@Bean
メソッドを処理します。
@Configuration public class AppConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public TransferService transferService() { return new TransferService(accountRepository()); } }
system-test-config.xml <beans> <!-- @Autowiredと@Configurationなどのアノテーション処理を有効化する --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }
上記のsystem-test-config.xml
では、AppConfig<bean/>
はid
要素を宣言していません。これは問題無く動作しますが、他のビーンがコンフィグレーションクラスを参照する必要性は無く、また、by nameでコンテナから明示的にフェッチすることもほとんどありません。同様にDataSource
ビーンはby typeでのみオートワイヤするので、明示的なビーンidは厳密に要求されません。
@Configuration
は@Component
でmeta-annotatedしているので、@Configuration
クラスは自動的にコンポーネントスキャンの候補になります。上述のケースは、コンポーネントスキャンを利用するようにsystem-test-config.xml
を定義できます。この場合の注意点としては、<context:component-scan/>
が同様な機能となるので、<context:annotation-config/>
を明示的に宣言する必要はありません。
system-test-config.xml <beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
@Configuration class-centric use of XML with @ImportResource
コンテナを設定するのに主に@Configuration
を使っているアプリケーションでも、いくらかのXMLを使う場合がありえます。そうした場合では、@ImportResource
を使いXMLと同等な定義を行います。これにより"Java中心"アプローチでコンテナを設定を行い、XMLの使用を最小限に抑えます。
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { return new DriverManagerDataSource(url, username, password); } }
properties-config.xml <beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }
5.13 Environment abstraction
Environment
はコンテナに統合される抽象化(abstraction integrated in the container)で、profilesとpropertiesというアプリケーション環境の二つの要素をモデル化したものです。
プロファイル(profile)とは、名前を持ち、与えられたプロファイルが有効な場合にのみコンテナに登録したいビーン定義の論理グループのことです。XMLかアノテーション定義のどちらかでプロファイルにビーンを割り当てられます。プロファイルと関連を持つEnvironment
オブジェクトのロールが、現在有効なプロファイルやデフォルトで有効になるべきプロファイルを決定します。
プロパティは大抵のアプリケーションで重要な役割を担い、様々なソースが考えられます。プロパティファイル・JVMシステムプロパティ・システム環境変数・JNDI・サーブレットコンテキストパラメーター・Propertiesオブジェクト・Mapなどです。プロパティと関連を持つEnvironment
の役割は、プロパティ元の設定と解決のための使いやすいサービスインタフェースをユーザに提供することです。
5.13.1 Bean definition profiles
ビーン定義プロファイル(Bean definition profiles)とは、コアコンテナの機能で、異なる環境には異なるビーンを登録するためのものです。環境(environment)という単語はユーザによって捉え方が様々なので、この機能は様々な場合に使用可能です。例えば、
- 開発時にはインメモリのデータソースで動かし、同一のデータソースでQAや本番環境時にはJNDIからルックアップする。
- 実行環境(performance environment)にアプリケーションをデプロイする場合にのみモニタリングインフラを登録する。
- 顧客Aと顧客Bそれぞれにカスタマイズしたビーン実装を登録する。
上記のうち最初のケースについてDataSource
を要求するアプリケーションを例に考えてみます。テスト環境では、コンフィグレーションは以下のようになります。
@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("my-schema.sql") .addScript("my-test-data.sql") .build(); }
このとき、アプリケーション用のデータソースは本番環境のAPサーバのJNDIディレクトリに登録すると仮定して、アプリケーションをQAや本番環境にデプロイするにはどうすればよいでしょうか。dataSource
ビーンは以下のようになっています。
@Bean public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }
ここでの問題は、二種類の環境それぞれに基づいて切り替えを行う方法です。Spring有史以来、ユーザはこのために様々なやり方を考案しており、通常は、システム環境変数と${placeholder}
トークンを含むXML<import/>
ステートメントを組み合わせて、環境変数の値に基づいて正しい設定ファイルパスを解決しています。ビーン定義プロファイルはコアコンテナの機能でこの問題を解決するためのものです。
上記の環境固有ビーン定義の例を一般化するとすれば、コンテキストごとにビーン定義を登録する必要がある、ということになります*4。ある状況Aにはそれ用のビーン定義プロファイル、異なる状況Bには異なるプロファイルを登録したいハズです。それではこれらのニーズを反映するために設定を書き換えていきます。
@Profile
@Profile
は、一つ以上の指定プロファイルを有効化する時、あるコンポーネントに登録資格があることを示すために使います。上記の例では、以下のようにdataSource設定を書き換えられます。
@Configuration @Profile("dev") 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("production") public class JndiDataConfig { @Bean public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
@Profile
は、カスタムステレオタイプアノテーションを含める場合など、メタアノテーションとしても使用可能です。以下の例は@Production
カスタムアノテーションを定義しており、これは@Profile("production")
の代替表現として使用可能です。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }
@Profile
はメソッドレベルにも指定可能で、コンフィグレーションクラスの特定ビーンのみを指します。
@Configuration public class AppConfig { @Bean @Profile("dev") public DataSource devDataSource() { 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 @Profile("production") public DataSource productionDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
@Configuration
クラスに@Profile
を付ける場合、すべての@Bean
メソッドとそのクラスに関連付けられた@Import
は、一つ以上の指定プロファイルを有効化しない限り、無視されます。@Profile({"p1", "p2"})
を付けた@Component
ないし@Configuration
の場合、プロファイルp1とp2の両方またはいずれか一方を有効化しない限り、登録や処理は行われません。プロファイルのプレフィクスがNOT演算子(!
)の場合、その要素はプロファイルを無効化する場合に登録が行われます。@Profile({"p1", "!p2"})
の場合、登録はp1が有効でp2が無効の場合に行われます。
5.13.2 XML Bean definition profiles
profile
属性を受け入れるbeans
要素にXMLを書き換えたのが以下の例です*5。上述のコンフィギュレーション例は以下のように二つのXMLに書き換えられます。
<beans profile="dev" 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>
なお、XMLの分割を避けて同一ファイル内にネストした<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="dev"> <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>
The spring-bean.xsd
has been constrained to allow such elements only as the last ones in the file.*6 これはXMLを散乱させることなく柔軟性を保つのに役立ちます。
Enabling a profile
これで設定の書き換えが済んだので、次はプロファイルを有効化する方法について知る必要があります。この段階でサンプルアプリケーションを動かそうとすると、NoSuchBeanDefinitionException
がスローされます。これは、コンテナがdataSource
という名前のビーンを見つけられないからです。
プロファイルの有効化にはいくつかの方法があり、最もシンプルな方法はApplicationContext
APIでプログラム的に行うやり方です。
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("dev"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh();
また、プロファイルはspring.profiles.active
プロパティで宣言的に有効化することも可能で、このプロファイルの指定は、システム環境変数・JVMシステムプロパティ・web.xml
のシステムコンテキストパラメータ・JNDIエンティティ、で行います(Section 5.13.3, “PropertySource Abstraction”を参照)。
注意点として、プロファイルは"二者択一"("either-or")ではありません。一度に複数のプロファイルを有効化可能です。プログラム的なやり方では、単にsetActiveProfiles()
のString...
引数に複数のプロファイル名を指定します。
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
宣言的には、spring.profiles.active
にプロファイル名をカンマで区切って指定します。
-Dspring.profiles.active="profile1,profile2"
Default profile
defaultプロファイルは、デフォルトで有効化するプロファイルです。以下の例を見てみます。
@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(); } }
有効化するプロファイルが一つも無い場合、上記のdataSource
が作られます。この機能は一つ以上のビーンにdefault定義を与えるのに使えます。任意のプロファイルを有効化する場合、defaultプロファイルは適用されません。
デフォルトプロファイル名はEnvironment
のsetDefaultProfiles
もしくはspring.profiles.default
プロパティで宣言的に変更可能です。
5.13.3 PropertySource Abstraction
SpringのEnvironment abstractionはプロパティソース階層の検索操作を提供します。これを解説するために、以下を見てみます。
ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment(); boolean containsFoo = env.containsProperty("foo"); System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
上記の例では、現在の環境にfoo
プロパティが定義されているかどうかSpringを検索する高レベルAPIを使っています。Environment
オブジェクトはPropertySource
オブジェクトを検索します。PropertySource
は単なるソースのkey-valueペアで、SpringのStandardEnvironment
は二つのPropertySourceオブジェクトで設定します。一つはJVMシステムプロパティ(System.getProperties())
)で、もう一つはシステム環境変数(System.getenv()
)です。
これらのデフォルトプロパティソースはStandardEnvironment
用のもので、スタンドアローンアプリケーションで使います*7。StandardServletEnvironment
には追加のデフォルトプロパティソースとしてサーブレットコンフィグとコンテキストパラメータが含まれます。同様に、StandardPortletEnvironment
はプロパティソースとしてポートレットコンフィグとコンテキストパラメータにアクセスします。両者ともオプションでJndiPropertySource
を有効化可能です。詳細はJavadocを参照してください。
具体的に言うと、StandardEnvironment
を使う場合、env.containsProperty("foo")
は、foo
システムプロパティもしくはfoo
環境変数が実行時に存在する場合にtrueを返します。
検索は階層的に行われます。デフォルトでは、環境変数よりシステムプロパティが優先されます。そのため、env.getProperty("foo")
を呼び出す場合にfoo
プロパティを両方に設定している場合、システムプロパティが勝ち、環境変数よりシステムプロパティが優先して返されます。
更に重要なこととして、この仕組み全体を設定可能です。いま、カスタムのプロパティソースがあり、この検索機構に組み入れたいとします。これは可能で、単に自前のPropertySource``を実装&インスタンス化して現在の
Environmentの
PropertySources```に追加します。
ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource());
上記のコードでは、MyPropertySource
は検索階層の最上位に追加しています。そこにfoo
プロパティがある場合、その他すべてのPropertySource
のfoo
プロパティより優先して検出が行われて戻されます。MutablePropertySources
APIはプロパティソースを操作できるメソッドをいくつか公開しています。
5.13.4 @PropertySource
@PropertySource
はSpringEnvironment
にPropertySource
の追加を宣言的に行うための機能です。
key/valueのtestbean.name=myTestBean
を持つ"app.properties"ファイルを与えるには、以下のような"myTestBean"を返すtestBean.getName()
を呼ぶ@PropertySource
を使う@Configuration
クラスにします。
@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; } }
@PropertySource
リソース場所の${...}
プレースホルダは環境に登録済みのプロパティソースの中から解決が行われます。
@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; } }
いま、"my.placeholder"が何らかの登録済みプロパティソース、システムプロパティや環境変数、に存在すると仮定すると、プレースホルダは対応する値に解決されます。もし存在しない場合、デフォルトして"default/path"が使われます。もしデフォルトを指定せずプロパティが解決不能な場合、IllegalArgumentException
をスローします。
5.13.5 Placeholder resolution in statements
歴史的に、要素中のプレースホルダーはJVMシステムプロパティないし環境変数に対してのみ解決可能でしたが、それは最早過去の話です。Environment abstractionはコンテナの至る所に統合しており、それを通してのプレースホルダ解決は簡単なことです。つまり、自分好みの方法で解決処理を設定できる、ということです。システムプロパティと環境変数の検索順序の変更や、それらの削除、場合に応じて自前のプロパティソースを混ぜることも可能です。
具体的に言うと、以下のステートメントはEnvironment
でcustomer
プロパティが使用可能な限り、それが定義されている場所がどこであろうと動作します。
<beans> <import resource="com/bank/service/${customer}-config.xml"/> </beans>
5.14 Registering a LoadTimeWeaver
LoadTimeWeaver
はSpringがJVMにロードされるクラスを動的に変換するために使います。
load-time weavingを有効にするには、@Configuration
クラスの一つに@EnableLoadTimeWeaving
を追加します。
@Configuration @EnableLoadTimeWeaving public class AppConfig { }
またはXMLコンフィグレーションではcontext:load-time-weaver
要素を使います。
<beans> <context:load-time-weaver/> </beans>
ApplicationContext
に一度設定を行うと、そのApplicationContext
内のビーンはいずれもLoadTimeWeaverAware
を実装可能になり、それによってload-time weaverインスタンスへの参照を受け取ります。これが特に役立つのは、JPAクラス変換にload-time weavingが必要となる場所でSpring’s JPA supportと組み合わせて使う場合です。詳細についてはLocalContainerEntityManagerFactoryBean
のjavadocを参照してください。AspectJ load-time weavingについては、Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”を参照してください。
5.15 Additional Capabilities of the ApplicationContext
この章のイントロダクションで述べたように、org.springframework.beans.factory
パッケージは、プログラム的な方法も含めて、ビーンの管理と操作の基本的な機能を提供します。org.springframework.context
パッケージはBeanFactory
インタフェースを拡張するApplicationContext
を追加し、加えて、アプリケーションフレームワーク指向のスタイル(application framework-oriented style)の追加機能を提供するために他のインタフェースも拡張しています。たいてのユーザは宣言的な方法でApplicationContext
を使用し、プログラム的には作りませんが、Java EE webアプリケーションのスタートアップ処理の一部としてApplicationContext
を自動的にインスタンス化するのにContextLoader
などのサポートクラスを使います。
よりフレームワーク指向のスタイルにBeanFactory
の機能を拡張するのに、contextパッケージは以下の機能も提供します。
MessageSource
インタフェース経由の、i18n-styleのメッセージアクセス(Access to messages in i18n-style)ResourceLoader
インタフェース経由の、URLやファイルなどの、リソースアクセス(Access to resources)ApplicationEventPublisher
インタフェース経由の、ApplicationListener
インタフェースを実装するビーンへのEvent publicationHierarchicalBeanFactory
インタフェース経由の、アプリケーションのwebレイヤーなど、特定のレイヤーにフォーカス可能な、複数(階層)コンテキストのロード(Loading of multiple (hierarchical) contexts)
5.15.1 Internationalization using MessageSource
ApplicationContext
はMessageSource
というインタフェースを拡張しているため、国際化(internationalization:i18n)機能を提供します。また、SpringはHierarchicalMessageSource
を提供しており、これはメッセージを階層的に解決します。これらのインタフェースは共にSpringがメッセージ解決を行うための基礎的な部分です。インタフェースには以下のようなメソッドが含まれています。
String getMessage(String code, Object[] args, String default, Locale loc)
:MessageSource
からメッセージ検索に使うための基本的なメソッド。指定ロケールにメッセージが見つからない場合、デフォルトメッセージが使われます。標準ライブラリが提供するMessageFormat
機能を使用して、Any arguments passed in become replacement values*8String getMessage(String code, Object[] args, Locale loc)
: 本質的には上のメソッドと同じですが、一つだけ違いがあります。デフォルトメッセージの指定が無く、メッセージが見つからない場合はNoSuchMessageException
をスローします。String getMessage(MessageSourceResolvable resolvable, Locale locale)
: 上記のメソッドで使われるすべてのプロパティをMessageSourceResolvable
にラップしています。
ApplicationContext
ロード時に、コンテキストに定義されているMessageSource
ビーンを自動的に検索します。ビーン名はmessageSource
にする必要があります。そのビーンが見つからない場合、上述のメソッド呼び出しはすべてメッセージソースにデリゲートします。メッセージソースが見つからない場合、ApplicationContext
は同名のビーンを含んでいるかどうか親を検索します。見つかった場合、そのビーンをMessageSource
として使います。ApplicationContext
がメッセージ用のソースを発見できない場合、空のDelegatingMessageSource
を上述のメソッド呼び出しが可能なようにインスタンス化します。
Springは二つのMessageSource
実装、ResourceBundleMessageSource
とStaticMessageSource
を提供します。両実装ともネスト化したメッセージ用にHierarchicalMessageSource
を実装します。StaticMessageSource
は稀にしか使いませんが、ソースにメッセージをプログラム的に追加する方法を提供します。ResourceBundleMessageSource
の例は以下の通りです。
<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans>
この例では、クラスパスに三つのリソースバンドルformat
, exceptions
, windows
を定義している、という想定です。メッセージ解決リクエストはJava標準のResourceBundleのメッセージ解決で処理します。例を示すために、上記のリソースバンドルには二つの内容が含まれる、とします。
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
MessageSource
機能を実行するプログラム例は以下の通りです。すべてのApplicationContext
はMessageSource
を実装しているので、MessageSource
にキャスト可能です。
public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("message", null, "Default", null); System.out.println(message); }
上記のプログラムの出力結果は以下のようになります。
Alligators rock!
要約すると、クラスパスルートに置くbeans.xml
ファイルにMessageSource
を定義します。MessageSource
ビーン定義はbasenames
プロパティ経由で複数のリソースバンドルを参照します。basenames
プロパティにはクラスパスルートにある三つのファイルをlist要素に渡し、それぞれのファイル名はformat.properties
, exceptions.properties
, windows.properties
です。
次の例はメッセージルックアップに渡す引数の例です。この引数は文字列に変換されてルックアップメッセージのプレースホルダーに挿入されます。
<beans> <!-- this MessageSource is being used in a web application --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <!-- lets inject the above MessageSource into this POJO --> <bean id="example" class="com.foo.Example"> <property name="messages" ref="messageSource"/> </bean> </beans>
public class Example { private MessageSource messages; public void setMessages(MessageSource messages) { this.messages = messages; } public void execute() { String message = this.messages.getMessage("argument.required", new Object [] {"userDao"}, "Required", null); System.out.println(message); } }
execute()
メソッドの実行結果は以下のようになります。
The userDao argument is required.
i18nに関しては、Springの各種のMessageResource
実装は標準JDKResourceBundle
と同様の同一ロケール解決とフォールバックルールに従います。端的に言うと、前述の例で定義したmessageSource
において、British(en-GB
)ロケールでメッセージ解決をしたい場合、format_en_GB.properties
, exceptions_en_GB.properties
, windows_en_GB.properties
をそれぞれ作成しておきます。
一般的に、ロケール解決はアプリケーションを動かす環境が管理します。この例では、解決したい(British)メッセージに対するロケールを手作業で指定します。
# in exceptions_en_GB.properties argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message); }
上記のプログラムの出力結果は以下のようになります。
Ebagum lad, the userDao argument is required, I say, required.
また、定義済みのMessageSource
の参照を取得するのにMessageSourceAware
インタフェースを使用可能です。MessageSourceAware
インタフェースを実装するApplicationContext
に定義したビーンは、ビーンの生成と設定時に、アプリケーションコンテキストのMessageSource
にインジェクトされます。
ResourceBundleMessageSource
の代わりとして、SpringはReloadableResourceBundleMessageSource
クラスを提供しています。このバリエーションは前者と同じバンドルファイルフォーマットをサポートしますが、標準JDKベースのResourceBundleMessageSource
よりも柔軟性があります。Springリソースロケーション(クラスパスで無くても良い)やバンドルプロパティファイルのホットリローディングが可能です。詳細はReloadableResourceBundleMessageSource
を参照してください。
5.15.2 Standard and Custom Events
ApplicationContext
内のイベントハンドリングはApplicationEvent
クラスとApplicationListener
で行います。ApplicationListener
インタフェースを実装するビーンをコンテキストにデプロイすると、ApplicationEvent
がApplicationContext
にパブリッシュされるたびに、そのビーンに通知が行われます。つまりは一般的なObserver
デザインパターンです。Springは以下の標準イベントを提供します。
Table 5.7. Built-in Events
Event | Explanation |
---|---|
ContextRefreshedEvent |
ApplicationContext の初期化もしくはリフレッシュ時にパブリッシュされます。たとえば、ConfigurableApplicationContext のrefresh() など。ここでいう"初期化"とは、すべてのビーンがロードされ、post-processorビーンの検出と有効化が行われ、シングルトンがpre-instantiatedされ、ApplicationContext オブジェクトが使用可能になった、ことを指します。コンテキストがクローズしていない限り、リレフッシュは複数回実行可能ですが、ApplicationContext が"ホット"リフレッシュをサポートしている場合に限ります。たとえば、XmlWebApplicationContext はホットリフレッシュをサポートしますが、GenericApplicationContext はサポートしません。 |
ContextStartedEvent |
ConfigurableApplicationContext インタフェースのstart() を使用するApplicationContext 開始時にパブリッシュされます。ここでいう"Started"の意味は、すべてのLifecycle ビーンが明示的なスタートシグナルを受け取ることを指します。通常、このシグナルは明示的な停止後のビーンリスタートに使いますが、自動開始(autostart)設定をしていないコンポーネントを開始するのにも使えます。例えば、初期化時に開始していないコンポーネントなどです。 |
ContextStoppedEvent |
ConfigurableApplicationContext インタフェースのstop() を使用するApplicationContext 停止時にパブリッシュされます。ここでの"Stopped"はすべてのLifecycle ビーンが明示的なストップシグナルを受け取ることを意味します。停止したコンテキストはstart() でリスタート可能になります。 |
ContextClosedEvent |
ConfigurableApplicationContext インタフェースのclose() を使用するApplicationContext クローズ時にパブリッシュされます。ここでの"Closed"はすべてのシングルトンビーンが破棄されたことを意味します。クローズしたコンテキストは寿命を終えたこと意味し、リフレッシュやリスタートは出来ません。 |
RequestHandledEvent |
HTTPリクエストが到着したことをすべてのビーンに通知するweb固有イベントです。このイベントはリクエスト完了後にパブリッシュされます。このイベントはSpringのDispatcherServlet を使うwebアプリケーションでのみ利用可能です。 |
また、カスタムイベントを作成してパブリッシュすることも可能です。以下はSpringのApplicationEvent
ベースクラスを拡張する単純な例です。
public class BlackListEvent extends ApplicationEvent { private final String address; private final String test; public BlackListEvent(Object source, String address, String test) { super(source); this.address = address; this.test = test; } // accessor and other methods... }
カスタムApplicationEvent
をパブリッシュするには、ApplicationEventPublisher
のpublishEvent()
を呼びます。通常、パブリッシュはApplicationEventPublisherAware
を実装するクラスで行い、このクラスはSpringビーンとして登録します。以下はそのクラスの例です。
public class EmailService implements ApplicationEventPublisherAware { private List<String> blackList; private ApplicationEventPublisher publisher; public void setBlackList(List<String> blackList) { this.blackList = blackList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent event = new BlackListEvent(this, address, text); publisher.publishEvent(event); return; } // send email... } }
設定時に、SpringコンテナはApplicationEventPublisherAware
を実装するEmailService
を検出して自動的にsetApplicationEventPublisher()
を呼び出します。実際には、引数にはSpringコンテナ自身が渡されます。あとは単にApplicationEventPublisher
を通してアプリケーションコンテキストとやり取りするコードを書きます。
カスタムApplicationEvent
を受信するには、ApplicationListener
を実装するクラスを作成してSpringビーンとして登録します。以下はそうしたクラスの例です。
public class BlackListNotifier implements ApplicationListener<BlackListEvent> { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } }
注意点として、ApplicationListener
は自前のカスタムイベント型のジェネリック型が利用可能で、ここではBlackListEvent
を指します。つまり、onApplicationEvent()
メソッドはタイプセーフで、ダウンキャストを避けられます。必要であれば多数のイベントリスナーを登録可能ですが、デフォルトではイベントリスナーはイベントを同期的に受け取ることに注意してください。つまり、publishEvent()
メソッドは前のイベント処理が終わるまで全てのリスナーをブロックします。同期的およびシングルスレッドアプローチの利点の一つは、リスナーがイベントを受け取るとき、もしトランザクションコンテキストが利用可能であればパブリッシャーのトランザクションコンテキスト内で処理をします。イベントパブリケーションに別の方法が必要であれば、SpringのApplicationEventMulticaster
インタフェースのJavaDocを参照してください。
以下は上記で解説した各クラスの登録及び設定のビーン定義例です。
<bean id="emailService" class="example.EmailService"> <property name="blackList"> <list> <value>known.spammer@example.org</value> <value>known.hacker@example.org</value> <value>john.doe@example.org</value> </list> </property> </bean> <bean id="blackListNotifier" class="example.BlackListNotifier"> <property name="notificationAddress" value="blacklist@example.org"/> </bean>
これらを設定すると、emailService
ビーンのsendEmail()
メソッドが呼ばれるとき、Eメールアドレスがブラックリストにあれば、カスタムイベントタイプBlackListEvent
がパブリッシュされます。ApplicationListener
として登録するblackListNotifier
ビーンはBlackListEvent
を受信し、そこで適切な当事者に通知を行います。
Springのイベントメカニズムは同一アプリケーションコンテキスト内のSpringビーン間の単純な通信用に設計されました。しかし、より高度なエンタープライズインテグレーションの要求のため、別個にSpring Integrationプロジェクトでは、building lightweight・パターン指向・こなれたSpringプログラミングモデルに基づくイベント駆動アーキテクチャ、の完全なサポートを提供しています。
5.15.3 Convenient access to low-level resources
アプリケーションコンテキストの理解とより良い使い方のためには、一般的にはユーザはChapter 6, Resourcesで解説するSpringのResource
を知るべきです。
アプリケーションコンテキストはResourceLoader
であり、これはResources
をロードします。Resources
は本質的にはJDKのクラスjava.net.URL
の機能豊富版で、実際、Resources
の実装は必要に応じてjava.net.URL
インスタンスをラップします。Resources
は透過的な方法で任意の場所から低レベルなリソースを取得し、任意の場所とは、クラスパス・ファイルシステム・標準的なURLで示す場所、などです。
アプリケーションコンテキストにデプロイするビーンに特殊なコールバックインタフェースのResourceLoaderAware
を実装することが可能で、これはResourceLoader
としてアプリケーションコンテキスト自身が渡される初期化時に自動的にコールバックが呼び出されます。静的リソースへのアクセスに使うため、Resource
のプロパティを公開し、他のプロパティのようにインジェクトが行われます。単純な文字列のパスとしてResource
プロパティを指定可能で、コンテキストが自動登録する特殊なJavaBeanPropertyEditor
経由で、ビーンデプロイ時に文字列を実際にResource
オブジェクトに変換します。
ApplicationContext
のコンストラクタに与えるロケーションパスもしくはパスは実際にはリソース文字列で、この単純な形式は特定のコンテキスト実装へと適切に処理されます。ClassPathXmlApplicationContext
はクラスパスロケーションとして単純なロケーションパスを扱います。なお、実コンテキスト型に関わらず、クラスパスやURLから定義のロードを強制するように特殊なプレフィクスを付与するロケーションパス(リソース文字列)も使用可能です。
5.15.4 Convenient ApplicationContext instantiation for web applications
ContextLoader
などを使用して宣言的にApplicationContext
インスタンスを作成可能です。また、ApplicationContext
実装の一つを使用してプログラム的にApplicationContext
インスタンスを作成することも出来ます。
以下のようにContextLoaderListener
を使用してApplicationContext
を登録できます。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
このリスナーはcontextConfigLocation
パラメータを参照します。パラメータが存在しない場合、リスナーはデフォルトとして/WEB-INF/applicationContext.xml
を使います。パラメータが存在する場合、リスナーはデリミタ(カンマ・セミコロン・空白)で文字列を分割し、アプリケーションコンテキストの検索対象ロケーションとして使います。Antスタイルのパスパターンも同様にサポートします。例としては/WEB-INF/*Context.xml
で、これは"WEB-INF"ディレクトリ内の"Context.xml"で終わるすべてのファイル名となり、/WEB-INF/**/*Context.xml
は"WEB-INF"のすべてのサブディレクトリのすべてのファイルとなります。
5.15.5 Deploying a Spring ApplicationContext as a Java EE RAR file
SpringのApplicationContextをRARファイルとしてデプロイ可能です。このRARファイルはコンテキストとそれが要求するビーンクラスとJava EE RARデプロイメントユニットのライブラリJRAをカプセル化したものです。このことは、スタンドアローンのApplicationContextを起動することと同等で、Java EEサーバの機能群にアクセスするためにJava EE環境にホストします。RARデプロイメントはheadless WARデプロイの自然な代替案です。実際、HTTPエントリポイントを持たないWARファイルはJava EE環境でSpring ApplicationContextを起動するためだけに使われます。
RARデプロイメントはHTTPエントリポイントは必要としないがメッセージエンドポイントやスケジュールジョブのみで構成されるアプリケーションコンテキストには理想的な形です。そうしたコンテキストのビーンは、JTAトランザクションマネージャ・JNDIバウンドのJDBC DataSources・JMS ConnectionFactoryインスタンス・Springの標準トランザクションマネージャとJNDIとJMXサポート機能経由でプラットフォームのJMXサーバに登録可能なもの、などのアプリケーションサーバリソースを使用可能です。アプリケーションコンポーネントはSpringのTaskExecutor
抽象化を通じてアプリケーションサーバのJCA WorkManagerと相互作用が可能です。
RARデプロイメントの設定詳細についてはSpringContextResourceAdapter
クラスのJavaDocを参照してください。
ただ単にJava EE RARファイルとしてSpring ApplicationContextのデプロイを行うには、すべてのアプリケーションクラスをRARファイルにパッケージします。このファイルは拡張子を変えた一般的なJARファイルです。すべての必要なライブラリJARをRARアーカイブのルートに追加します。"META-INF/ra.xml"デプロイメント記述子(SpringContextResourceAdapter
のJavaDoc参照)とそれに対応するSpring XMLビーン定義ファイル(多くの場合"META-INF/applicationContext.xml")を追加し、出来たRARファイルをアプリケーションサーバデプロイディレクトリに配置します。
このようなRARデプロイメント単位は通常自己完結的(self-contained)で、外部にコンポーネントを公開せず、同一アプリケーション内の他モジュールについても同様です。RARベースのApplicationContextとの相互作用は、通常、他モジュールを共有するJMSデスティネーション経由で発生します。また、RARベースApplicationContextの、例えば、スケジュールジョブは、ファイルシステム上のファイル生成(など)に反応します。外部からの同期アクセスを許可する必要がある場合、例としてはRMIエンドポイントを公開する事で可能ですが、これは当然同一マシン上の別のアプリケーションからも使用可能です。
5.16 The BeanFactory
BeanFactory
はSpring IoCコンテナの最も基礎となる部分ですが、サードパーティフレームワークとの統合にみ直接使用するだけであり、また、現在では多くのSpringユーザにとって歴史的に最もお馴染みのものです。BeanFactory
と関連インタフェースのBeanFactoryAware
, InitializingBean
, DisposableBean
はSpringと統合する多くのサードパーティフレームワークとの後方互換性のために現在でも存在しています。いくつかのサードパーティコンポーネントでは、JDK 1.4との互換性維持やJSR-250への依存性を回避するために、@PostConstruct
や@PreDestroy
などの近代的な機能が使えません。
このセクションでは、BeanFactory
とApplicationContext
との更なる違いに関する背景と、クラシックなシングルトンルックアップを通じてIoCコンテナに直接アクセスする方法を示します。
5.16.1 BeanFactory or ApplicationContext?
何かしらが出来ない明確な理由が無い限りはApplicationContext
を使います。
その理由はApplicationContext
がBeanFactory
の全機能を含んでいるからで、メモリ消費が重要で数KBが大きな違いとなるリソース制約の強い組み込みアプリケーションなど数少ない例外を除き、通常はBeanFactory
へは間接的な使用を推奨します。ただし、通常のエンタープライズアプリケーションでは、ApplicationContext
で十分です。SpringはBeanPostProcessor
extension pointを大量に(heavy)使用しています(プロキシなど)。軽量なBeanFactory
のみ使いたい場合、いくらかのステップが無いわけでは無いですが、トランザクションとAOPなどは無効化するためのサポートが方法にあります。こうした状況は混乱を招くことがあり、それは設定によって何もおきないことが実際には間違っている事があるためです*9。
以下の表はBeanFactory
とApplicationContext
インタフェースと実装が提供する機能のリストです。
Table 5.8. Feature Matrix
Feature | BeanFactory |
ApplicationContext |
---|---|---|
ビーンのインスタンス化・ワイヤリング | Yes | Yes |
自動BeanPostProcessor 登録 |
No | Yes |
自動BeanFactoryPostProcessor 登録 |
No | Yes |
(i18n用)簡易MessageSource アクセス |
No | Yes |
ApplicationEvent 発行 |
No | Yes |
BeanFactory
実装で明示的にbean post-processorを登録するには、以下のようなコードを書く必要があります。
ConfigurableBeanFactory factory = new XmlBeanFactory(...); // now register any needed BeanPostProcessor instances MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // now start using the factory
BeanFactory
実装使用時にBeanFactoryPostProcessor
を明示的に登録するには、以下のようなコードを書く必要があります。
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); // bring in some property values from a Properties file PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory);
両方のケースにおいて、明示的な登録手順は面倒くさいものであり、ほとんどのSpringベースアプリケーション、特にBeanFactoryPostProcessors
とBeanPostProcessors
を使うような、において上述のBeanFactory
よりも各種のApplicationContext
実装が好まれる理由の一つがこれです。これらのメカニズムはプロパティプレースホルダー置換とAOPなどの重要な機能を実装しています。
5.16.2 Glue code and the evil singleton
DIスタイルで多くのアプリケーションコード書く上でのベストは、コードはSpring IoCコンテナの外側に位置し、依存性の生成と提供はコンテナが行い、コードはコンテナと完全に分離することです。ただし、時には他のコードを必要とする、薄い接着剤のような役割を成すレイヤー用には、Spring IoCコンテナへのシングルトンスタイル(もしくは擬似シングルトン(quasi-singleton))のアクセスが必要です。たとえば、サードパーティのコードが新規オブジェクトを直接生成(Class.forName()
など)し、Spring IoCコンテナの外側でそのオブジェクトを取得する機能が無い場合があります。もしサードパーティのコードが生成するオブジェクトが小規模のスタブやプロキシの場合、実オブジェクトにデリゲートするSpring IoCコンテナのシングルトンスタイルを用いることで、大多数のコードで制御の反転が実現できます(オブジェクトそのものはコンテナ外から来る)。このような場合ではコードの大部分はコンテナ非依存で疎結合が維持されています。また、EJBでは、Spring IoCコンテナから取得した通常のJava実装オブジェクトにデリゲートするのにstub/proxyアプローチを採っています。Spring IoCコンテナ自体は完全なシングルトンである必要はありませんが、メモリ使用と初期化時間(HibernateSessionFactory
などをSpring IoCコンテナで使う場合)の観点からは非現実的と思われます*10。
EJB 2.1環境や、WRAファイルを横断してWebApplicationContextの親として単一のApplicationContextを共有したい場合など、service locatorスタイルでのアプリケーションコンテキストのルックアップだけが共有Springマネージドコンポーネントにアクセスするための唯一の手段な場合もあります。この場合、Spring team blog entryで解説しているユーティリティクラスContextSingletonBeanFactoryLocator
を参照してください。
*1:and it is assumed that DI metadata such as @Autowired or @Inject are used within those classes where necessary.が原文。those classesがよくわからん。@ComponentとJSR-330クラスの事で良いのだろうか?
*2:it will be picked up during the call to scan()が原文。pick upの訳に悩んだがそのままにした。@Configurationそのものがスキャン対象なので、スキャン対象パッケージ内に@Configurationがあれば自動的にスキャン候補としてピックアップするよーん、って感じなので、そのまんまにした。
*3:Using Java for this type of configuration provides a natural means for implementing this pattern.が原文。上手く訳せず
*4:本文にはこの後 ,while not in othersと続くのだが上手く訳せなかったので訳文には含めていない
*5:The XML counterpart is an update~が原文。どう訳すのが自然なのだろうか…
*6:訳せなかった
*7:These default property sources are present for StandardEnvironment, for use in standalone applications.が原文
*8:なんかちょっと上手く訳せなかった。引数args
*9:This situation could be confusing because nothing is actually wrong with the configuration.が原文
*10: it may be unrealistic in terms of memory usage or initialization times (when using beans in the Spring IoC container such as a Hibernate SessionFactory) for each bean to use its own, non-singleton Spring IoC container.が原文。for each~以下が良くわからんかったので訳には含めていない