04.基本概念:Controllerの処理フロー
概要
ここでは、Spring MVCでの最重要なControllerの動作について見ていきます。
具体的・実践的なサンプルは次の記事で触れますので、ここでは処理の概要を把握することが目的です。
まずControllerの骨組みだけで作ったソースコードを提示して、それから動作を1つ1つ見ていきます。
バインドも重要な内容ですので、前回の記事をまずお読みいただき、ご理解の上、読んでいただけると助かります。
【この記事で把握したいこと】
この記事で書きたかったのは、以下のことです。
・InitBinder(バインダーの初期化)が呼ばれるタイミング
・標準的なControllerでの処理とその順番
【先だって覚えておいてほしいこと】
Modelオブジェクト: Springが用意するMapオブジェクトで、Viewに渡すオブジェクトを設定します。
@Xxxx : アノテーションを表します。指定したメソッドをSpringが呼び出します。
Controllerクラスの骨組み
【Controllerクラスのサンプル】
//変更したユーザ情報の入力を受け取って、DBの値を変更する画面のコントローラです。
@Controller
public class SampleController {
@Autowired
private AccountService accountService;
@InitBinder("user")
public void initBinder(WebDataBinder binder) {
binder.setAllowedFields("id", "name");
}
//(1)
@ModelAttribute("user")
public User newRequest(
@RequestParam(required=false, value="id") String id,
) {
if(id== null) return null;
return this.accountService.get(id);
}
//(2)
@RequestMapping(value="/accountEdit", method=RequestMethod.POST)
public String form(@Valid @ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()){
return "account-edit-input";
}
this.accountService.updateUser(user);
return "account-edit-complete";
}
}
【Userクラス】
public class User {
private String id;
private String name;
private Integer feeId;
//getter/setterは省略します。
}
後で少し詳しくSpringMVCにおけるController処理を見ていきます。
細かな内容はともかく、以下のことだけ覚えておいていただければと思います。
【通常のControllerの処理の流れ】
(1).@ModelAttributeの呼び出し : 事前準備として、DBからPOJOを取得して、Model に登録します。
(2).@RequestMappingの呼び出し: Model からPOJOを取り出して、画面処理をします。
【Controller処理のイメージ図】
※@InitBinder は、引数にモデルが存在するときに、モデルにバインドするたびに呼ばれます。
つまり、引数のモデルの数だけ呼ばれます。
以下で、上記の(1)、(2)をそれぞれもう少し細かく説明していきます。
Controllerの処理の流れ
(1)@ModelAttributeメソッドの処理の流れ
【再掲:実際のコード】
@ModelAttribute("user") //②モデルの前処理
public User newRequest(
@RequestParam(required=false, value="id") String id,
) {
if(id== null) return null;
return this.accountService.get(id);
}
【図の説明】
①Springでの処理
通常、initBinderメソッドを呼び出して初期化し、リクエストパラメタ("id")を、@ModelAttributeのメソッドの引数(id)にバインドします。
このサンプルでは、@InitBinder("id")が付けられたメソッドがないので、
initBinderのメソッドは何も呼ばれず、デフォルトのBinderでバインドします。
それにより、型変換された値が引数に渡されます。
②モデルの前処理
リクエストパラメタのうちUserオブジェクトのキーになるidを受け取り、DBから指定のidのオブジェクトを取得します。
③Springでの処理
@ModelAttributeのメソッドの返り値をModelオブジェクトにaddします。
【補足】
ModelAttributeメソッドは、リクエストのたびに、RequestMappingメソッドの前に呼ばれます。
ModelAttributeメソッドは無くても動作する任意のメソッドです。
しかし使用した方がController処理がすっきりし、DB処理も簡単に記述できるようになります。
以下の(2)で見ることになりますが、たいていの場合、DBの更新をするときは、DBから取得したモデル(POJO)にこの画面で更新したいフィールドのみを
上書きし、Daoに渡すことになるからです。
(ModelAttributeメソッドでDBからPOJOを取得し、RequestMappingメソッドを呼び出すときにSpringが自動的に画面の値で必要なPOJOフィールドを更新する)
(2)@RequestMappingメソッドの処理の流れ
【再掲:実際のコード】
@InitBinder("user")
public void initBinder(WebDataBinder binder) {
binder.setAllowedFields("id", "name");
}
@RequestMapping(value="/accountEdit", method=RequestMethod.POST) //②コントロール処理
public String form(@Valid @ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()){
return "account-edit-input";
}
this.accountService.updateUser(user);
return "account-edit-complete";
}
【図の説明】
①Springでの処理
@ModelAttributeで指定された"user"がModelから取り出されます。
もしModelに"user"がない場合、新たにUserをnewして、Modelに設定します。
今回のサンプルの場合、(1)のモデル前処理で既にModel に"user"が設定されているので、(1)のオブジェクトが取り出されます。
また、このサンプルでは、ModelAttribute("user")に対応する@InitBinder("user")があるので、
それが付けられたメソッドが呼ばれ、Binderが初期化されます。
取り出したUserオブジェクトに、Binderを介してリクエストパラメタを設定します。nameが"次郎"で上書きされ、user.feeIdは無視されます。
initBinderメソッドで、パラメタ "user.feeId" を許可していないからです(DBのfeeIdの値を変更したくないので無視するように設定しました)。
最後にUserオブジェクトは②のメソッドの引数に引き渡されます。
②コントロール処理
自作したコントロール処理をします。
引数で渡ってきたuserは、Model 内のuserとインスタンスが同じため、userを操作するとModel 内のuserの値も変更されます。
ここではreturnとして、View名をStringで返しています。(実際にはreturnで返却できるクラスはString(View名)だけでなく、他にもあります。)
③Springでの処理
Modelと、②のメソッドが返したView名をまとめて、ModelAndViewを作成して、次の処理に渡します。
まとめ
ちょっとこまかかったでしょうか?
すみません。
把握してほしかったのは、InitBinderが呼ばれるタイミング、スタンダードなControllerでの処理とその順番です。
大まかに理解できていれば嬉しいです。
結局、DBからデータを取得し、リクエストの内容でDBを更新する、という流れです。
Springの場合、この一連の流れが、きれいに1つずつメソッドになっているので、分かりやすく、処理も簡潔に書けます。
付け加えておくと、(1)のモデル前処理は必須ではありませんので、必要に応じて使用することになります。
インターネットを検索するとSpring MVCのサンプルは数多く出てきます。
しかし、たいていのサンプルでは(1)の処理について省略しています。が、実践では必須になってくると思います。
なぜなら、リクエストパラメタ全てをそのままバインドすると、セキュリティホールになってしまうからです。
Created Date: 2012/04/07