JSFのページレンダリングの仕組み

JSFのページレンダリングの仕組みを自分なりに噛み砕いてまとめたものがPCの中に眠っていたみたいなので、今回はそれについて少々解説したいと思います。日本ではまだStruts1が使われることが多いとネットとかでも見るのでどれだけ有効かはわかりませんが・・・。

JSFはブラウザで最初に特定のページアクセスすると、対象のページをJSFが読み込み、そのページ内にあるJSFのタグ(h:inputText等)のタグハンドラが実行され、その後コンポーネントツリーが構築されます。

例えば次のようなページがあったとして、

<h:form>
  <h:inputText />
<h:form>

このページを読み込むと以下のようなコンポーネントツリーを構築します。

UIForm

UIInput

コンポーネントツリーはJSF内におけるページを表すデータ構造とでも言うべきでしょうか。

コンポーネントツリーを構築するとJSFはHTMLの描画を行うわけですが、最初にJSFのタグ以外のテキストデータが描画され、その後各タグをHTMLにエンコードしてブラウザに出力します。

その際に各タグはJSFに対して描画を行うのに必要な情報(値)を問い合わせます。つまりEL式を定義している場合、そのBeanから値の取得が行われます。

HTMLをブラウザに表示する際にエンコードという表現を利用しましたが、逆にブラウザに表示されたページの各タグにデータを入力して送信(POST)を行うと今度はデコード処理が行われます。

デコード処理は、コンポーネントツリーを構成する各コンポーネントが送られてきたリクエストの内容を参照し、例えばinputTextタグであればvalue属性にEL式で定義しているBeanのプロパティを受け取った値で更新します。振舞いはコンポーネント毎に異なります。

JSFを利用する上では、このレンダリングの基本的な仕組みとJSF内部のライフサイクルに関する理解が重要かと思います。

それでは簡単なJSFを使ったサンプルプログラムを示します。

まずweb.xmlを以下のように設定します。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
</web-app>

次に以下のようなManagedBeanを定義します。

import java.io.Serializable;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@SessionScoped
@ManagedBean(name = "hello")
public class HelloAction implements Serializable {

    private String name;
    
    private String message;
    
    public String sayHello() {
        message = "Hello, " + name;
        return "hello";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

次にwelcome.xhtmlとhello.xhtmlをコンテキストルート直下にpagesというフォルダを作成しその配下に定義します。

welcome.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

    <h:head>
        <title>Welcome.</title>
    </h:head>
	<h:body>
	    Input Your Name.<br />
	    <h:form>
	        <h:panelGrid columns="2">
	            <h:outputLabel value="Your Name" />
	            <h:inputText id="name" value="#{hello.name}" />
	        </h:panelGrid>
	        <h:commandButton value="Say Hello" action="#{hello.sayHello}"/>
	    </h:form>
	</h:body>
</html>


hello.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

	<h:outputLabel value="#{hello.message}" />
</html>

最後にfaces-config.xmlでページの遷移を定義します。

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xi="http://www.w3.org/2001/XInclude"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 <navigation-rule>
  <from-view-id>/pages/welcome.xhtml</from-view-id>
  <navigation-case>
   <from-outcome>hello</from-outcome>
   <to-view-id>/pages/hello.xhtml</to-view-id>
  </navigation-case>
 </navigation-rule>
</faces-config>

任意のアプリケーションサーバにwarファイルをデプロイしwelcomeページにアクセスし見てください。ソースを表示するとJSFのタグがエンコードされHTMLで表示されていることが分かると思います。

inputTextに適当な名前を入れてボタンを押下すると、リクエストが送信されデコード処理でHelloActionのnameプロパティにが入力値で更新され、sayHelloメソッドが実行されます。その後faces-config.xmlの定義に従ってhelloページに遷移するためにhello.xhtmlページが読み込まれ、コンポーネントツリーが構築され、HelloActionのmessageプロパティの値が出力されると思います。

今回はCDIとの連携はお行っていません。個人的にはJSFを素で利用するよりCDIと連携させたほうがカンバセーションスコープやEJBとの連携が容易になるのでいいと思います。この辺についてはまた次回以降で。