2008年5月23日金曜日

Springフレームワークの続き(リベンジ)

前回失敗したrequestデータの受け渡しを(ベタですが)HttpSessionを使って実装し、とりあえずリベンジしました。

いろいろ調べていると"SimpleFormController"はformBackingObjectで画面を描画し"onSubmit"でその画面に入力された処理を行うという、ある特定の一画面を処理するのに特化したコントローラのようです。

画面間でデータを受け渡すには以下の方法があって、
1.DBやHttpSessionを使う
2.AbstractWizardFormControllerとSpring WebFlowを使う
ということのようです。

(参考ページ)getSuccessView() and formBackingObject() [Archive] dsklyut氏の投稿より

前回は「Postされたデータが取れない」と嘆きましたが、どうやらSimpleFormControllerはFormも(恐らくはRequestも)次画面に遷移する時はクリアしてしまうらしいですね。納得できる説明です。

以下が変更した内容です。

SelectProduct.java(太字部分が追加)
Requestから直でIDを取るからいらないか、と思ってBeanから抜いてしまっていたプロパティを追加しました。(本来入れておくべきだったかも)
package springapp.service;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;

public class SelectProduct {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private List products = null;

private int id = 0;

public int getId() {
return id;
}

public void setId(int id) {
logger.info("id:" + id + " set.");
this.id = id;
}

public List getProducts() {
return products;
}

public void setProducts(List products) {
this.products = products;
}
}

SelectInventoryFormController
HttpServletRequest引数を持つonSubmitに変え、SessionにObjectを詰めています。
package springapp.web;

import org.springframework.validation.BindException;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.service.ProductManager;
import springapp.service.SelectProduct;

public class SelectInventoryFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private ProductManager productManager;

protected Object formBackingObject(HttpServletRequest request)
throws ServletException {
SelectProduct selectProduct = new SelectProduct();
logger.info("productManager:" + productManager.getProducts().size());
selectProduct.setProducts(productManager.getProducts());
logger.info("selectProd:" + selectProduct.getProducts().size());
return selectProduct;
}

@Override
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
logger.info("Executed.");
SelectProduct sp = (SelectProduct) command;
logger.info("SelectProduct.getId():" + sp.getId());
HttpSession session = request.getSession();
session.setAttribute("SelectProduct", sp);
return new ModelAndView(new RedirectView(getSuccessView()));
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}

public ProductManager getProductManager() {
return productManager;
}

}

SetPriceFormController
formBackingObjectの中でSessionを参照し、前画面の結果を貰っています。
package springapp.web;

import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;
import springapp.service.ProductManager;
import springapp.service.SelectProduct;
import springapp.service.SetPrice;

public class SetPriceFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private ProductManager productManager;

public ModelAndView onSubmit(Object command) throws ServletException {
int id = ((SetPrice) command).getId();
double price = ((SetPrice) command).getPrice();

productManager.setPrice(id, price);
logger.info("Update prices by " + price + ". Id is " + id + ".");
logger.info("returning from PriceIncreaseForm view to "
+ getSuccessView());

return new ModelAndView(new RedirectView(getSuccessView()));
}

protected Object formBackingObject(HttpServletRequest request)
throws ServletException {
SetPrice ret = new SetPrice();
SelectProduct sp = (SelectProduct) request.getSession().getAttribute(
"SelectProduct");
int i = sp.getId();

for (Product product : productManager.getProducts()) {
if (product.getId() == i) {
ret.setId(i);
ret.setDescription(product.getDescription());
ret.setPrice(product.getPrice());
break;
}
}
return ret;
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}

public ProductManager getProductManager() {
return productManager;
}

}


まずは一段落。Springの評価が少し上がりました(私の誤解が解けただけですけどね)。

2008年5月22日木曜日

「同じ」と「違う」(5)~ 本質を見極める

抽象化とアナロジー、そして「同じ」によって人が物事を理解します。しかし、その理解は具体的な差異(「違う」ということ)を捨象して得られたものです。つまりその理解からは具体的な何かが失われている。ありのままの「物事それ自体」は理解されていないのです。

(補足)ここでは別にカントの「物自体」のような大げさなことを言っている訳ではありません。多分に牽強付会ではありますが、むしろフッサールの言う(「自然的態度」と対比させた上での)事象それ自体に近いという思いはあります。

誤解なのか、それとも本質を見抜いた理解なのか。どのようにして人は本質を見抜くことができるのか。

そもそも本質ってなんだよ、と思ったあなたは鋭いですね。ですが、ここでは「本質とは」という議論には踏み込みません。誤解ではなく、妥当な理解という定義に留めます。

では、抽象化しアナロジーによって把握した上でなおかつ誤解に至らないようにするには、どのようにしたらよいのでしょうか。

別にあっと驚くような意外な方法論があるわけではありません。本質に近づくためには、まず抽象化して削ぎ落とす前に、十分に具象を把握することが必要なのです。それからアナロジーによって把握すると同時に、そのアナロジーが取りこぼした差異をできるだけ把握しておくことです。

その努力を怠れば、粗雑な抽象化による乱暴な認識に留まってしまいます。

当たり前のことですね。

例を上げればきりがありません。
ブラックバスだろうがフナだろうが魚は魚。同じだろう。在来種だろうが外来種だろうが木は木だ。草は草だ。その認識が生態系の破壊をもたらします。自然界は微妙なバランスの上に成り立っている。放っておけば(あるいは粗雑な理解で介入すれば)荒れるばかりです。

あるいはシステムの理解もそうですね。OracleもDB2も同じだろう。要は同じRDBMSだ。安ければいい。ある意味では本質的な意見ではあります。例えば経営層などであれば、SQLの方言だとか細かい使い勝手の差異を気にする必要はありません。しかし「OracleもDB2も同じだ」という認識ではプロジェクトは回りません。発生する障害の種類がまるで違う。互換性が違う。サポートが違う。個々の機能の名前と実装が違う。

そのように同じ物を別のレベルで抽象化して見てしまっている両者が話をする時は、お互いに垣根を乗り越える必要があります。残念ながらその垣根を越えさせられるのは、大抵弱い立場の人間(ベンダーや現場の人間)であるわけですが。

偉い人も含めて、われわれが普段周りの世界を乱暴に認識していることにも気がつくべきでしょう。世の中はそんなに単純ではない。自然にしてもシステムにしても、単純に抽象化して割り切れるようなものではないのです。

(一旦終了)

2008年5月20日火曜日

「同じ」と「違う」(4)~ 抽象化

前回の相対性理論に関する記述を少し補足します。
相対性理論に含まれる数式は何語に翻訳されようとも当然同じです(というか翻訳の対象ではないですよね)。これは「記号」の配列だからです。しかし相対性理論の「解釈」は異なる可能性がある。量子力学を理解している人と、講談社ブルーバックスを斜め読みした人とは当然解釈がことなる。そういうことです。

では「似ている」について。

通常「同じ」と混同されている言葉に「似ている」があります。

例えば「Javaの参照とC言語のポインタ」は似ている」⇒「要するにJavaの参照ってのはポインタだよね」。このようにして人は新しい物事を理解します。つまり「似ている」というのは物事を把握するための重要なステップなのです。

(余談)「Javaの参照とC言語のポインタを混同するな!」と怒る人もいそうですが、Javaで普通に開発するのであればその程度の理解で十分だと思います。

しかし、「似ている」(以降はそれっぽく「アナロジー」と呼びます)とするためにはその前段階である特徴なり構造なりを取り出す必要があります。Javaの参照とポインタの例で言えば、前者はクラスの実体ではなくその(比喩的には)場所を指しています。ポインタは実メモリの番地を指している。まず、それぞれが「場所を指している」ということを取り出す必要があります。これを抽象化と言います。

事物を抽象化して構造なり特徴なりを取り出し、別のものとアナロジーによって理解して「同じ」だと判断する。ここには二つの「切り捨て」(捨象)が含まれています。

まず抽象化によって細部(具象)が切り捨てられる。例えば虫について言えば、ゴキブリだろうが蝿だろうが「足を6本持った存在」まで切り捨てられます。これが一つ目の捨象。次にその足がどのような足かに関わらず「足が6本なら、同じだ」とされます。これが二つ目の捨象。

抽象化とアナロジーによって世の中は単純に見えます。
虫に何かいちいち構っていられない。自分が乗る電車にいちいちこだわってはいられない。虫は虫だろう。電車は電車だ。そう断ずることによって、考える手間がずいぶん省けます。

しかし当然副作用もあります。つまり「同じ」と思い込んでいるけれども実は全てが違う。つまり誤解をしている可能性だって大いにある。もちろん、抽象化して「同じ」だと断じた結果、本質を取り出すこともできます。

抽象化した結果、無理解に陥ったのか。それとも本質を見抜いたのか。次にそれが重要となります。

(続く)

Springフレームワークの印象

ざくっといじってみた結果の、現時点での印象を書きます。

【良い点】
・とにかくデザインが素晴らしい
Spring上で開発されたアプリは、コンポーネント間の結合粒度が粗くなります。その結果↓
 ⇒ インターフェースがきちんと分かれる
 ⇒ 各コンポーネント(クラス)が独立して開発可能です。変更の影響範囲も分かり易い。つまり Spring で開発していると「UTやっておけば大丈夫」という「おおむね安心という心証」を得る機会が多くなります。これは素晴らしいことです。

Struts はシンプルで機能を絞り込んでいるが故に、開発側の自由度が上がります。つまりいろいろなことが出来てしまいます。しかし、Spring 上でアプリを開発した場合は、当面「Springのお作法に従わなければならない」という不自由さはあるものの、結果としてコードがキレイに分割されることになります。
自身のデザインではなく、自身の上で稼働するアプリのデザインを実に美しく規定していますこれはすごいことです。

XML地獄は健在だと以前に書きましたが、コンポーネント(クラス)同士を疎結合するためのXMLであるとすれば、その欠点を補って余りあるデザインになっていると思います。

【中立】
POJO(Pure Old Java Object)を標榜していますが、私見ではギリギリのところで踏みとどまっている印象があります。

私の感覚では「ツールによって開発すると便利だが、いざとなったら自分でXMLやJavaソースをゴリゴリ編集してメンテナンスできる」というのが最低限の基準です。
この基準からすると、例えばXML定義ファイルが完全に正となってそこから全て生成するようなツールはアウトです(アウトプットに手を入れられなくなるため)。他にもベンダーが提供する開発ツールを離れると事実上何もできなくなるような技術もダメ(例えばEJB。WebSphere と Rational Application Developer の連携がよい例ですね)。

Ant と エディタ(あるいはeclipseの標準機能)、百歩譲って xDoclet が限界で、それ以上は勘弁して欲しいというのが個人的な基準ですが、Spring は何とかその基準をクリアしているようです(おまえの勝手な基準だろ?と言われればその通りですが)。

「何とかクリア」というのは、やはりブラックボックス的な要素(Daoなど)があるからです。しかし、そこはいざとなったらソースを読めば何とかなるはずということは分かっていて、しかしそのソースを読む時間も体力もないな、というあたりの迷いが「中立」という立場につながっています。

それから学習体力もバカになりません。結構 Spring を理解して使うのは大変だと思います。(Web で Spring を試していらっしゃる方は皆さん優秀です!びっくりします)

【現時点での要注意事項】
・ドキュメントが足りない
過渡的なものでしょうが情報が足りません。前回、チュートリアルを拡張しようと頑張ってみたのですが、結局失敗しました。その経験から(あつかましくも)言ってます。(5/23追記:成功しました。「Springフレームワークの続き(リベンジ)」
「おまえの力が足りない」と言われればその通りです。それは認めましょう。しかし「一覧表から項目を選択して、編集のために次画面に渡す」というのはどのWebアプリにも含まれるような、極一般的な作りだと思います。それが簡単に出来ないのはやはり問題だと思われました。

具体的には
-radioボタンを Spring的に動的に生成する方法が分かりませんでした。(ゴリゴリとコーディングしてしまいました)
-前画面からデータを受け取って次の画面に描画することができませんでした。(formBackingObjectのrequestからpostデータが取れませんでした)

恐らくは「私の Spring に対する無理解」「当初のデザインが根本的に失敗している」「他に有効なメソッドがあるのを見落としている」といった原因があるのでしょうが、英語のページ含めてどんなに調べても達成できなかったのは残念なところです。

【総評】
確かに素晴らしいアプリケーションです。しかし例によって(この業界でありがちなことですが)喧伝され過ぎているきらいがあります。
(ある種の人からはバカにされるかもしれませんが)Strutsと生のJDBCでゴリゴリ書くのと比べてどちらが良いか。迷って迷ってやはり Spring を選んだ方がよいような気がしないでもないということはないと言い切るにはいささかやぶさかである、といったところでしょうか。