JSON-libを使ってXMLHttpRequestとServlet間通信
JSON-libのV1.0が出たみたいですね。
http://json-lib.sourceforge.net/
早速試してみました。AjaxとServletでJSON通信でCRUD処理を行う・・・という設定。HTMLのFORMは使わずに、全てJSONで送信してサーバ側でBeanに変換し、戻り値は逆にBeanからJSONに変換して返す・・・という前提で考えてみたいと思います。
まずは登録に使うEntityクラス。Doltengのサンプルテーブルからそのまま利用しました。
@Entity public class Dept { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "deptGenerator") @SequenceGenerator(name = "deptGenerator", sequenceName = "SEQ_DEPT", allocationSize = 1) private Integer id; private Integer dept_no; private String dept_name; private String loc; @Version private Integer version_no; }
setter・getterは省略。
続いて、このEntityのCRUD処理を行うServiceクラス
public class DeptServiceImpl implements DeptService { private EntityManager entityManager; public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } public void delete(Dept dept) { dept = entityManager.merge(dept); entityManager.remove(dept); } public Dept find(Integer id) { return entityManager.find(Dept.class, id); } public Dept insert(Dept dept) { entityManager.persist(dept); return dept; } public Dept update(Dept dept) { return entityManager.merge(dept); } }
そして、Servletクラス
public class DeptPage extends HttpServlet { /** * */ private static final long serialVersionUID = 6021914337961203461L; private DeptService deptService; public void setDeptService(DeptService deptService) { this.deptService = deptService; } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Integer id = Integer.parseInt(URIUtil.getResources(request.getPathInfo())[1]); JSONObject jObj = JSONObject.fromString(getBody(request.getReader())); Dept dept = (Dept) JSONObject.toBean(jObj, Dept.class); dept.setId(id); deptService.delete(dept); } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Dept dept = deptService.find(Integer.parseInt(URIUtil.getResources(request.getPathInfo())[1])); response.setContentType("application/javascript; charset=UTF-8"); JSONObject.fromBean(dept).write(response.getWriter()); } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { JSONObject jObj = JSONObject.fromString(getBody(request.getReader())); Dept dept = (Dept) JSONObject.toBean(jObj, Dept.class); deptService.insert(dept); response.setContentType("application/javascript; charset=UTF-8"); JSONObject.fromBean(dept).write(response.getWriter()); } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Integer id = Integer.parseInt(URIUtil.getResources(request.getPathInfo())[1]); JSONObject jObj = JSONObject.fromString(getBody(request.getReader())); Dept dept = (Dept) JSONObject.toBean(jObj, Dept.class); dept.setId(id); dept = deptService.update(dept); response.setContentType("application/javascript; charset=UTF-8"); JSONObject.fromBean(dept).write(response.getWriter()); } protected String getBody(BufferedReader reader) throws IOException { try { StringBuilder sb = new StringBuilder(); String str; while ((str = reader.readLine()) != null) { sb.append(str); } return sb.toString(); } finally { reader.close(); } } }
JSON-libを使えば、JSON-Bean変換は簡単に実行できるんですね。
URIUtilクラスは自作のユーティリティで、今は単純にパスを「/」で分割してるだけです。もっと綺麗にURLからパラメータを取得する仕組みがあればいいんですが・・・
さて、このServletにはServiceクラスがDIされてますが、これはServletをPageクラスとしてSmart Deployさせてるからです。その処理を行っているServletを別に作成してます。内容は以下のとおり
public class SmartServlet extends HttpServlet { /** * */ private static final long serialVersionUID = -8268071562015328437L; /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpServlet delegate = getDelegate(request, response); if (delegate != null) { delegate.service(request, response); } else { response.sendError(HttpServletResponse.SC_NOT_FOUND); } } protected HttpServlet getDelegate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String servletPath = request.getServletPath(); String pathInfo = request.getPathInfo(); if (pathInfo != null && pathInfo.length() > 1) { if (pathInfo.indexOf("/", 1) != -1) { pathInfo = pathInfo.substring(0, pathInfo.lastIndexOf("/")); } S2Container container = SingletonS2ContainerFactory.getContainer(); NamingConvention convention = NamingConvention.class.cast(container.getComponent(NamingConvention.class)); String pageName = convention.fromPathToPageName(servletPath + pathInfo); Class clazz = convention.fromComponentNameToClass(pageName); if (clazz != null) { Object delegate = container.getComponent(clazz); if (delegate instanceof HttpServlet) { HttpServlet servlet = HttpServlet.class.cast(delegate); if (servlet.getServletConfig() == null) { servlet.init(getServletConfig()); } return servlet; } } } return null; } }
NamingConventionクラスはServlet用にいじってます。ServletConfigを渡す部分がちょっと微妙ですが・・・本番はCool Deployすれば大丈夫かな??
さて、このサーブレットとやりとりするHTMLが以下のとおり
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>dept</title> <script type="text/javascript" src="/smartServlet/js/json.js"></script> <script type="text/javascript"> function createXMLHttp() { if (window.XMLHttpRequest) { return new XMLHttpRequest(); } else if (window.ActiveXObject) { try { return new ActiveXObject("MSXML2.XMLHTTP"); } catch (e) { return new ActiveXObject("Microsoft.XMLHTTP"); } } return null; } function Dept() { this.id = null; this.dept_name = null; this.dept_no = null; this.loc = null; this.version_no = null; this.copy = function(newDept) { this.id = newDept.id; this.dept_name = newDept.dept_name; this.dept_no = newDept.dept_no; this.loc = newDept.loc; this.version_no = newDept.version_no; } this.save = function() { this.id = document.getElementById("dept_id").value; this.dept_name = document.getElementById("dept_name").value; this.dept_no = document.getElementById("dept_no").value; this.loc = document.getElementById("loc").value; this.version_no = document.getElementById("version_no").value; } this.load = function() { document.getElementById("dept_id").value = this.id; document.getElementById("dept_name").value = this.dept_name; document.getElementById("dept_no").value = this.dept_no; document.getElementById("loc").value = this.loc; document.getElementById("version_no").value = this.version_no; } } function post() { var dept = new Dept(); dept.save(); dept.id = null; var req = createXMLHttp(); req.open("POST", "/smartServlet/view/dept/", true); req.onreadystatechange = function() { handler(req); } req.setRequestHeader("Content-Type", "text/javascript; charset=UTF-8"); req.send(dept.toJSONString()); } function get() { var req = createXMLHttp(); req.open("GET", "/smartServlet/view/dept/" + document.getElementById("dept_id").value, true); req.onreadystatechange = function() { handler(req); } req.send(null); } function put() { var dept = new Dept(); dept.save(); var req = createXMLHttp(); req.open("PUT", "/smartServlet/view/dept/" + document.getElementById("dept_id").value, true); req.onreadystatechange = function() { handler(req); } req.setRequestHeader("Content-Type", "text/javascript; charset=UTF-8"); req.send(dept.toJSONString()); } function del() { var dept = new Dept(); dept.save(); var req = createXMLHttp(); req.open("DELETE", "/smartServlet/view/dept/" + document.getElementById("dept_id").value, true); req.onreadystatechange = function() { if (req.readyState == 4 && req.status == 200) { document.getElementById("dept_id").value = ""; document.getElementById("dept_name").value = ""; document.getElementById("dept_no").value = ""; document.getElementById("loc").value = ""; document.getElementById("version_no").value = ""; } } req.setRequestHeader("Content-Type", "text/javascript; charset=UTF-8"); req.send(dept.toJSONString()); } function handler(req) { if (req.readyState == 4 && req.status == 200) { var json = req.responseText; var newDept = eval("(" + json + ")"); var dept2 = new Dept(); dept2.copy(newDept); dept2.load(); } } </script> </head> <form action=""> id:<input type="text" id="dept_id"/><br/> dept_name:<input type="text" id="dept_name"/><br/> dept_no:<input type="text" id="dept_no"/><br/> loc:<input type="text" id="loc"/><br/> <input type="hidden" id="version_no"> <input type="button" value="GET" onclick="get()"> <input type="button" value="POST" onclick="post()"> <input type="button" value="PUT" onclick="put()"> <input type="button" value="DELETE" onclick="del()"> </form> </html>
json.jsを使ってJavaScriptのオブジェクトをJSON変換して、HTTPのボディに流して送信してます。FORMのパラメータは使ってません。
・・・とまぁ、かなり乱雑な作りですが(汗)、JSONを使えば、JavaScriptとサーバサイドで、オブジェクト単位でやりとり出来るっぽいですね。バリデーション等も、JavaSE6とか使えば、まずはJavaScript側で実装しておいて、サーバサイドでも同じスクリプトを読み込んで共通化する・・・みたいなことも可能かも?
あと、XMLHttpRequestとやりとりする前提なら、Headerも自由に追加できるので、よりHTTPの規約に則った形でWebアプリの細かな制御が出来そうな気がします。HTMLのFORMにはもう縛られる必要はないわけだから、もっとHTTPをフルに活用する前提でWebアプリを考えてみるのも結構面白そうです。