[連載]
初めてのEnhydra

第4回 デザインとロジックを分離するXMLCの正体




2.XMLCの正体

 XMLCの動作を確認する

 XMLCが、XMLやHTMLのファイルを基にJavaのクラスファイルを作るものであるということは、前回お話ししたとおりです。では、Javaのクラスファイルを生成することによってどんなメリットが得られるのでしょうか。それを考えるために、まずWebにおけるインタラクティブなページを作成するためのテクノロジについて、再度考えてみます。

 動的なWebコンテンツを生成するためのテクノロジとしては、いくつかの方法が挙げられます。その中で最も古くから存在し、広範に普及している技術といえば、やはりCGIです。CGIはCommon Gateway Interface(コモン・ゲートウェイ・インターフェイス)の略です。その名のとおり、「相互のプログラム間で橋渡しを行う際の、共通の決まりごと」を定義したものです。実際は、サーバとクライアントの間で情報をやりとりするための、共通の名前を持つ変数と取り決められた規則の集合です。CGIは、Webブラウザなどのクライアントからの要求に対して、Webサーバが同一サーバマシン上で別のプログラムを起動して実行し、その結果を再びクライアントに返すためのインターフェイスです。このときにWebサーバに呼び出されてサーバマシン上で動作するプログラムのことをCGIプログラムと呼びます。CGIプログラムを動作させるためにはWebサーバがCGIをサポートしている必要があります。CGIプログラムは標準出力に書き出しのできるものであれば、Perl、C言語、C++言語など、言語は何でも構いません。スクリプト言語で書かれたCGIプログラムのことをCGIスクリプトとも呼びます。

 CGIの仕組みは非常にシンプルです。そのため、さまざまなプラットフォーム上で実装が進められ、容易に利用することが可能になっています。CGIの処理の流れは、次のようになります。

  1. クライアントが、WebサーバにURLによる要求を送信する(このとき、クライアントは特に「サーバ上のCGIプログラムを実行する」ということは意識する必要がない)

  2. Webサーバは要求されたURLを基に、CGIプログラムの起動を行う(このとき、WebサーバはCGIプログラムに対してクライアントからの引数を引き渡す)

  3. CGIプログラムは処理を実行し、実行結果を標準出力に書き出す。処理が完了したCGIプログラムはプロセスを終了する

  4. Webサーバは、CGIプログラムによって書き戻された処理結果をクライアントに返信する

 CGIは、その仕組み上、要求のたびにCGIプログラムを起動します。そのため、同時にたくさんの要求をWebサーバが受け取ると、要求の数と同じだけのCGIプログラムを同時に起動することになるため、どうしてもサーバマシンの負荷が増加します。また、残念なことに同じ要求を受け取っても、要求のたびに毎回CGIプログラムを起動しては消滅する、ということを繰り返します。そのため、応答性能についてどうしても限界が低くなってしまいます。一般にCGIオーバーヘッドと呼ばれる問題です。このようにCGIは性能上の問題を抱えています。

 ではほかのテクノロジはどうでしょうか。ある程度の規模以上になると、昨今ではJavaを使うことが主流となっています。Javaによる動的なWebコンテンツ生成の仕組みといえばやはりServletです。ServletはWebサーバを通じてクライアントからの要求を受け取って処理を行い、処理結果を返します。つまりCGIと同等の仕組みであるといえます。ただし、ServletはCGIと異なり独自のServletコンテナが必要になります。Servletは動作自体はCGIに似ていますが、CGIオーバーヘッドの問題を解決しています。具体的には次のような仕組みによって、CGIで起こる問題を解消します。

  • ServletはServletコンテナと呼ばれるプログラムの上で動作する
  • ServletはServletコンテナに常駐する
  • 複数のリクエストに対して、Servletプログラム内部でスレッドを生成する
  • セッション管理を行う機構を備えている

 Servletはクライアントから受け取ったリクエストに対する処理をスレッド上で動作させるため、要求のたびに独立したプロセスとして起動するCGIプログラムより効率が良くなっています。またServletに割り当てられるスレッド自体も、サーバの起動直後にあらかじめ複数の空きスレッドが生成され、リクエストを受け取るとそのスレッドを割り当てます。そのため、新しいスレッドを生成する時間も節約されます。さらに、処理を終了したServletはプロセスを終了せずにそのままサーバ上に常駐してクライアントからの要求に備えて待機します。そして次にクライアントから要求されたときには再び常駐しているServletが割り当てられます。この仕組みによって負荷の大きいServletプロセスの生成回数を減らすことができます。このようにServletの採用によってCGIの性能問題は解決しました。しかし、Servletは開発生産性に大きな問題を抱えています。

 具体的には、ServletはHTMLの出力を行うためにプログラムのソースコード内にタグを直接記述する必要があり、そのためほんの少しの見た目の変更をしたい場合でもプログラムの変更を行う必要があります。また、HTMLタグを自分で地道にコーディングしなければならないため少しでも書き間違いなどがあるとうまく表示されません。Javaのソースコード中にHTMLタグを記述しなければならないためWebデザイナーが通常使うようなGUIによるデザインツールを使うことができないのです。

Servletのコーディング例。このようにHTMLタグを延々と記述する必要がある
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.*;

public class HelloServlet extends HttpServlet {
  public void service(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    res.setContentType("text/html; charset=Shift_JIS");
    PrintWriter out = res.getWriter();
    out.println("<HTML>");
    out.println(" <BODY>");
    out.println(" Hello Servlet");
    out.println(" </BODY>");
    out.println("</HTML>");
  }
}

 このデザインのコード化についての問題を解消するために開発されたテクノロジがJSPです。JSPは、正式名称をJava Server Pagesといい、マイクロソフトのActive Server Pagesを参考にして作られたHTML内に記述する埋め込み型のテクノロジです。JSPは、Servletのテクノロジを基にして構築されており、Servletの持つメリットをすべて継承し、加えてServletが持つプログラミング上の欠点を解消するための設計がなされています。さらにHTMLの中に独自のタグを埋め込み、そのタグの中にプログラミングをしていく形式になっています。プログラミング言語はJavaをそのまま使用します。

 具体的には、<% Javaコード %>という形式で記述していきます。これをJava Scriptlet(スクリプトレット)と呼びます。作成したHTMLファイルは拡張子を.htmlではなく.jspとして保存します。この方法によって、Servletのソースコードの中にprintメソッドを使用して出力するHTMLファイルのタグを延々と記述する必要がなくなります。JSPのページ自体は、普通のHTMLファイルと同様に単なるテキストファイルにすぎません。

 そのため、デザインを行う際には一般的なWebデザインツールを利用することが可能です。このため出力するHTMLの作成にもデザインツールを利用することができます。これによって大幅に生産性が向上します。JSPは実行時に一般的なHTML埋め込み型のスクリプト言語とは異なる動作をします。まず、初めてJSPページに対して要求があった場合、コンテナはJSPファイルを読み込んでServletに変換します。より具体的には、JSPファイルの記述を基にしてServlet用のソースコードを出力し、このソースコードを自動的にバイトコードにコンパイルします。こうやって生成されたServletプログラムを読み込んで起動、あとは通常のServletと同様にマルチスレッドでクライアントからのリクエストに対応し、プログラムがコンテナに常駐することで応答速度の向上を実現します。つまり、JSPは、コーディングの問題をHTMLへのタグ埋め込み型記述によって解決し、CGIの性能問題をServletに変換することによって解決するのです。

JSPのコーディング例。HTMLファイルの中にソースコードを記述する形式
<HTML>
  <BODY>
    <%
      String st = "Hello JSP";
      out.println(st);
    %>
  </BODY>
</HTML>

 JSPの登場によって、サーバサイドにおける動的なWebコンテンツ生成に関してはすべてがうまくいくように見えました。しかし、JSPも万能ではありません。JSPにも問題があります。最大の問題は、デザインとロジックの混合です。JSPは野放図にコーディングをしていくと、どんどんHTMLファイル内にJavaのコードが混入してしまいメンテナンス性が低下します。それを解決するために、JavaBeansコンポーネントを組み込むという仕組みが用意されていますが、それでもJavaのコードをHTMLファイル内から一掃することは不可能です。これはデザインを行う人間がJavaのコードに対する理解を必要とする、あるいは、Javaのプログラミングをする人間がWebデザインに対する理解を必要とすることを意味します。何よりもHTMLタグとJavaのコードを1つのファイルに記述するということは、デザインの専門家とプログラムの専門家がそろっていたとしても、同一のページに対しての作業を並行して行うことは不可能であるということになります。ここでJSPには、デザイナーとプログラマーの分業ができない、という問題が生じることになります。

 ここまでの問題を総括してみると、応答性能の問題を解消し、ソースコード内にHTMLタグを地道に記述するという生産性の問題を解消し、デザイナーとプログラマーの並行作業ができないという問題を解消する、そういうテクノロジが必要であるということが見て取れます。そして、そのテクノロジこそがXMLCなのです。

HTMLファイルのコーディング例。プログラミングに関するものは一切含まれない。ファイル名はWelcome.htmlで、XMLCで変換した後はWelcomeHTML.classファイルとなる
<HTML>
  <BODY>
    <SPAN id=msg>何かを表示</SPAN>
  </BODY>
</HTML>

Javaソースコードの例。HTMLファイルのid属性を取得して値を書き換える処理を行っている。尚、ソースコードはEnhydraのWeb Applicationウィザードを使用
/*
* msgTest
*
*/

package msgtest.presentation;

// Servlet imports
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// Standard imports
import java.io.IOException;
import java.util.Date;
import java.text.DateFormat;

public class WelcomeServlet extends HttpServlet {

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    ServletOutputStream out;
    WelcomeHTML welcome;
    byte[] buffer;

    welcome = new WelcomeHTML();
    welcome.setTextMsg("Hello XMLC"); // この行が値の操作を行っている。
    buffer = welcome.toDocument().getBytes();
    response.setContentType( "text/html" );
    response.setContentLength( buffer.length );
    out = response.getOutputStream();
    out.write(buffer);
    out.flush();
    response.flushBuffer();
  }

}

 XMLCのメリットには、次のようなものが挙げられます。

  • 基本的なHTMLのページ構成を作成すれば、その後はデザイナーとプログラマーとで並行して作業を行うことができる

  • ページレイアウトを変更してもプログラミングロジックには影響せず、またその逆の場合も影響しないため、アプリケーションのメンテナンスが容易になる。さらに、XMLCの自動再コンパイル機能により、アプリケーションを停止することなくアプリケーションのプレゼンテーション (ルック&フィール)を変更できる

  • ドキュメントは実行時ではなくコンパイル時に解析されるため、アプリケーションの実行時パフォーマンスが向上する

  • HTMLページとJavaソースコードの不一致を事前に検出することにより、アプリケーションの完全性の確保に役立つ。例えば、デザイナーがJavaソースコードで使用されているタグを削除した場合、実行時に問題が発生するのではなく、Javacによるバイトコードへのコンパイル時に問題が検出される

 このように、XMLCはServlet、JSPの持つメリットを継承したまま、さらにJSPの持つ問題まで解消していることが理解できます。

 このメリットを実現しているキーポイントとなるのが、DOMです。DOM(ドキュメント・オブジェクト・モデル)はHTML文書のタグ構造のそれぞれのノードをオブジェクトとして定義します。これによってHTMLをオブジェクトのかたまりとして自由に操作することが可能になります。このDOMの仕組みを使って、XMLCはHTMLをJavaのソースコードへと変換します。JSPがJava側からHTML側への歩み寄りであるとすれば、XMLCはHTMLをJava側に引き寄せたといえるでしょう。XMLC自体はHTML DOM構造を出力したり操作するためのメソッドを提供するクラスファイルを生成するにすぎません。このDOM構造を操作する側のプログラミングロジックは、別のクラスファイルとして構築されることになります。この2つのファイルへの分離と相互をつなぐためのアクセスメソッドの定義こそが、XMLCの最大の特徴なのです。

 上記のコーディング例をもう少し掘り下げてみることで、XMLCの動作を確認してみましょう。上記のWelcome.htmlファイルをXMLCでコンパイルすると、WelcomeHTML.classファイルが生成されます。クラスファイルを生成するには一度Javaソースコードを生成する必要があります。ちょうどJSPが最初の実行時にワークディレクトリにタグ出力を行うServletのソースコードを生成して、それをバイトコードに変換することでServletコンテナ内に常駐するのと似ています。今回は、DOM構造を出力するクラスファイルの大本を見てみましょう。XMLCのオプションに-keepを追加してコンパイルを行います。すると、WelcomeHTML.javaファイルが残されています。次のコードがその内容です。

WelcomeHTML.javaのコード。元のHTMLファイル内に記述したid属性msgに対応するsetTextMsg()メソッドが生成されていることが確認できる
/*
************************************
* XMLC GENERATED CODE, DO NOT EDIT *
************************************
*/
package msgtest.presentation;

import org.w3c.dom.*;
import org.enhydra.xml.xmlc.XMLCError;
import org.enhydra.xml.xmlc.XMLCUtil;
import org.enhydra.xml.xmlc.dom.XMLCDomFactory;

/**
* XMLC Document class, generated from
* C:/usr/prj/msgTest/src/msgtest/presentation/Welcome.html
*/
public class WelcomeHTML extends org.enhydra.xml.xmlc.html.HTMLObjectImpl implements org.enhydra.xml.xmlc.XMLObject,org.enhydra.xml.xmlc.html.HTMLObject
{
  private int $elementId_msg = 5;

  private org.enhydra.xml.lazydom.html.LazyHTMLElement $element_Msg;

/**
* Field that is used to identify this as the XMLC generated class
* in an inheritance chain. Contains a reference to the class object.
*/
  public static final Class XMLC_GENERATED_CLASS = WelcomeHTML.class;

/**
* Field containing CLASSPATH relative name of the source file
* that this class can be regenerated from.
*/
  public static final String XMLC_SOURCE_FILE =
    "msgtest/presentation/Welcome.html";

/**
* XMLC DOM factory associated with this class.
*/
  private static final org.enhydra.xml.xmlc.dom.XMLCDomFactory fDOMFactory
  = org.enhydra.xml.xmlc.dom.XMLCDomFactoryCache.getFactory
(org.enhydra.xml.xmlc.dom.lazydom.LazyHTMLDomFactory.class);;

/**
* Options used to preformat the document when compiled
*/
    private static final org.enhydra.xml.io.OutputOptions fPreFormatOutputOptions;

/**
* Template document shared by all instances.
*/
  private static final org.enhydra.xml.lazydom.TemplateDOM fTemplateDocument;

/**
* Lazy DOM document
*/
  private org.enhydra.xml.lazydom.LazyDocument lazyDocument;

/*
* Class initializer.
*/
  static {
  org.enhydra.xml.lazydom.html.LazyHTMLDocument doc =
  (org.enhydra.xml.lazydom.html.LazyHTMLDocument)
  fDOMFactory.createDocument(null, "HTML", null);


  buildTemplateSubDocument(doc, doc);

  fTemplateDocument = new org.enhydra.xml.lazydom.TemplateDOM(doc);

  fPreFormatOutputOptions = new org.enhydra.xml.io.OutputOptions();

  fPreFormatOutputOptions.setFormat(org.enhydra.xml.io.OutputOptions.FO
RMAT_AUTO);

  fPreFormatOutputOptions.setXmlEncoding("Shift_JIS");

  fPreFormatOutputOptions.setPrettyPrinting(false);

  fPreFormatOutputOptions.setIndentSize(4);

  fPreFormatOutputOptions.setPreserveSpace(true);

  fPreFormatOutputOptions.setOmitXMLHeader(false);

  fPreFormatOutputOptions.setOmitDocType(false);

  fPreFormatOutputOptions.setOmitEncoding(false);

  fPreFormatOutputOptions.setDropHtmlSpanIds(true);

  fPreFormatOutputOptions.setOmitAttributeCharEntityRefs(true);

  fPreFormatOutputOptions.setPublicId(null);

  fPreFormatOutputOptions.setSystemId(null);

  fPreFormatOutputOptions.setMIMEType(null);

  fPreFormatOutputOptions.markReadOnly();

}

/**
* Default constructor.
*/
public WelcomeHTML() {
  buildDocument();

}

/**
* Constructor with optional building of the DOM.
*/
public WelcomeHTML(boolean buildDOM) {
  if (buildDOM) {

    buildDocument();

  }

}

/**
* Copy constructor.
*/
public WelcomeHTML(WelcomeHTML src) {
  setDocument((Document)src.getDocument().cloneNode(true), src.getMIMET
ype(), src.getEncoding());

  syncAccessMethods();

}

/**
* Create document as a DOM and initialize accessor method fields.
*/
public void buildDocument() {
  lazyDocument = (org.enhydra.xml.lazydom.html.LazyHTMLDocument)(
  (org.enhydra.xml.xmlc.dom.lazydom.LazyDomFactory)
  fDOMFactory).createDocument(fTemplateDocument);

  lazyDocument.setPreFormatOutputOptions(fPreFormatOutputOptions);

  setDocument(lazyDocument, "text/html", "Shift_JIS");

}

/**
* Create a subtree of the document.
*/
private static void buildTemplateSubDocument(org.enhydra.xml.lazydom.Lazy
Document document,org.w3c.dom.Node parentNode)
{
  Node $node0, $node1, $node2, $node3, $node4;

  Element $elem0, $elem1, $elem2, $elem3;

  Attr $attr0, $attr1, $attr2, $attr3;



  $elem1 = document.getDocumentElement();

  ((org.enhydra.xml.lazydom.LazyElement)$elem1).makeTemplateNode(1);

  ((org.enhydra.xml.lazydom.LazyElement)$elem1).setPreFormattedText("<H
TML>");



  $elem2 = document.createTemplateElement("HEAD", 2, "<HEAD>");

  $elem1.appendChild($elem2);



  $elem3 = document.createTemplateElement("TITLE", 3, "<TITLE>");

  $elem2.appendChild($elem3);



  $elem2 = document.createTemplateElement("BODY", 4, "<BODY>");

  $elem1.appendChild($elem2);



  $elem3 = document.createTemplateElement("SPAN", 5, "<SPAN>");

  $elem2.appendChild($elem3);

  $attr3 = document.createTemplateAttribute("id", 6);

  $elem3.setAttributeNode($attr3);



  $node4 = document.createTemplateTextNode("msg", 7, "msg");

  $attr3.appendChild($node4);



  $node4 = document.createTemplateTextNode(
  "\u4f55\u304b\u3092\u8868\u793a",8,
   "\u4f55\u304b\u3092\u8868\u793a");

  $elem3.appendChild($node4);

}

/**
* Clone the document.
*/
public Node cloneNode(boolean deep) {
  cloneDeepCheck(deep);

  return new WelcomeHTML(this);

}

/**
* Get the XMLC DOM factory associated with the class.
*/
protected final org.enhydra.xml.xmlc.dom.XMLCDomFactory getDomFactory() {

  return fDOMFactory;

}

/**
* Get the element with id <CODE>msg</CODE>.
* @see org.w3c.dom.html.HTMLElement
*/
public org.w3c.dom.html.HTMLElement getElementMsg() {
  if (($element_Msg == null) && ($elementId_msg >= 0)) {

    $element_Msg = (org.enhydra.xml.lazydom.html.LazyHTMLElement)lazy
Document.getNodeById($elementId_msg);

  }

  return $element_Msg;

}

/**
* Get the value of text child of element <CODE>msg</CODE>.
* @see org.w3c.dom.Text
*/
public void setTextMsg(String text) {
  if (($element_Msg == null) && ($elementId_msg >= 0)) {

    $element_Msg = (org.enhydra.xml.lazydom.html.LazyHTMLElement)lazy
Document.getNodeById($elementId_msg);

  }

  doSetText($element_Msg, text);

}

/**
* Recursize function to do set access method fields from the DOM.
* Missing ids have fields set to null.
*/
protected void syncWithDocument(Node node) {
    if (node instanceof Element) {

        String id = ((Element)node).getAttribute("id");

        if (id.length() == 0) {

    } else if (id.equals("msg")) {

        $elementId_msg = 5;

        $element_Msg = (org.enhydra.xml.lazydom.html.LazyHTMLElement)node;

    }

  }

  Node child = node.getFirstChild();

  while (child != null) {

  syncWithDocument(child);

  child = child.getNextSibling();

  }

  }

}

 いかがでしょうか。いままでブラックボックスだったXMLCの正体が明解になったように感じます。やはりソースコードが公開されるというのは良いことです。それはさておき、このソースコードを見ると、2つの重要なポイントが見つかります。1つは、DOM構造を構築するための記述があり、これが元のHTMLの文書構造を再現していること。そしてもう1つは、publicメソッドとして元のHTML文書内に記述しておいたid属性(この場合は"msg")に対するアクセスメソッドが用意されているということです。

 さて、このWelcomeHTML.classを操作する側のJavaプログラムですが、前回のサンプルとちょっとだけ、しかし重大な違いがあることにお気付きになられたでしょうか? 実は、このサンプルはEnhydraのPO(プレゼンテーション・オブジェクト)ではありません。普通のServletなのです。よく見てみましょう。クラス宣言のところでHttpServletをextends、つまり継承しています。本来POであればinterfaceであるcom.lutris.appserver.server.httpPresentation.HttpPresentationをimplements、つまり実装するということになるはずです。しかし、この例では普通のServletになっています。また、POであればrun()メソッドに処理を記述するはずですが、ここではdoGet()メソッドを使っています。正に普通のServletです。どういうことなのでしょうか。

 これは、XMLCが生成したWelcomeHTML.classがあくまでも普通のクラスにすぎないということです。ですから、このクラスを使用できるのはEnhydraに限定されないということなのです。もちろんEnhydraのPOフレームワークは非常に多くのメリットを提供してくれます。しかし、XMLCのメリットはEnhydraに限定されることなく多くのJavaプログラムで利用可能です。なお、EnhydraのPOとして、WelcomeHTML.classを利用する場合は、以下のようになります。この場合はEnhydraのエンジンが自動的にPOフレームワークに従ってrun()メソッドを実行します。

POの場合のコーディング例。HttpPresentationインターフェイスの実装を宣言している
// Enhydra SuperServlet imports
import com.lutris.appserver.server.httpPresentation.HttpPresentation;
import com.lutris.appserver.server.httpPresentation.HttpPresentationComms;
import com.lutris.appserver.server.httpPresentation.HttpPresentationException
;


// Standard imports
import java.io.IOException;
import java.util.Date;
import java.text.DateFormat;

public class WelcomePresentation implements HttpPresentation {


  public void run(HttpPresentationComms comms)
    throws HttpPresentationException, IOException {

    WelcomeHTML welcome =(WelcomeHTML)comms.xmlcFactory.create(WelcomeHTML.class);
    welcome.setTextMsg("Hello XMLC and Enhydra");
    comms.response.writeDOM(welcome);
  }

}

 XMLCは、HTMLやXMLだけでなく、iモード用のC-HTMLや今後のHTMLの標準となっていくXHTML、さらにはVoiceXMLやWMLをサポートしているため、さまざまなプレゼンテーション層に応じたDOM構造クラスを生成できます。そしてホットスポットとしてのid属性の名称さえそろえておけば、ロジック部の使いまわしも可能になります。

 しかし、何よりもXMLCはHTMLドリブンの開発モデルからの脱却を促進します。現在、SOAPを中心としてXML文書によるメッセージの交換による新しいスタイルのWebアプリケーション、すなわちWebサービスが注目を集めています。今後は動的なXMLコンテンツの生成が重要なテーマになってくるのは間違いありません。このようなときに、自前で地道にコードを記述していくのは骨の折れる作業です。XMLCはこの手間を省略します。XMLCは、XMLやHTML文書をJavaソースコード内で自由に操作可能にするために非常に優れた手段を提供してくれるのです。

2/3
3.XMLCアプリケーションを作成する

初めてのEnhydra(第4回)
  1.Enhydraの最新動向
2.XMLCの正体
XMLCの動作を確認する
  3.XMLCアプリケーションを作成する
デフォルト値を持つフォームの生成



連載記事一覧

 



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間