Java版のGoogle App Engine でテンプレートエンジン
Java版のApp Engineをいろいろいじって見ている.Eclipse Plug inの助けがあるので,当初思ったよりはずっと使いやすい.やっぱりJavaのような言語はIDEがあってナンボ,なのか.
Java版のサンプルを実行してみて一番嫌だったのは,JSP (JavaServer Pages)を使う部分.JSPは,HTMLのファイルのなかに,Javaコードでロジックを埋め込むもので,動的にJava Servletにコンパイルされて実行される.ある意味,非常に強力なテンプレートエンジンであると考えることもできなくもない.が,ロジックとプレゼンテーションの分離という観点からすると,あきらかになにか間違っている機構だと思う.HTMLファイルの中にimport文がずらずらならぶというだけでもなんかおかしいだろう.10年以上前(1998年)に作られた機構なので仕方が無いのかもしれないが.
テンプレートエンジンを使ってみる
JSPを使うのはいやなので,テンプレートエンジンを使えるか試してみた.結論から言うとまったく問題なく使えることがわかった.テンプレートエンジンとしてはFreeMarkerを使ってみた.たぶんVelocityでもなんでも同じように使えると思う.
- jarファイルを用意する.
- WEB-INF/lib に置く.
- プロジェクトのプロパティ,Java Build Path, Libraryでjarファイルを指定.
FreeMarkerの場合は,FreeMarkerをビルドしたときにできるfreemarker.jarだけでよい.WEB-INF/lib下に置いておくだけで自動的にcloud上のappengineにも配備される.
サンプル
サンプルとして,ここにあるJSPをServlet+テンプレートエンジンで置き換えてみる.
まずはServlet. ポイントは
- テンプレートをサーブレットコンテナ内部のリソースとして取得するために,TemplateLoaderを指定すること
cfg.setTemplateLoader( new WebappTemplateLoader(this.getServletContext()));
- テンプレートをServletコンテナトップ(/war)からの相対で指定すること.ここでは war/WEB-INF/greetings.tmplに置いているので,
Template temp = cfg.getTemplate("WEB-INF/greetings.tmpl");
としている.
package greetings; import freemarker.cache.WebappTemplateLoader; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; import freemarker.template.TemplateException; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.jdo.PersistenceManager; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; public class MainPage extends HttpServlet { private static final Logger log = Logger.getLogger(SignPage.class.getName()); public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { Map<String, Object> root = new HashMap<String, Object>(); UserService userService = UserServiceFactory.getUserService(); User user = userService.getCurrentUser(); PersistenceManager pm = PMF.get().getPersistenceManager(); String query = "select from " + Greeting.class.getName(); List<Greeting> greetings = (List<Greeting>) pm.newQuery(query).execute(); root.put("user", user); root.put("signOutUrl", userService.createLogoutURL(req.getRequestURI())); root.put("signInUrl", userService.createLoginURL(req.getRequestURI())); root.put("greetings", greetings); Configuration cfg = new Configuration(); cfg.setTemplateLoader( new WebappTemplateLoader(this.getServletContext())); cfg.setObjectWrapper(new DefaultObjectWrapper()); Template temp = cfg.getTemplate("WEB-INF/greetings.tmpl"); resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); try { temp.process(root, resp.getWriter()); } catch (TemplateException e) { resp.getWriter().println(e.getMessage()); } } }
テンプレート本体はこんな感じ.
<html> <body> <#if user?? > <p> Hello, ${user.nickname}! (You can <a href="${signOutUrl}">sign out</a>.)</p> <#else> <p> Hello! <a href="${signInUrl}"> Sing in</a> to include your name with greetings you post.</p> </#if> <#if greetings?size == 0 > <p>The guestbook has no messages.</p> <#else> <#list greetings as g > <#if g.author?? > <p><b>${g.author.nickname}</b> wrote:</p> <#else> <p>An anonymous person wrote:</p> </#if> <blockquote>${g.content}</blockquote> </#list> </#if> <form action="/sign" method="post"> <div><textarea name="content" rows="3" cols="60"></textarea></div> <div><input type="submit" value="Post Greeting" /></div> </form> </body> </html>
JSPよりは実行速度はだいぶ遅いだろうが,こちらのほうが,遥かにロジックが分かりやすいと思うのだけど,どうだろうか.