3.11. Java-based container configuration

3.11.1 Basic concepts: @Configuration and @Bean

@Configurationをつけたクラスに、@Beanをつけたメソッドを定義するとbeanを定義できる。というのが中心概念。メソッド名がbean名になります。

3.11.2 Instantiating the Spring container using AnnotationConfigApplicationContext

以下はAnnotationConfigApplicationContextについての説明。AnnotationConfigApplicationContextは@Configurationだけではなく、@ComponentやJSR330のアノテーションを解釈できます。@Configurationをつけたクラスは、それ自身Beanとして登録され、さらにそこに定義された@Bean追加メソッドから取れるBeanが登録されます。またJSR-330のアノテーションや@ComponentをつけたクラスもBeanとして登録されます。

3.11.2.1 Simple construction

AnnotationConfigApplicationContextはXMLがなくてもインスタンス生成できます
#うーん、これってコンセプト的にはGuiceのぱくり?


public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
複数指定できる。また渡すクラスは@Configurationだけではなく、@Componentとかでもいい。

3.11.2.2 Building the container programmatically using register(Class...)

registerメソッドで設定を追加できます。


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();
}

3.11.2.3 Enabling component scanning with scan(String...)

ComponentScanの指定です。


public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}

3.11.2.4 Support for web applications with AnnotationConfigWebApplicationContext

web.xmlに登録することでwebアプリでも使えます。(AnnotationConfigWebApplicationContext)


<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>

<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>

<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<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 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>

<!-- map all requests for /main/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/main/*</url-pattern>
</servlet-mapping>
</web-app>

3.11.3 Composing Java-based configurations
3.11.3.1 Using the @Import annotation

ConfigにConfigをインポートします。


@Configuration
public class ConfigA {
public @Bean A a() { return new A(); }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {
public @Bean B b() { return new B(); }
}
このやり方よりもっといいのは、@Configuraton自身が@Componentであることを利用し、@Configurationに対して@Autowiredを使ってインジェクションすることです。

@Configuration
public class ServiceConfig {
private @Autowired RepositoryConfig repositoryConfig;

public @Bean TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}


@Configuration
public interface RepositoryConfig {
@Bean AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
public @Bean AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
public @Bean 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");
}

依存関係がややこしくなるけど、この点はSpringSource Tool Suiteあたりを使うとBeanの依存関係グラフを見たりできるので良いと思うよ。
もし本当に依存関係を明示的にしたければ、@Configurationをインジェクションして直接getter呼んじゃえよ。


@Configuration
public class ServiceConfig {
private @Autowired RepositoryConfig repositoryConfig;

public @Bean TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}

RepositoryConfigにインタフェース咬ますと結合度下がっていいかもね。

3.11.3.2 Combining Java and XML configuration

@ConfigurationがすべてXMLの代わりになるわけじゃないと思ってる。XMLが適する場所もある。そういう場合はXMLを中心として設定を書くやり方と、AnnotationConfigApplicationContext を使ってJavaを中心に書いて、@ImportResource を使ってXMLをインクルードするやり方がある。

前者について
これはコンテナの初期化設定はXMLで行い、アドホックに@Configurationを書いていくやり方。コンテナをブートストラップするXMLを書いておくと、XMLに登録されたBeanに付与されている@Configrationを評価し、Beanを登録してくれる。で、@Configurationは@Componentのメタアノテーションなので、 をつけておくと自動的に@Configurationが検索され登録される。

後者について
@ImportResource("classpath:/com/acme/properties-config.xml")というのを付けるとBeanにXMLをおりこむことができる。こんな感じ。


@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
private @Value("${jdbc.url}") String url;
private @Value("${jdbc.username}") String username;
private @Value("${jdbc.password}") String password;

public @Bean DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}

properties-config.xml


<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

3.11.4 Using the @Bean annotation

に相当するのが@Bean。属性としてinit-method,destroy-method,autowired,nameを持ってる。

3.11.4.1 Declaring a bean

@Beanをgetterにつけると、メソッド名の名前でbeanが登録される。

3.11.4.2 Injecting dependencies

依存関係の解決について、DIに頼らずとも自分でgetterで解決するやり方もあるよ


@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}

3.11.4.3 Receiving lifecycle callbacks

@Configurationで定義されたクラスには@PostConstructや@PreDestroyでライフサイクル制御ができる。@Beanのinit-method,destory-methodでもできる。InitaializingBeanやLifecycleを実装するやり方、Awareシリーズを実装するのもぜんぜんOK.

3.11.4.4 Specifying bean scope

@Scopeを使ってください。


@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}

の代わりに@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)でscopdProxyができます。

トリッキーなやり方。抽象クラスを作り、@Configurationをつけた具象クラスで無名クラスを作って登録するのもできますよ、便利です。


@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}


@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with command() overridden
// to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}

3.11.4.5 Customizing bean naming

@Bean(name = "myFoo")

3.11.4.6 Bean aliasing

@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })

3.11.5 Further information about how Java-based configuration works internally


@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がに書い呼ばれて別インスタンスが作られるように見える。ClientDaoはsingletonのスコープなので矛盾する・・・が、実際はSpringはブートアップ時にCGLIBを引っ掛けてclientDaoの戻り値をscoped-proxyにしちゃうので、こう書いてもシングルトンが保たれる。すごいでしょ。

まあそういうわけで、JavaConfig を使うためにはCGLIBがいりますよ。あとCGLIBが介入するために以下の制約があります。

  • @Configurationをつけたクラスはfinalではならない。
  • デフォルトコンストラクタを持たなければならない。