Spring FrameworkはDIコンテナです。 古くはXMLでコンポーネントを定義していました。 こんな感じのXMLです。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="com.example.Foo" />
<bean id="bar" class="com.example.Bar" />
<property name="foo" ref="foo" />
</bean>
</beans>
アノテーションでもコンポーネントを定義できます。
package com.example;
import org.springframework.stereotype.Component;
@Component
public class Foo {
}
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Bar {
@Autowired
private Foo foo;
public Foo getFoo() {
return foo;
}
}
また@Bean
をメソッドに付ければ戻り値をコンポーネントとして定義できます。
これは例えばアノテーションを付けられないクラスをコンポーネントとして扱いたい場合に便利です。
package com.example;
public class Foo {
}
package com.example;
public class Bar {
private final Foo foo;
public Bar(Foo foo) {
this.foo = foo;
}
public Foo getFoo() {
return foo;
}
}
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FooBarConfig {
@Bean
public Foo foo() {
return new Foo();
}
@Bean
public Bar bar() {
return new Bar(foo());
}
}
現在はXMLよりもアノテーションでコンポーネント定義をするのが主流です。
コンポーネントには他のコンポーネントをインジェクションできます。 インジェクションの方法はいくつかあります。
まずコンストラクタインジェクション。 コンストラクタ引数がインジェクション対象となります。
package com.example;
import org.springframework.stereotype.Component;
@Component
public class Bar {
private final Foo foo;
public Bar(Foo foo) {
this.foo = foo;
}
}
次にフィールドインジェクション。
インジェクション対象のフィールドに@Autowired
を付けます。
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Bar {
@Autowired
private Foo foo;
}
それからセッターインジェクション。
インジェクション対象のセッター(セッターじゃなくてもメソッドなら良さそうな感じはする)に@Autowired
を付けます。
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Bar {
private Foo foo;
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
}
Springチームはコンストラクタインジェクションを推奨しています。 私も次の理由からコンストラクタインジェクション派です。
- フィールドを
final
にできる - インジェクション対象が多すぎる状態を可視化できる(依存が多すぎる状態は良くない)
コンポーネントはスコープを持ちます。
スコープ名 | 説明 | 私の感想 |
---|---|---|
singleton |
コンテナ内で一つのインスタンス。デフォルトのスコープ。 | 一番よく使う |
prototype |
コンポーネントを要求するたびにインスタンス化される。 | 特に使いどころが思いつかない |
request |
一回のHTTPリクエストごとに一つのインスタンス。Webのスコープ。 | ログインユーザーの情報を表すときに使う |
session |
HTTPセッションごとに一つのインスタンス。Webのスコープ。 | あまり使わない |
globalSession |
session に似たスコープで、Portletで使うらしい |
使ったことない(Portletも使ったことない) |
application |
ServletContext ごとに一つのインスタンス。Webのスコープ。 |
基本的に1アプリケーション1プロセスなのでそうするとsingleton と変わらない |
websocket |
WebSocketのセッションごとに一つのインスタンス。Webのスコープ。 | WebSocketを使う場合は使うかも(そもそもWebSocketを仕事で使ったことがない) |
スコープがsingleton
、request
、session
のコンポーネントを定義する方法を記載します。
まずスコープについて何も指定しなければsingleton
です。
package com.example;
import org.springframework.stereotype.Component;
@Component
public class Foo {
}
request
はクラスに@RequestScope
を付けます。
package com.example;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
public class Bar {
}
session
はクラスに@SessionScope
を付けます。
package com.example;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;
@Component
@SessionScope
public class Baz {
}
@Bean
でコンポーネントを定義する場合もデフォルトはsingleton
で、request
やsession
にしたいときはメソッドにアノテーションを付けます。
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
@Configuration
public class FooBarBazConfig {
@Bean
public Foo foo() {
return new Foo();
}
@Bean
@RequestScope
public Bar bar() {
return new Bar();
}
@Bean
@SessionScope
public Baz baz() {
return new Baz();
}
}
SpringのコンテナはApplicationContext
です。
ApplicationContext
がコンポーネントを管理していて、getBean
メソッドなどで取り出すことができます。
※ただし、DIコンテナを使う場合はlookupはしないようにしましょう。 lookupは実行するその時までコンポーネントが存在するか分かりません。 また、依存対象がシグネチャに現れないのでソースコード上の検索がこんなんです。 injectionであればコンテナ起動時にコンポーネントの存在はチェックできますし、依存対象がシグネチャに現れます。
余談ですがコンテナの実体は他のDIコンテナでいうと、Java EEのCDIならBeanManager
、Seasar2ならS2Container
、Google GuiceならInjector
がそれに当たります。
ここまででSpringはDIコンテナであり、アノテーションでコンポーネント定義ができるということを開設しました。
Spring Bootは自動で適用されるコンポーネント定義の集合体です。
spring-boot-autoconfigure
というJARにMETA-INF/spring.factories
というファイルがあります。
これを開くと次のような記述があります。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
(以下略)
=
の右辺にはコンポーネントを定義するクラスが,
区切りで列挙されており、条件に合うと自動で読み込まれてコンポーネントが登録されます。
条件に合うと、と述べましたが条件とは何でしょうか。
例えばThymeleafAutoConfiguration
を開いてみましょう。
クラス定義は次のようになっています。
@Configuration
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass(TemplateMode.class)
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
3行目の@ConditionalOnClass
が条件です。
これはクラスパス上にTemplateMode
というクラスがあれば、このThymeleafAutoConfiguration
を読み込むということです。
条件アノテーション | 説明 |
---|---|
@ConditionalOnClass |
クラスパス上に指定されたクラスがあればコンポーネント登録する |
@ConditionalOnBean |
指定されたコンポーネントがあればコンポーネント登録する |
@ConditionalOnMissingBean |
指定されたコンポーネントが無ければコンポーネント登録する(上書きを想定してデフォルト値を設定する時に使う) |
@Conditional |
value 要素に設定するCondition を評価した結果に応じてコンポーネント登録する |
@Profile |
プロファイル(spring.profiles.active )に応じてコンポーネント登録する |
@Conditional
と@Profile
を使えば割と手軽に環境の違いによってモックに差し替えるといったことができます。
Spring BootはWebやThymeleafをはじめとして様々なモジュールがあります。
artifactId
はspring-boot-starter-xxx
となっています(xxx
にはweb
やjdbc
、security
などが入る)。
それをpom.xml
に追加するだけで先に述べたAutoConfiguration
によってコンポーネント登録がされて使用可能になります。
<!-- これだけでSpring MVCが使えるようになる -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
TODO @Configurationが付いたクラスはproxyになり@Beanが付いたメソッドの戻り値は登録されたコンポーネントが返るようになる
TODO @SpringBootApplicationについて
proxyに関連しているブログ書いた。http://backpaper0.github.io/2018/02/22/spring_proxy.html