1クール続けるブログ

とりあえず1クール続けるエンジニアの備忘録

黒べこ本(kotlin)でSpring Boot入門メモ③ (DI・バリデーション)

DIとは

DIはDependency Injectionのことで日本語では依存性の注入と言われる。Springのドキュメントでは基本的には、類似の意味を持つIoC(Inversion of Control)という言葉が使われている。DIは、オブジェクトが依存関係を定義する処理となる。以下に挙げる方法でのみ、オブジェクトが依存する他のオブジェクトの定義を行う。

  • コンストラクタ引数 ex) val regex = Regex("Kotlin")
  • ファクトリメソッドの引数 ex) val mutableList = mutableListOf(1, 2, 3)
  • 返り値のオブジェクトインスタンスに設定されるプロパティ ex) person.age = 25

DIコンテナ

DIコンテナは、Beanを作成するときにそれらの依存関係を注入する。BeanとはSpring IoC コンテナによって管理されるオブジェクトのことです。コンテナによって管理されるオブジェクトとしては以下のものが例としてあげられる。

  • ViewResolverのBean (Controllerの戻り値の文字列を解決してViewを戻す処理を行う)
  • ValidatorのBean
  • DataSourceのBean
  • トランザクションマネージャのBean
  • 型変換のBean
  • Controller・Service・Repositoryなどの業務ロジックに関係したBean

DIコンテナは、ConfigurationにしたがってBeanを作成し、IDを割り振って管理する。インタフェースorg.springframework.context.ApplicationContextがSpring IoCコンテナを表している。

Beanの定義

Configurationの設定の仕方は以下の3種類ある。

<bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
</bean>
  • JavaConfig
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

インスタンスをBeanとしたクラスに@Componentもしくは@Componentをベースにしたアノテーションを付与する(@Controller / @Service / @Repository / @Configuration)
Java.configに@ComponentScan(basePackages = {"hoge.service.impl"})を付与する。ここで指定したベースパッケージ配下のアノテーションをチェックする。
@Autowiredをインスタンス変数に付与することでコンテナ内から、インジェクションする対象となる。

@Controller
@RequestMapping("houses")
class HouseController {

    @Autowired
    private lateinit var shs: HouseFactoryImpl

@Autowiredの使い方

@Autowiredを使用したDependency Injectionの種類は以下の3種類有ります。記述が簡易なフィールド・インジェクションが使用されることが多いと思われます。ただフィールド・インジェクションは、Spring FrameworkのDIコンテナに依存してしまう点と設計の妥当性(依存関係が多くなり過ぎていないか、循環依存になっていないか)が分かりづらいということが有り、必須の依存関係でコンストラクタ・インジェクション、オプションの依存関係をセッター・インジェクションを使用する方がアプリが大規模になる場合に有効であると言えます。

  • コンストラクタ・インジェクション
class Foo @Autowired constructor(dependency: MyDependency, service: MyService) {
    //...
}

ただし、Spring4.3以降でコンストラクタが単一の場合は@Autowiredが省略できるため、以下のようになります

class Foo(dependency: MyDependency, service: MyService) {
    //...
}
  • フィールド・インジェクション
    lateinitはプロパティを遅れて初期化することをコンパイラに知らせるものです。 バッキングフィールドを持つプロパティは初期化が必要となるはずですが、lateinitを付けることでプロパティ宣言時の初期化を避けることが出来ます。
class Foo {
   @Autowired
   private lateinit var dependency: MyDependency
   @Autowired
   private lateinit var service: MyService
}
  • セッターインジェクション
    kotlinでは無し? → 調査中

バリデーション

Formのクラスを作成し、そのクラスのフィールドにアノテーションを付与することで、単項目チェックを行うことができる。Formのクラスは、HTMLなどから受け取る送信formに対応するもの。アノテーションは、Bean Validationの実装であるhibernate validatorを利用する。Bean Validationは値検証のためのJavaの仕様の一つで、hibernate validatorはそのリファレンス実装となっている。 相関チェックは、Validatorインターフェースを実装したチェック用のクラスを作成し、Controllerにインジェクションします。

単項目チェック

class TaskCreateForm {

    @NotBlank
    @Size(max = 20)
    var content: String? = null
}

仕様頻度が高そうなアノテーションは以下

  • @Length(min=, max=) //文字列長の検査
  • @Max(value=) //数値の最大値の検査
  • @Min(value=) //数値の最小値の検査
  • @NotNull //値がNullでないか
  • @NotEmpty //文字列がNullまたは空文字で無いか
  • @Pattern(regex="regexp", flag=) //正規表現に一致するか否か

以下のようにメソッドの引数としてとったFormにアノテーション@Validatedをつけ、そのすぐ次の引数にBindingResultを取るようにすると、バリデーションが施され、その結果がBindingResultオブジェクトとして表現される。BindingResultオブジェクトのhaserror()メソッドを使用することで検査の結果を得ることが出来ます。

class TaskController(private val taskRepository: JdbcTaskRepository) {

    @PostMapping("")
    fun create(@Validated form: TaskCreateForm,
               bindingResult: BindingResult): String {
        if(bindingResult.hasErrors())
            return "tasks/new"

        val content = requireNotNull(form.content)
        taskRepository.create(content)
        return "redirect:/tasks"
    }

}

次は例外処理周りか永続化について書きます。


参考記事

SpringでField InjectionよりConstructor Injectionが推奨される理由 - abcdefg.....

[随時更新]SpringBoot(with Thymeleaf)チートシート - Qiita

Validator (Java Platform SE 8)