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アプリケーションの設定例です。contextClasscontext-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という名前のビーンを見つけられないからです。
プロファイルの有効化にはいくつかの方法があり、最もシンプルな方法はApplicationContextAPIでプログラム的に行うやり方です。
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プロパティより優先して検出が行われて戻されます。MutablePropertySourcesAPIはプロパティソースを操作できるメソッドをいくつか公開しています。
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はBeanPostProcessorextension 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~以下が良くわからんかったので訳には含めていない