Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SpringMVCとmixer2で作る Webアプリのキホン

SpringMVCとmixer2で作る Webアプリのキホン

WebデザイナーフレンドリーなJavaテンプレートエンジン"Mixer2"と、SpringMVCフレームワークを組み合わせての開発

Yu Watanabe

January 24, 2013
Tweet

More Decks by Yu Watanabe

Other Decks in Technology

Transcript

  1. 目次 1. SpringMVC 2. テンプレートエンジン 3. Mixer2をHelloWorldで解説 4. Why mixer2

    ? 5. SpringMVCとmixer2の組み合わせの勘所 6. コントローラとビューに対するテスト 7. Webアプリの分割開発 8. まとめ 9. FAQ 9
  2. SpringMVC • JavaでWebアプリをつくりためのMVCフレー ムワーク。 • 生のサーブレット&JSPで作るより100倍作り やすい。 • 大昔のStrutsより10倍は学習しやすい •

    ライバルとしてはSeasarのSAStrutsとか。 • Spring3.Xになって以降はSAStrutsよりもさら に使いやすくなった! • 詳しくは「Spring3入門」を読みましょう。 11
  3. テンプレートエンジン 13 JSP:一番身近なテンプレートエンジン こんにちは <% if (name == null) {

    %> ゲストさん <% } else { %> <%= name %>さん <% } %> 通常のJava言語、EL式、カスタムタグで書く
  4. テンプレートエンジン 16 Mixer2:Webデザイナーと仲良く仕事するため のテンプレートエンジン こんにちは <span id=“name”>ななし</span>さん String name =

    “ヤマダ”; Span span = html.getById(“name”, Span.class); span.getContent.clear(); span.getContent.add(name); // これで <span id=“name”>ヤマダ</span>さん // になる テンプレートファイル(*.html)は純粋なXHTMLとCSS 値の埋め込みやロジックは普通のJavaで書く(*.java)
  5. 補足:タグとJava型 18 <html>…</html> ⇔ org.mixer2.jaxb.xhtml.Html <div>…</div> ⇔ org.mixer2.jaxb.xhtml.Div (ほか全120種類くらいのタグすべてを実装済み) HTMLタグとJavaオブジェクトを相互マッピング

    タグの属性はJavaオブジェクトのプロパティにマッピング。 setter/getterメソッドでアクセス <div id=“foo”>…</div> をテンプレートとしてロードすると String id = div.getId(); // これでidに”foo”が入る (html4/5のすべての属性を実装済み)
  6. 補足:複数要素はListになる 19 <html> <body> <p>Hello World</p> foo <span>bar</span> </body> </html>

    index 型 0 P 1 String 2 Span Html html = mixer2Engine .loadHtmlTemplate( “template.html”); java.util.List<Object> list = html.getBody() .getContent(); listの中身 template.html
  7. ふつうのコントローラとJSP 27 @Controller public class ItemController { @RequestMapping(value = "/item/{itemId}")

    public ModelAndView showItem(@PathVariable long itemId) { // DBから商品情報を取得 Item item = itemService.getItem(itemId); // modelAndViewにitemを詰めて返す retern new ModelAndView(“item.jsp”, “item”, item); } <%@page pageEncoding="UTF-8"%> <html> <body> <span>商品名:${item.name}</span> </body> </html> 商品情報を表示するコントローラクラス JSP
  8. コントローラの肥大化を防ぐ 28 @RequestMapping(value = "/item/{itemId}") public ModelAndView showItem(@PathVariable long itemId)

    { // DBから商品情報を取得 Item item = itemService.getItem(itemId); // テンプレートのロード String mainTemplate = "classpath:m2mockup/m2template/item.html" File file = ResourceUtils.getFile(mainTemplate); Html html = mixer2Engine.loadHtmlTemplate(file); // 商品情報のdivタグ Div itemBox = html.getBody().getById("itemBox", Div.class); // 商品名を書き込む itemBox.getById("itemName", H1.class).getContent().clear(); itemBox.getById("itemName", H1.class).getContent().add(item.getName()); // 価格、説明、その他もろもろも... このへんが肥大化 してしまう
  9. コントローラの肥大化を防ぐ 29 @RequestMapping(value = "/item/{itemId}") public ModelAndView showItem(@PathVariable long itemId)

    { // DBから商品情報を取得 Item item = itemService.getItem(itemId); // テンプレートのロード String mainTemplate = "classpath:m2mockup/m2template/item.html" File file = ResourceUtils.getFile(mainTemplate); Html html = mixer2Engine.loadHtmlTemplate(file); // 商品情報を埋め込む ItemHelper.replaceItemBox(html, item); …… ヘルパークラスに切り 出せば1行で済む
  10. コントローラの肥大化を防ぐ 30 public class ItemHelper { public static void replaceItemBox(Html

    html, Item item) { // 商品情報を入れるdivタグを取得 Div itemBox = html.getBody().getById("itemBox", Div.class); // divの中のH1やSpanの中にDBから取得した値を入れる itemBox.getById("itemName", H1.class).getContent().clear(); itemBox.getById("itemName", H1.class).getContent().add(item.getName()); itemBox.getById("itemPrice", Span.class).getContent().clear(); itemBox.getById("itemPrice", Span.class).getContent().add( item.getPrice().toString()); itemBox.getById("itemDescription", Div.class).getContent().clear(); itemBox.getById("itemDescription", Div.class).getContent().add( item.getDescription()); ヘルパーはごく単純なstaticメソッドでよい テンプレのhtml DBから取得した商品情報
  11. 相対パスの書き換え 31 <a class=“topPageAnchor” href="../m2template/index.html"> <img src="../m2static/img/fruitshop-logo.png" /> </a> 左上のロゴはトップページへ

    のリンク テンプレートファイルではこうなってるけど <a class=“topPageAnchor” href=“/[contextPath]/"> <img src="/[contextPath]/m2static/img/fruitshop-logo.png" /> </a> 実際の出力ではこうしなきゃならない
  12. 相対パスの書き換え 32 String ctx = "xxx"; // コンテキストパスを取得しておく for (A

    a : html.getDescendants("topPageAnchor", A.class)) { a.setHref(ctx + "/"); } “topPageAnchor”というclass属性を持つすべてのaタグのhref属 性を書き変える Mixer2ではすべてのタグ型が下記のメソッドを持っている • getDescendants()メソッド:該当するすべての子孫タグをList で取得 • getById()メソッド:id属性でタグを1個だけ取得 • 他にもreplace系とかremove系のメソッドもあります。
  13. 相対パスの書き換え 33 // <img src="" /> for (Img img :

    tagObj.getDescendants(Img.class)) { if (img.isSetSrc()) { String src = img.getSrc(); img.setSrc(convertPath(src)); } } Imgタグのsrc属性、styleタグのhref属性なども同様 convertPathメソッドは、 ../m2static/ のような文字列を /[contextPath]/m2static/ に置換している
  14. Java/Webアプリでの静的ファイルの配置 35 http://localhost:8080/[contextPath]/foo/bar.png src └─main ├─java ├─resources └─webapp └─foo └─bar.png

    ※maven標準ディレクトリ構造です 普通ならdocroot配下に置く。 さもないとブラウザからアク セス不可能
  15. Java/Webアプリでの静的ファイルの配置 36 src └─main ├─java ├─resources │ │ applicationContext.xml │

    │ │ └─m2mockup │ ├─m2static │ │ └─img │ │ logo.png │ │ │ └─m2template │ index.html │ item.html │ └─webapp テンプレートhtmlと画像 やCSSをまとめて resources配下に置く。 クラスパス上に置くほう が、Javaコードから扱い やすいから!
  16. Java/Webアプリでの静的ファイルの配置 38 <mvc:resources mapping="/m2static/**" location="classpath:/m2mockup/m2static/" cache-period="60" /> src/main/resources/mvc-dispatcher-servlet.xml の抜粋 1.

    http://.../contextPath/m2static/** というURLへの アクセスに対して 2. クラスパスから /m2mockup/m2static/** というリ ソースを探してそれを返す 3. そのとき Cache-Control: max-age=60 のような httpレスポンスヘッダつきで返す
  17. ただし、注意しないと、、、! 39 src └─main ├─java ├─resources │ └─m2mockup │ ├─img

    │ │ logo.png │ └─item.html └─webapp 1. こういうディレクトリ構造で <mvc:resources mapping= "/m2mockup/**" location= "classpath:/m2mockup/"/> 2. こういう設定をしてしまうと 3. 画像やCSSだけでなく、テンプレートhtmlにもそのままアクセス できてしまう!(もちろんまずい) http://…/[ContextPath]/m2mockup/img/logo.png http://…/[ContextPath]/m2mockup/item.html
  18. だから、これがオススメ構造 40 src └─main ├─java ├─resources │ │ applicationContext.xml │

    │ │ └─m2mockup │ ├─m2static │ │ └─img │ │ logo.png │ │ │ └─m2template │ index.html │ item.html │ └─webapp m2mockup配下にモック アップhtmlを作る 画像やCSSはm2static配下に 置いて、 <mvc:resources /> の設定での出力対象にする htmlテンプレートは m2template配下に
  19. これでデザイナとプログラマが仲良く仕事できる! 41 プログラマとデザイナの取り決め事項 1. htmlモックアップは src/main/resources/m2mockup の下に作 ろうぜ。 2. ただし*.htmlはm2template,それ以外は

    m2staticの配下でたのむ。 3. 商品情報のdivタグはid=“itemBox”にしよう。 4. 商品名はspanタグでid=“itemName” 5. …..その他の情報も同様にclass属性やid属性を決 めておけばよい。
  20. ざっくりした流れ 1. JunitコードのランナーとしてSpringJUnit4ClassRunner を 使えば、DIコンテナが勝手にいい感じで起動してくれる。 2. HttpServletRequest, HttpServletResponseのモックをイン スタンス化する 3.

    モックのrequestにテスト対象のURIやパラメータをセット 4. そのrequestオブジェクトをリクエストハンドラに渡すと疑似 リクエストが発生し、コントローラクラスに渡される。 5. コントローラの該当メソッドが戻り値として返す ModelAndViewオブジェクトにhtmlStringが入っている。 6. このhtmlStringの中をMixer2Engineで再度Htmlオブジェク ト化する 7. Htmlオブジェクトの中をAssertすればよい。 44
  21. まとめ • SpringMVCはシンプルで使いやすいフレームワーク • mixer2とSpringMVCは良いコンビ • ディレクトリ構造を考えたうえでSpringMVCの静的リ ソース出力機能と組み合わせれば、htmlモックアップ をjspに書き変える作業は不要 •

    jspでは難しい、ビューに対するテストの自動化も可 能 • htmlテンプレートファイルと静的ファイル(ex.画像)と Javaクラスをjarパッケージ化できるので、Webアプリ の分割開発すら可能。 54
  22. FAQ • Q. モックアップHTMLはクラスパス上に置かなきゃダメですか? – A. Mixer2EngineのloadHtmlTemplateメソッドはjava.io.File, String, StringBufferのいずれかでテンプレートhtmlを読めます。したがってOSのファイ ルシステム上でもDB上でもどこでもOKです。

    – ※後日談:ver1.1.14 以降、InputStreamからも読めるようになりました。 • Q. WEB-INF/view/mixer2view.jsp を通じて結局jspを使っているのはダ サくないですか? – A. 実はその通りです。本来は、コントローラのメソッドの戻り値としてHtmlオブ ジェクトを返すだけで済むようなViewResolverを実装するべきです。誰か作っ て!(Pull Request熱烈歓迎!) • Q. mixer2を使う場合はJSPは完全に排除しなきゃだめですか?既存の taglibも使いたいのですが? – A. 可能です。たとえば、 *.htmlで用意したテンプレートを読み込んで、その一部 のタグだけを部分マーシャルし、jsp上に埋め込むことも可能です。 55