Springとは
Spring Bootを紹介する前に、Spring Bootの基盤技術でもあるSpring Frameworkについて簡単に紹介します。
Javaを以前から使っているならば、Springという言葉を聞いたことがある方も多いはずです。Strutsなどが全盛期の頃は、Springと言えば現在のSpring Frameworkを指していました。そのため、現在でもSpring Frameworkのことを単にSpringと呼ぶ方もいます。
Spring "Framework"と呼ばれてはいますが、開発者にとってはフレームワークというよりも、DI(依存性注入)やAOP(アスペクト指向プログラミング)といったスタイルを実現するためのライブラリに近い位置づけかと思います。
そのため、Spring Frameworkはどのようなフレームワークを選んでいても、よく一緒に使われてきました。
現在でこそDIスタイルのプログラミングはスクリプト言語でもよく見かけますが、DIやAOPといったトレンドが生まれた大きな要因はJavaであり、そのJavaの中で強く影響力を与えた1つがSpring Frameworkです。
そしてその後、Spring Frameworkは表1に示す通りさまざまなニーズに応じてプロジェクトを増やし、多くの開発者が利用するようになってきました。
プロジェクト名 | 概要 |
---|---|
Spring Framework | Springプロジェクトの基本となるプロジェクト。DIやAOPなどを実現するためのフレームワーク。 |
Spring Cloud | クラウド環境を前提としてアプリケーションを分散構成として管理するためのプロジェクト。 |
Spring Data | ORマッピングなどの各種のRDBやNoSQLなどデータストアを行うためのプロジェクト。データストアごとにサブプロジェクトに分かれているので、実際に利用するミドルウェアごとに使い分ける。 |
Spring Integration | さまざまなシステムやサービスを疎結合につなげてシステム全体を構築するようなEIP(Enterprise Integration Patterns)フレームワークのプロジェクト。また、このようなシステムをEIP(Enterprise Integration Patterns)とも呼ぶので、EIPフレームワークといった呼ばれ方もします。 |
Spring Batch | バッチ処理を行うシステムを構築するためのプロジェクト。 |
Spring Shell | 独自のShellコンソールを持つためのプロジェクト。例えば、JSehllのようなREPL(Read-Eval-Print Loop)ツールのようなインターフェースを持つアプリケーションの作成が可能。 |
Spring Security | システムの「認証」や「認可」を行うセキュリティ用機能を実現するためのプロジェクト。 |
Spring AMQP | AMQP(Advanced Message Queuing Protocol)を扱うためのプロジェクト。JMSと似ているがAMQPというプロトコルを扱う点が異なる。AMQPを扱えるプロダクトはいくつかあり、Rabbit MQなどがある。 |
Spring Web | Services SOAPやXMLなどを用いたWebServiceを構築するためのプロジェクト。 |
また、これ以外にも多くのプロジェクトがあります。詳しくはこちらの公式ページを参照してください。
[Note:EIP(Enterprise Integration Patterns)フレームワークとは]
システム間の接続をメッセージング形式で定義し、それらの入出力エンドポイントとして接続することでシステム全体を構築するためのフレームワーク。例えば、FTPやSCP、メール受信などを他のシステムの入力として簡単に接続ができる。Javaで有名なプロジェクトとしてはSpring Integrationの他にApache Camelがある。
Spring Frameworkの歴史と背景
Spring Frameworkの1.0がリリースされたのは2004年です。この頃にはEJBも2.0もリリースされエンタープライズでのJavaの利用シーンも広がっていました。
しかし、多くの方にとってEJBは過剰なシステムであり、依然としてTomcat等のWebアプリケーションコンテナ上にアプリケーションを構築するケースが多い時代でした。その中で蓄積してきたコード資産や外部のライブラリなどを、効率よく利用できる仕組みが求められ、管理を最適化する需要が高まりました。さらに、実際よりも高度な機能を求めてしまっているEJBなどエンタープライズ向けのフレームワークを簡素化したい需要も生まれました。
その両者に応えるようにその存在感を増していったのがSpring Frameworkです。
Spring Frameworkの特徴
Spring Frameworkの特徴としてよく上げられるのが、AOP(アスペクト指向プログラミング)とDI(依存性注入)です。
まずAOPとは何かを簡単に説明するならば、「コード自体に変更を加えることなく、新たな処理をそのコードの実行時に加えること」です。しかし、AOPはビジネスロジックを作る開発者の間ではほとんど使われていないのが現状でしょう。
よくある説明ではログ処理などを加えるものがありますが、その程度であればAOPを使ってまで実装する必要はありません。もちろん、AOPのようなプログラミングスタイルは、汎用的なライブラリやフレームワーク開発者は知っておくべき知識であり、知っておいた方が便利ではありますが、Spring Bootを利用する上で必須の知識ではなく、どうしても必要になった時点での理解でも間に合うと思います。そのため、ここでは説明は省略します。
一方で、DIの概念は非常に一般的なものになってきています。もともとDIは、Javaなどのコンパイル言語で実行する際、オブジェクトの生成処理などを変えられない場合に、大きな効果を発揮していました。しかし現在ではスクリプト言語でも一般的に使われるようになり、「疎結合な設計」を実現するコーディングスタイルとして、DIの仕組みが使われるようになってきています。
Spring FrameworkのDI機能
Spring FrameworkのDIを定義する方法には、以下の3つがあります。
- XMLファイルを使い定義する方法(リリース当初から利用可能)
- Javaのソースコード内で定義する方法 (Spring Framework 3.0から利用可能)
- アノテーションを使い定義する方法(Spring Framework 2.5から利用可能)
Spring Bootを使う場合にはJavaのコードで定義する方法か、アノテーションを使って定義する方法がよく使われます。次回以降で紹介するサンプルではこれらの方法を用いていますが、今回はXMLを使った方法について簡単に説明します。
Spring Frameworkを使うプロジェクトはSpring Bootが初めて、という開発者の中には、XMLを使った方法があることを知らない方もいるようです。
XMLを利用する場合には、リスト1のように定義していきます。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" // ・・・省略・・・・ <bean id="main.handler" class="com.coltware.server.WebRTCSignalingWebSocketHandler" scope="prototype"> <property name="channelGroup"> <ref bean="channel.group"></ref> </property> </bean> <bean id="channel.group" class="com.coltware.channel.AllChannelGroup"> </bean> </beans>
ここでは、XMLで設定する場合の課題点を理解してもらうためにコード例を示しているため、具体的なコードの説明はしませんが、複雑なことをしようとすればするほどXMLも複雑になっていき管理が難しくなることがイメージできると思います。
XMLファイルは分割して管理もできますが、設定が大きく複雑になればなるほど、その設定が正しいかどうかも判別しづらくなり、利用しているコード部分の担当開発者でないと分からない状況になりがちです。このため、コンパイルが必要なJavaであってもXMLでの設定からだんだんと、ソースコード内に定義できる方法が広まっていったと思われます。
また、ソースコード内に定義が移っていったことにより、柔軟に定義でき、分割して管理することも容易になっていきます。
ただし、データベースの接続ユーザやパスワードを設定する場合にはソースコードベースであっても設定ファイルから簡単に値を取得するようになっていますので、その点は心配する必要はありません。
DIコンテナの必要性
設計時における「疎結合」の実現だけであれば、DIコンテナを使わずとも実現は可能です。また、運用時に後から利用するオブジェクトを変える必要性が生じるケースはほとんどないと思います。また、その必要性が生じた場合でも、その前提としてビジネスロジックに関しての機能の改善や修正なども求められるケースがほとんどであり、やはり、運用時にDIコンテナの必然性を疑問に思う方もいるでしょう。
それにもかかわらずDIコンテナは既になくてはならない要素の1つになっています。そこには現在の開発スタイルが大きく影響していると思われます。
現在、開発者にとって結合テストやサービス全体の全体テストよりも、単体テストの必要性がより重視される傾向が強まっています。
その背景には、以下の2つの理由があると筆者は思います。
- アプリケーションフレームワークを前提とした開発スタイルの普及
- 外部サービスAPIの共通化
アプリケーションフレームワークは、一般的に、それぞれの機能を自分で作成できる開発者であっても利用します。アプリケーションフレームワークを用いることで、実際の動作時のリソースやセキュリティ面などを考慮した実装を省略できるからです。
こういった利点は、開発期間を短くするだけでなく、品質の安定性やノウハウの蓄積などにも大きく寄与しています。一方でフレームワークを使う前提の場合、実装されたビジネスロジック部分をテストするときも、フレームワーク上で動作させてチェックしなくてはなりません。
すべての開発者がアプリケーションフレームワーク上でテストできるプロジェクトであれば問題ありませんが、規模が拡大した場合や、さまざまな条件下で参加している場合など、テストできない開発者が出てきてしまうケースがあります(図1)。
そこで、DI機能により、実際の本番運用や結合テストのように全体を起動させる前提の場合と、単体テストやユニットテストのように一部分だけでテストをする場合に、その前提を変えることができる仕組みになっています。
通常のWebシステムで動作確認をする場合には、その確認自体の処理の前にもいくつかの条件があります。例えば、既にログインされ認証済みであることや、POSTデータとしてサーブレットから入力データを取得する場合には、そのサーブレットが起動しているWebコンテナ自体が必要になります。
しかし、単体テストでは、これらの条件を毎回満たした上でテストする必要があるので手軽にテストができなくなります。そこでDIを利用することで、認証後のセッション情報の取得やサーブレットから必要なデータがあたかも取得できる状態としてテストできるようになるのです。
現在では、アプリケーションフレームワーク自体にテストのための機能が含まれている場合も多いので意識しなくなっていますが、DIの機能が普及する前は単体テストをするにも非常に面倒な手順を毎回行うことが多かったです。
こうした傾向は、システム上必要な普遍的な機能だけでなく、外部のサービスを使う場合でも高まっています。図2のようにアプリケーションを作成する際に外部サービスと接続することが当たり前になってきました。これらの外部サービスは有料であることも多く、すべての開発者がそのサービスを使えるケースもなかなかないのが現状です。
そのため、結合テストの段階になるまで、実際のサービスに接続できないケースもあります。そうした中で開発するためには、実サービスと接続する場合と同じテスト用のモックが必要です。
また、現在ではOAuthのように異なるサービスであっても、同様に利用できるようインターフェースが定義されたり、共通のインターフェースがなくてもそれらの違いをライブラリが吸収したりするケースが一般化しつつあり、そうすることでテスト自体を省略することもあります。
DI機能は、こういったケースでも有効に機能します。