Skip to content

Instantly share code, notes, and snippets.

@backpaper0
Last active May 29, 2019 21:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save backpaper0/6d302823826665f2c166cfe218b1e116 to your computer and use it in GitHub Desktop.
Save backpaper0/6d302823826665f2c166cfe218b1e116 to your computer and use it in GitHub Desktop.

速習Spring Boot

DIコンテナとしてのSpring Framework

コンポーネント定義

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を仕事で使ったことがない)

スコープがsingletonrequestsessionのコンポーネントを定義する方法を記載します。

まずスコープについて何も指定しなければ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で、requestsessionにしたいときはメソッドにアノテーションを付けます。

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 Boot

ここまででSpringはDIコンテナであり、アノテーションでコンポーネント定義ができるということを開設しました。

Spring Bootは自動で適用されるコンポーネント定義の集合体です。

AutoConfiguration

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を使えば割と手軽に環境の違いによってモックに差し替えるといったことができます。

dependency

Spring BootはWebやThymeleafをはじめとして様々なモジュールがあります。 artifactIdspring-boot-starter-xxxとなっています(xxxにはwebjdbcsecurityなどが入る)。 それをpom.xmlに追加するだけで先に述べたAutoConfigurationによってコンポーネント登録がされて使用可能になります。

<!-- これだけでSpring MVCが使えるようになる -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

@Configurationとproxy

TODO @Configurationが付いたクラスはproxyになり@Beanが付いたメソッドの戻り値は登録されたコンポーネントが返るようになる

TODO @SpringBootApplicationについて

proxyに関連しているブログ書いた。http://backpaper0.github.io/2018/02/22/spring_proxy.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment