出張Java講座: 身体で覚えるSpring Framework
- Spring Framework(以下、Spring)の概要を知る - (可能な限り)身軽に(*)Springプログラミングを試して、身体で感覚を養う - ついでに、5分でWebとServletの動作を知る
出張Java講座: 身体で覚えるSpring Framework
最初に
色々と私見が入っていますが、明示しているので、気に入らない人は無視してください。
本日の勉強会の目的
- Spring Framework(以下、Spring)の概要を知る
- (可能な限り)身軽に(*)Springプログラミングを試して、身体で感覚を養う
- ついでに、5分でWebとServletの動作を知る
(*)
- Javaの世界は、簡単なことに大袈裟な準備が多すぎて、くじけそうになります。
- 普通の環境(エディタとシェルだけ)で気軽に試すことを目指します。
Springをひとことで言うと(私見)
- 偏執狂的に疎結合を追求したフレームワーク。
- 理想主義なソフトは現場で使えないことが多いですが、Springは現場で使えます。
位置付け
一般的には...
- Springは軽量J2EEアプリケーションフレームワーク、として知られます。
- Springは機能盛り沢山かつ疎結合な作りなので、一部分を取り出してJ2EEと無関係に使うことも可能です。
話を発散させたくないので、J2EEに話を限定します。
- J2EEが何か分からない人は、(正確ではないですが)「サーブレット周りのJava」、だと思ってください。
本日扱う範囲と扱わない範囲
扱う範囲
- SpringのDI(Dependency Injection)
- SpringのWeb MVCフレームワーク (サーブレットエンジンはTomcatを利用)
扱わない範囲
- AOP
- データベース周り(ORM、DAO)
事前準備
以下をインストール、セットアップしてください
- JDK 1.5 (http://java.com)
- ant 1.6.x (http://ant.apache.org)
- Tomcat 5.5.x (http://tomcat.apache.org/
- Spring 1.2.x (http://www.springframework.org)
Springは依存関係が多いので、-with-dependenciesのついた配布を使うのがお薦めです。
J2EEを離れてDIを理解する
DIについて (2004年1月)
- 「Inversion of Control Containers and the Dependency Injection pattern」 by Martin Fowler
- http://martinfowler.com/articles/injection.html
用語などは基本的にこれに従います。
ソフトウェアコンポーネント
- コンポーネント: ソースを変更せずに使えるソフトウェア(by Martin Fowler)
定義は曖昧です...
理念としては、「再利用可能な部品のような」ソフトウェア
「境界を意識して」、「依存度(*)を意識して」、ソフトウェアをモジュール化していれば、結果的にコンポーネントになります(たぶん)。
狭義では、バイナリ/言語に対する非依存をコンポーネントの要件にする宗派もあります。Javaの世界に閉じこもれば無関係です。
(*)依存度をシンプルに。ほとんどの場合、依存度を小さくすることでシンプルさは達成できますが、目指すべきはシンプルさです(私見)。
J2EEを離れてDIを理解する(cont.)
DIという用語が現れる以前に、IoC(Inversion of Control)と呼ぶ人もいました。
IoCという用語は、いわゆるイベントドリブンに見られる「Hollywood Principle (don't call us, we'll call you.)」と区別がつきません
=> わざわざ新しい用語を持ち出すまでも無い
- Martin FowlerがDIという用語を提案
DIの特別さはどこにあるのか?(私見)
- 具象クラスへの依存をソースコードの外部に追い出す点
- 実行時の具象クラスへの依存解決のために、それを解決する汎用ファクトリ(ビルダー)フレームワーク(*)
(*)リフレクションを活用して、オブジェクトの生成と構成を行います
依存を外部化 => POJO(Plain Old Java Object) => 単体テストしやすい! => Javaは素晴らしい!
...そもそも依存度を小さくモジュール化するのはJava以前の常識なので、Javaの世界が狂っていただけ
しかし、DIは素晴らしいと思います。まさに「家貧しくて孝子顕る」です
DIをとても単純化すると...
public class MyFoo { private List<String> list = new ArrayList<String>(); ... }
この"new ArrayList<String>()"をソースコードから消して、外部(普通はXML設定ファイル)に追い出します。
DIを知らない、普通の感覚でソースを見ると、本来あるべき"new Foo"が見つからなくて不思議な気分になります。
DIをとても単純化すると...(cont.)
そもそも
<< ArrayList<String> list = new ArrayList<String>(); //bad >> List<String> list = new ArrayList<String>(); //good
後者で書く気持ちは、"listはListインターフェースとして振る舞う何かを指す"の意志表示なので、変数の型からArrayListを追い出しても、"new ArrayList"がソースに残っていたら負けです。
=> だからDIを使います。
DIの分類
個人的にはこの分類にあまり意味を感じませんが(*)、3つのタイプにDIを分類します
- Constructor Injection
- Setter Injection
- Interface Injection
(*)個人的には、「Constructor Injection」派です
DIの使い所
クラスを用途で2分してみます
- エンティティ的(セッション的)
-- 実体(状態遷移)の表現を役割の主とするクラス
- サービス的(一部、関数的)
-- サービス提供を役割の主とするクラス
インターフェースに対してプログラミングして具象クラスから疎結合であるべきなのは後者。
=> DIを使うのは「サービス的」なクラス (がbetterプラクティス)
build.xmlの例
<?xml version="1.0"?> <project name="sample" basedir="." default="build"> <property name="src.dir" location="."/> <property name="build.dir" location="."/> <property name="spring.dir" value="/opt/java/lib/spring-framework-1.2.6"/> <path id="master-classpath"> <fileset dir="${spring.dir}/dist"> <include name="**/*.jar"/> </fileset> <fileset dir="${spring.dir}/lib"> <include name="**/*.jar"/> </fileset> <fileset dir="/usr/share/java"> <include name="*.jar"/> </fileset> </path> <target name="build" description="Compile main source tree java files"> <javac srcdir="${src.dir}" destdir="${build.dir}" target="1.5" debug="true" deprecation="true"> <classpath refid="master-classpath"/> <!--compilerarg value="-Xlint:all"/--> </javac> </target> <target name="run" depends="build"> <input message="Class to run?" addproperty="class_name"/> <java classname="${class_name}" classpath="."> <classpath refid="master-classpath"/> </java> </target> </project>
DI実践
- ~/src/java/di/DiTest.java
- ~/src/java/di/DiTest2.java
- ~/src/java/di/DiTest3.java
- ~/src/java/di/Counter.java
- ~/src/java/di/CharCounter.java
- ~/src/java/di/WordCounter.java
3分で理解するWebの動作(cont.)
<form action="/foo" method="GET or POST"> <input type="text" name="bar"/> <input type="submit" name="ok"/> </form>
GETの場合
=> サーバ GET /foo?bar=value HTTP/1.1 色々...(ヘッダの最後が空行) <= サーバ レスポンス
POSTの場合
=> サーバ POST /foo HTTP/1.1 色々...(ヘッダの最後が空行) bar=value <= サーバ レスポンス
3分で理解するWebの動作(cont.2)
GETでもPOSTでも、サーブレット(J2EEのAPI)でプログラミングしていれば、
- リクエストURI: /foo
- パラメータ
-- パラメータ名: bar -- パラメータ値: value
注意1
- J2EEのAPI上、パラメータ値は常に文字列
注意2
- GET /foo?bar=value&bar=value2 HTTP/1.1
こういう場合、パラメータふたつで、両方ともパラメータ名は"bar"
GETとPOSTの使い分け
理屈上はすべてPOSTでもアプリは作れます...
が、格好わるいので使い分けるべきです。
HTTPの理念的には
- GETは(Web上の)リソースを書き換えない (SQLとの対比で言えば select 相当)
- POSTは(Web上の)リソースを書き換える (SQLとの対比で言えば insert 相当) (とりあえずPUTは無視)
GETとPOSTの使い分け(cont.)
例外がふたつあります。 たとえリソースを書き換えないリクエストでも次のふたつはPOSTを使います。
- パスワードなど、URLとしてブラウザのヒストリに残ってはまずい場合
- (一般にGETのquery stringのサイズ上限はPOSTデータより小さいので)リクエストデータが大きい場合
GETを積極的に使うべき場面
- query string込みのURLに意味がある場合 (eg. 地図上の位置を示すURLで座標をquery stringで持つ場合)
クッキー
基本的なアイディア
- サーバがレスポンスにクッキーをセットすると、それ以降、リクエストにCookieヘッダが毎回載ってくる
- サーバはリクエストからクッキーの値を知ることができる
- サーバ(Webアプリ)が、誰(ユーザ)にどんなクッキーを与えたかを覚えておけば、リクエストが誰から来たかが分かる
J2EEの世界では、クッキーもしくは(クッキーが使えない環境のために)URLパラメータでユーザ識別をします。
=> セッション管理
セッション管理の文脈では、J2EEがクッキーの存在を隠蔽しているので、アプリ開発者は気にする必要はありません。
keyword: HttpSession
3分で理解するサーブレットエンジンの動作
クラシックなWebサーバ
- URL => ファイルシステム上のファイルにマッピング
色々、変遷...
サーブレットエンジン
- URL => Javaのクラス(*)にマッピング
該当するクラスのインスタンスは、リクエストを入力として受け取り、出力としてレスポンスを返すように実装します(J2EEのAPI利用)。
(*)狭義の「サーブレットクラス」
URLとクラスのマッピング
web.xmlファイルで行います(Servletエンジンのレベル)
web.xmlファイルの見方
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
- DefaultServletクラス(ちなみにこれはファイルシステム上のファイルを返す)に"default"という名前を付ける(web.xml内で識別するための名前)
- URLパターンとクラスのマッピング
URLとクラスのマッピング(FYI)
servlet spec. 11.2. Specification of Mapping
- beginning with '/' and ending with '/*' => パスマッピング - beginning with '*.' => 拡張子マッピング - only '/' => デフォルトマッピング - exact match
<!-- jspを処理するクラスとURLパターンのマッピング例 --> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> </servlet-mapping>
Webフレームワーク、MVCアーキテクチャ
理屈上は、
- URL定義
- 対応するクラスを実装(リクエストを受けてレスポンスを返す)
- ひたすらマッピング
で、サーブレット上のWebアプリ開発はできます。
全部jspにして*.jspのマッピングに任せたり、(Tomcatの)invokerクラスで*.classを直接叩くURLにすると、深く考えずマッピングができます。
が、ちょっと格好悪いのと、気を付けないと、次のような弊害がでてきます。
- 共通処理をどこに置くか? (各クラスで正しく呼び出す?)
- ビュー(表示処理)側のカオスがコード全体に浸蝕
=> MVCアーキテクチャ(役割を明確化してモジュール化)
MVCアーキテクチャ(step1)
コントローラを明確化します
- URL => 処理クラスへマッピング
この役割を分離します
宗派
- フロントコントローラひとつ vs. 複数
- サーブレットコントローラ vs. JSPコントローラ
Springは
- フロントコントローラひとつ
- サーブレットコントローラ
Springのコントローラ(MVC)
web.xml(Tomcatの世界)
<servlet> <servlet-name>appname</servlet-name> <!-- appnameはアプリ名 --> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>appname</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
appname-servlet.xml (アプリ名-servlet.xmlはSpringが読むファイル)
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/login"><ref local="authorizeController"/></entry> 略
- URLパターンとクラスのマッピングを記述
Springのコントローラ(MVC)(cont.)
結果的に、
コントローラという名前で、微妙に異なる役割の2種類のクラスが存在します
- 最初にディスパッチ(処理の振り分け)をするだけのコントローラ
- 上のコントローラから処理を委譲されて、ビジネスロジックの呼び出しとビューの選択を行うコントローラ
前者を「ディスパッチャーコントローラ」「コントローラ of コントローラ」と呼んで、後者と区別します。
多くの場合、「ディスパッチコントローラ」はWebフレームワークが用意しているので、アプリ開発者が実装するのは、後者の「(狭義の)コントローラクラス」です。
- ビジネスロジック
- アプリケーション(固有の)ロジックを実装した部分のこと。
MVCアーキテクチャ(step2)
モデルという用語の宗派
- コントローラ、ビュー以外のすべて
- ビジネスロジック(バックエンドDBへのアクセス層(エンティティ層)を含まない)
- ビジネスロジック(バックエンドDBへのアクセス層を含む)
- バックエンドDBへのアクセス層(=> ActiveRecord的発想)
- ビューが参照するデータ(ビジネスロジック独立)
Springは
- ビューが参照するデータ(ビジネスロジックやエンティティ層から独立)
Springのモデル(MVC)
Springのモデルは
- ビジネスロジックやバックエンドから独立したデータ
- ビューが参照するデータ
なので、結果的に
- コントローラクラスが、ビジネスロジックの出力データを加工(*)してモデルを生成。そして、ビューに渡す
ことになります。
(*)「加工しない」ケースも含みます。つまり出力データそのものの場合です。
Springのモデル(MVC)(cont.)
理屈上は、
- モデルはビューに依存しない
- ビューはモデルに*だけ*依存する
となります。
この時、コントローラは、
- 呼ぶべきビジネスロジックのインターフェースは知っている
- 呼ぶべきビューの名前(識別子)は知っている
- ビューに渡すべきモデルの詳細を知っている
ちょっと知りすぎ(*)なので、コントローラとビジネスロジックの間に「サービス層」を作るのが推奨です
(*)依存度をシンプルに、の原則
Springのモデル(MVC)(私見)
で、こんなにモデルを偏執狂的に裏側から切り離すことに意味はあるのか?
アリエルでは、ここまで偏執狂的にやっていません。
一方、ビューのコードは、変更しやすく保ちたいので、
- ビューはモデルに*だけ*依存する
ことは重要です(ビューの依存度をシンプルに)
モデルを通じて、裏側の事情がだだ洩れにならないようにはしたいものです。
(参考)StrutsのMVC
- ディスパッチャーコントローラ; Strutsが担当
- コントローラ; ActionFormの形でアプリ開発者が実装
- モデル; Beanオブジェクト(フォームに対応するBean、およびビジネスロジックを実装したBean)
- ビュー; JSP
HTMLのフォームが中心となるアーキテクチャ(に見える)
- フォームに対応したBeanクラス(フォームのコントロールとプロパティが1対1に対応したBeanクラス)を実装(by アプリ開発者)
- Strutsディスパッチャーコントローラが、フォーム入力(POSTリクエスト)に対してBeanオブジェクトを生成
- コントローラ(アプリ開発者が実装)にBeanオブジェクトが渡される
Java Beans
いくつかの必要条件を満たしたクラスは、Beanクラスになりえます。 厳しい必要条件ではないので、どちらかと言うと、利用側の意図で、「これはBeanかどうか」が決まります。
# EJB(Enterprise Java Beans)は、更に(主に分散処理を念頭にした)必要条件が追加した別物です。
Beanクラスは、プロパティをprivateに持ち、setterとgetterのアクセサメソッドを提供します。
# ここから厭味。
- Beanクラスを実装すると、行数がたくさん書ける => Javaプログラマの生産性は高い!
- (ビルド時の)「コードの自動生成」は(やり方を間違わなければ)良いプラクティスですが、ツールによる「コードの自動挿入」はバカげた手段です。
MVCアーキテクチャ(step3)
Webアプリに限定すれば、ビューの定義は
- HTTPのレスポンスデータを生成する部分(プレゼンテーション層)
です。
多くはHTML生成ですが、AJAX対応などでXML生成やJSON生成だったりします。
ビュー、JSP
Javaの世界だけでもビュー技術はたくさんありますが、保守本流はJSPです 保守的なので、以下、JSPに話を限定します
# Spring自体はJSPへの依存を避けて、異なるビュー技術をサポートしています
Springのビュー
JSPの動作イメージ
- 実際にはサーブレットのクラスになって動作します
つまり、サーブレットの.javaファイルを書くことと、.jspファイルを書くことは記述の差でしかありません
# 感覚的には、
# サーブレットクラスの場合、Javaのコードの中にHTML出力コードが紛れ込む # JSPファイルの場合、HTMLの中にJavaコードが紛れ込む
JSPのアンチパターン
- JSPの中になんでも書いてしまうスタイル(*)
=> JSPの中に書かれたロジックを「スクリプトレット」と呼んでバカにするのがJava流(私見)
=> ストレス発散のために、せめてPHPやASPぐらいはバカにするのがJava流(私見)
(*)これで完結するスタイルは、いわば、URLにマッピングしたクラスの中でリクエスト解析、レスポンス生成を全部やってしまうパターンのJSP版です
# でも、PHPでもやっているし、簡単な動作確認なら、これでもいいかも (私見)
SpringのJSP指定
説明の前にServletのお約束
- /webapps/アプリ名/WEB-INF/
WEB-INFディレクトリの下は、*外部から直接URLで叩けません* (叩かせてはいけないファイルを置きます)
SpringのJSP指定(cont.)
jspファイルはWEB-INFの外に置けば、外部からURLで直接叩けます(web.xmlで*.jspとのマッピングをしていれば)
Springの流儀は
- jspファイルはWEB-INFディレクトリより下に配備
- コントローラだけがjspファイルを知っている(*)
- (*)正確には「ビュー名」だけを知っている状態にして、ビュー名とjspファイルのマッピングは次のように外部定義します。
- この"viewResolver"は、ビュー名"foo"から/WEB-INF/jsp/foo.jspファイルを探して叩きます。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property> <property name="prefix"><value>/WEB-INF/jsp/</value></property> <property name="suffix"><value>.jsp</value></property> </bean>
SpringのMVC実践
概要
- JavaのWeb層は最後にModelAndView(Spring定義のクラス)のオブジェクトを返す
- ModelAndViewは、
-- 名前で識別される複数のModelオブジェクト(通常、BeanオブジェクトもしくはMap) -- ビュー名
- ビュー名で決まるJSPファイルが、モデルオブジェクトを参照 (JSTLのEL(式言語)から、モデル名で参照可能)
- ELからモデル参照方法の例
-- モデル名.プロパティ名 (Bean) -- モデル名.キー (Map)
実践
- hello,spring
補習のお知らせ
- 時間: 12/22(金) 18:00から
- 場所: アリエルネットワーク