読者です 読者をやめる 読者になる 読者になる

JSF ライフサイクルはソース見るのが手っ取り早い

Java JavaEE

f:id:Naotsugu:20170117211117g:plain

JSF のライフサイクルを理解するにはソース見てしまうのが一番早いですねー。

いまさらですが、Mojarra 2.2.13 のソースでライフサイクルを追ってみます。いまさらですが。

大枠を眺めるため、ソースは大幅に削ったり加工したりしたものなのでご注意ください。。

FacesServlet

JSF の入り口は javax.faces.webapp.FacesServlet です。 サーブレットはサーブレットコンテナにより初期化init()が呼ばれます。

@MultipartConfig
public final class FacesServlet implements Servlet {

    private FacesContextFactory facesContextFactory = null;
    private Lifecycle lifecycle = null;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

        facesContextFactory = (FacesContextFactory)
                FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);

        LifecycleFactory lifecycleFactory = (LifecycleFactory)
                FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);

        lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
    }
}

FacesContextFactoryLifecycle (com.sun.faces.lifecycle.LifecycleImpl) の初期化をしています。


初期化後にHTTPリクエストがやってくると、サーブレットの service() メソッドが呼ばれます。

@MultipartConfig
public final class FacesServlet implements Servlet {

    @Override
    public void service(ServletRequest req, ServletResponse resp) {

        FacesContext context = facesContextFactory.getFacesContext
              (servletConfig.getServletContext(), request, response, lifecycle);

        try {
            lifecycle.attachWindow(context);
            lifecycle.execute(context);
            lifecycle.render(context);
        } catch (FacesException e) {
        } finally {
            context.release();
        }
    }
}

先程初期化した FacesContextFactory から FacesContext を取得しています。 そして、そのコンテキストで lifecycle.execute()lifecycle.render() を実行します。

Lifecycle

lifecycle (com.sun.faces.lifecycle.LifecycleImpl) には以下のようなフェーズが定義されています。

public class LifecycleImpl extends Lifecycle {

    private Phase response = new RenderResponsePhase();

    private Phase[] phases = {
        null, // ANY_PHASE placeholder, not a real Phase
        new RestoreViewPhase(),
        new ApplyRequestValuesPhase(),
        new ProcessValidationsPhase(),
        new UpdateModelValuesPhase(),
        new InvokeApplicationPhase(),
        response
    };
}

そして、同じくLifecyclelifecycle.execute()lifecycle.render() は以下の実装となっています。

public class LifecycleImpl extends Lifecycle {

    @Override
    public void execute(FacesContext context) throws FacesException {
        for (int i = 1, len = phases.length -1 ; i < len; i++) {
            if (context.getRenderResponse() ||
                context.getResponseComplete()) {
                break;
            }
            phases[i].doPhase(context, this, listeners.listIterator());
        }
    }

    @Override
    public void render(FacesContext context) throws FacesException {
        if (!context.getResponseComplete()) {
            response.doPhase(context, this, listeners.listIterator());
        }
    }
}

各フェーズのインスタンスを、コンテキストを伴って順番に実行してしているだけです。 単純ですね。

これが JSF のライフサイクルの各フェーズの実行の大枠です。

では個別に各フェーズの中身を見ていきましょう。

RestoreViewPhase

最初のフェーズは RestoreViewPhase で doPhase() は以下のような実装になっています。

public class RestoreViewPhase extends Phase {

    @Override
    public void doPhase(FacesContext context,
                        Lifecycle lifecycle,
                        ListIterator<PhaseListener> listeners) {

        Util.getViewHandler(context).initView(context);
        super.doPhase(context, lifecycle, listeners);
        notifyAfter(context, lifecycle);
    }
}

親クラスの doPhase() に委譲していますね。 親の doPhase() は以下のようになっています。

public abstract class Phase {

    public void doPhase(FacesContext context,
                        Lifecycle lifecycle,
                        ListIterator<PhaseListener> listeners) {

        context.setCurrentPhaseId(getId());

        try {
            handleBeforePhase(context, listeners, event);
            execute(context);
        } catch (Throwable e) {
            queueException(context, e);
        } finally {
            try {
                handleAfterPhase(context, listeners, event);
            } catch (Throwable e) {
                queueException(context, e);
            }
            context.getExceptionHandler().handle();
        }

    }
}

リスナの処理や例外のハンドリングなどが色々とありますが、大きくは execute(context) を実行しているだけです。

特徴的なのは、例外や各種のイベント(後述)をキューイングして扱っている点でしょうか。

RestoreViewPhase の execute() は以下となります。

public class RestoreViewPhase extends Phase {

    @Override
    public void execute(FacesContext facesContext) throws FacesException {

        String viewId = // ・・

        boolean isPostBack = (facesContext.isPostback() && !isErrorPage(facesContext));
        if (isPostBack) {
            viewRoot = viewHandler.restoreView(facesContext, viewId);
            facesContext.setViewRoot(viewRoot);

        } else {
            String derivedViewId = viewHandler.deriveLogicalViewId(facesContext, viewId);
            ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, derivedViewId);
            ViewMetadata metadata = vdl.getViewMetadata(facesContext, derivedViewId);
            viewRoot = metadata.createMetadataView(facesContext);
            if (!ViewMetadata.hasMetadata(viewRoot)) {
                facesContext.renderResponse();
            }
            facesContext.setViewRoot(viewRoot);
        }
    }

流れがわかりやすいようにかなり改編してます。ポストバック時とそうでない場合(単なる初期表示時のGETリクエストなど)に分かれます。

isPostBack 時(初期表示の後の POST) の場合は viewHandler.restoreView() にて UIViewRoot を復元して facesContext に保存しています。

isPostBackでなければ、ViewDeclarationLanguage を取得し、ViewDeclarationLanguage から ViewMetadata を取得し、そこから UIViewRoot を作成して facesContext 保存しています。

続いてhasMetadata() では処理すべき UIComponent があるかどうかを見ていて、なければそのまま facesContext.renderResponse() でフラグを立てます。


ViewDeclarationLanguage は Faceletの場合は FaceletViewHandlingStrategy、JSP の場合は JspViewHandlingStrategy とビューの記述言語を Strategy により切り替える仕組みになっていますね。


UIViewRoot は Facelet のタグに応じたコンポーネントツリーになっており、例えばフォームの中にテキストボックスとコマンドボタンがある場合には以下のようなツリーがオブジェクトとして作成されます。

UIViewRoot
  └ HtmlForm
     ├ HtmlInputText
     └ HtmlCommandButton


さて、GETリクエストの場合には facesContext.renderResponse() としてコンテキストにフラグを立ててあるので、以下のようにフェースの実行はbreakし、最後のフェーズである RenderResponsePhase が実行されます。

    public void execute(FacesContext context) throws FacesException {
        for (int i = 1, len = phases.length -1 ; i < len; i++) {
            if (context.getRenderResponse() ||
                context.getResponseComplete()) {
                break;
            }
            phases[i].doPhase(context, this, listeners.listIterator());
        }
    }
}

以上のように RestoreViewPhase では、ViewDeclarationLanguage を解析し、UIViewRoot を構築または再構築して FacesContext に格納します。 UIViewRoot を構築または再構築の部分はいろいろとやっていますが ここでは省きます。

ApplyRequestValuesPhase

次のフェーズです。ApplyRequestValuesPhase。

execute() は以下のようになっています。

public class ApplyRequestValuesPhase extends Phase {

    public void execute(FacesContext facesContext) throws FacesException {
        UIComponent component = facesContext.getViewRoot();
        try {
            component.processDecodes(facesContext);
        } catch (RuntimeException re) {
            throw new FacesException(re.getMessage(), re);
        }
    }
}

前フェーズで構築したコンポーネントツリー UIComponent をコンテキストから取り出し、processDecodes() により子供のコンポーネントに対して再帰的に呼び出しを行っています。processDecodes() ではクライアントからのリクエスト値を、各コンポーネントに反映していきます。

UIComponentUIViewRoot の親クラスで以下のような継承構造になっています。

UIComponent
  └  UIComponentBase
        └ UIViewRoot

UIComponentBaseprocessDecodes() では子供のコンポーネントに対して processDecodes() を呼んでいくようになっています。

public abstract class UIComponentBase extends UIComponent {

    public void processDecodes(FacesContext context) {

        pushComponentToEL(context, null);

        Iterator kids = getFacetsAndChildren();
        while (kids.hasNext()) {
            UIComponent kid = (UIComponent) kids.next();
            kid.processDecodes(context);
        }

        try {
            decode(context);
        } catch (RuntimeException e) {
            // ・・
        } finally {
            popComponentFromEL(context);
        }
    }
}

誤解されがちな Immediate

ここでちょっと横道にそれます。

processDecodes() は例えばテキストボックスの UIInput では以下のようになっています。

public class UIInput extends UIOutput implements EditableValueHolder {

    public void processDecodes(FacesContext context) {

        if (!isRendered()) {
            return;
        }

        super.processDecodes(context);

        if (isImmediate()) {
            executeValidate(context);
        }
    }
}

isImmediate() の場合に executeValidate(context) を実行していますね。

つまりimmediate = true の場合はApplyRequestValuesPhaseでバリデーション処理が行われます。

ProcessValidationsPhase の前に immediate とマーキングされたコンポーネントのバリデーションが先に行われます。

さらに、immediate な場合には、通常は InvokeApplicationPhase で実行される Action、Action Listener の処理、 ProcessValidationsPhase で実行される Value Change Listener も ProcessValidationsPhase で実行されます(バリデーション処理が実行される過程で合わせて実行されるというのが正しい)。

これらの様子を見てみましょう。

先程見た UIComponentBaseprocessDecodes() は以下のようになっていました。

public abstract class UIComponentBase extends UIComponent {

    public void processDecodes(FacesContext context) {
        // ・・
        decode(context);
        // ・・
    }

    public void decode(FacesContext context) {
        String rendererType = getRendererType();
        Renderer renderer = this.getRenderer(context);
        renderer.decode(context, this);
    }
}

renderer.decode() がこのコンポーネント自身に対する処理で、この中で renderer.decode() に処理を委譲しています。

例えばボタンの場合(ButtonRenderer)では以下のようなっています。

public class ButtonRenderer extends HtmlBasicRenderer {

    @Override
    public void decode(FacesContext context, UIComponent component) {

        String clientId = decodeBehaviors(context, component);
        if (wasClicked(context, component, clientId) && !isReset(component)) {
            component.queueEvent(new ActionEvent(component));
        }

    }

この処理では、ActionEvent のインスタンスを生成して component.queueEvent() でコンポーネントのキューにイベントが格納されます。

キューに入れる箇所は以下のようになっていて、

public class UICommand extends UIComponentBase implements ActionSource2 {

    public void queueEvent(FacesEvent e) {
        UIComponent c = e.getComponent();
        if (e instanceof ActionEvent && c instanceof ActionSource) {
            if (((ActionSource) c).isImmediate()) {
                e.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
            } else {
                e.setPhaseId(PhaseId.INVOKE_APPLICATION);
            }
        }
        super.queueEvent(e);
    }
}

immediate の場合は APPLY_REQUEST_VALUES のフェーズIDでマーキングされます。

このキューイングされたイベントは 最終的に UIViewRoot の processDecodes() で処理されます。

public class UIViewRoot extends UIComponentBase implements UniqueIdVendor {

    @Override
    public void processDecodes(FacesContext context) {

        try {
            if (!skipPhase) {
                if (context.getPartialViewContext().isPartialRequest() &&
                    !context.getPartialViewContext().isExecuteAll()) {
                    context.getPartialViewContext().processPartial(PhaseId.APPLY_REQUEST_VALUES);
                } else {
                    super.processDecodes(context);
                }
                broadcastEvents(context, PhaseId.APPLY_REQUEST_VALUES);
            }
        } finally {
            clearFacesEvents(context);
            notifyAfter(context, PhaseId.APPLY_REQUEST_VALUES);
        }
    }
}

broadcastEvents(context, PhaseId.APPLY_REQUEST_VALUES)APPLY_REQUEST_VALUES とフェーズIDを指定してブロードキャストを依頼しており、broadcastEvents では(大幅に省略しますが)コンポーネントのbroadcast() が呼ばれます。

public class UIViewRoot extends UIComponentBase implements UniqueIdVendor {

    private List<List<FacesEvent>> events;
    
    public void broadcastEvents(FacesContext context, PhaseId phaseId) {

            if (null != (eventsForPhaseId = events.get(phaseId.getOrdinal()))) {
                while (!eventsForPhaseId.isEmpty()) {
                    FacesEvent event = eventsForPhaseId.get(0);
                    UIComponent source = event.getComponent();
                    UIComponent compositeParent = //・・・
                    source.broadcast(event);
                    eventsForPhaseId.remove(0);
                }
            }
    }


同じように ValueChangeEvent は、例えば UIInput の場合は以下のように ValueChangeEvent をキューイングしています。

public class UIInput extends UIOutput implements EditableValueHolder {
    public void validate(FacesContext context) {
        Object submittedValue = getSubmittedValue();
        Object newValue = getConvertedValue(context, submittedValue);
        validateValue(context, newValue);
        if (isValid()) {
            Object previous = getValue();
            setValue(newValue);
            setSubmittedValue(null);
            if (compareValues(previous, newValue)) {
                queueEvent(new ValueChangeEvent(this, previous, newValue));
            }
        }
    }

キューイングされたイベントは、前述の Action の流れと同じようにイベントが処理されます。

ProcessValidationsPhase

ApplyRequestValuesPhase でリクエスト値をコンポーネントに反映したので、次は ProcessValidationsPhase です。

public class ProcessValidationsPhase extends Phase {

    public void execute(FacesContext facesContext) throws FacesException {

        UIComponent component = facesContext.getViewRoot();
        try {
            component.processValidators(facesContext);
        } catch (RuntimeException re) {
            String exceptionMessage = re.getMessage();
            throw new FacesException(exceptionMessage, re);
        }
    }
}

他のフェーズと同じようにコンポーネントに委譲して processValidators() を呼んでいます。

例えば UIInput の processValidators() は以下のようになっています。

public class UIInput extends UIOutput implements EditableValueHolder {

    public void processValidators(FacesContext context) {

        if (!isRendered()) {
            return;
        }

        if (!isImmediate()) {
            executeValidate(context);
        }
        for (Iterator<UIComponent> i = getFacetsAndChildren(); i.hasNext(); ) {
            i.next().processValidators(context);
        }
    }
}

自身のコンポーネントのバリデーション処理は executeValidate() で実施しており以下のようになっています。

public class UIInput extends UIOutput implements EditableValueHolder {

    private void executeValidate(FacesContext context) {
        validate(context);
        if (!isValid()) {
            context.validationFailed();
            context.renderResponse();
        }
    }

    public void validate(FacesContext context) {

        Object submittedValue = getSubmittedValue();
        // ・・・
        Object newValue = null;

        try {
            newValue = getConvertedValue(context, submittedValue);
        } catch (ConverterException ce) {
            addConversionErrorMessage(context, ce);
            setValid(false);
        }

        validateValue(context, newValue);

        if (isValid()) {
            Object previous = getValue();
            setValue(newValue);
            setSubmittedValue(null);
            // ・・・
        }
    }
}

submitte された値を変換し、validateValue() を呼びます。 validateValue() は以下のようになっています。

    protected void validateValue(FacesContext context, Object newValue) {

        if (isValid() && isRequired() && isEmpty(newValue)) {
            // ・・・
            context.addMessage(getClientId(context), message);
            setValid(false);
        }

        if (isValid() && (!isEmpty(newValue) || validateEmptyFields(context))) {
            Validator[] validators = this.validators.asArray(Validator.class);
            for (Validator validator : validators) {
                try {
                    validator.validate(context, this, newValue);
                } catch (ValidatorException ve) {
                    setValid(false);
                    FacesMessage message = // ・・・;
                    context.addMessage(getClientId(context), message);
                }
            }
        }
    }

必須チェックを先に行った後、登録済みの Validator 全てに対して validate() を呼んでいます。

例えば BeanValidator の場合は validate() で以下の処理が行われます。

public class BeanValidator implements Validator, PartialStateHolder {

    public void validate(FacesContext context,
                         UIComponent component,
                         Object value) {

        ValueExpression valueExpression = component.getValueExpression("value");
        ValidatorFactory validatorFactory = // ・・・
        ValidatorContext validatorContext = validatorFactory.usingContext();
        // ・・・
        javax.validation.Validator beanValidator = validatorContext.getValidator();
        
        ValueExpressionAnalyzer expressionAnalyzer = new ValueExpressionAnalyzer(valueExpression);
        ValueReference valueReference = expressionAnalyzer.getReference(context.getELContext());

        if (isResolvable(valueReference, valueExpression)) {
            Set<ConstraintViolation<?>> violations = null;
            violations = beanValidator.validateValue(valueReference.getBaseClass(),
                                                    valueReference.getProperty(),
                                                    value,
                                                    validationGroupsArray);

            if (violations != null && !violations.isEmpty()) {
                // ・・・
                throw new ValidatorException(messages);
            }
        }        
    }
}

モデルへの反映はこの後のフェーズですが、モデル側のビーンバリデーション定義を ValueExpressionAnalyzer で解析してバリデーション処理が行われます。

エラーがあった場合は ValidatorException をスローしています。この例外は呼び元でキャッチされ、クライアントID とともに FacesMessage としてコンテキストに登録されます。

UpdateModelValuesPhase

続いて UpdateModelValuesPhase。

public class UpdateModelValuesPhase extends Phase {

    public void execute(FacesContext facesContext) {
        UIComponent component = facesContext.getViewRoot();
        component.processUpdates(facesContext);
    }
}

こちらも同じように component.processUpdates() でコンポーネントに処理を委譲しています。

この中でコンポーネントツリーに反映された値をバッキングビーンなどのモデルに反映します。

例えば UIInput の場合の processUpdates() は以下のようになっています。

public class UIInput extends UIOutput implements EditableValueHolder {

    public void processUpdates(FacesContext context) {

        if (!isRendered()) {
            return;
        }

        super.processUpdates(context);
        updateModel(context);
        if (!isValid()) {
            context.renderResponse();
        }
    }
}

updateModel() は以下のようになっています。

    public void updateModel(FacesContext context) {

        ValueExpression ve = getValueExpression("value");
        if (ve != null) {
            Throwable caught = null;
            FacesMessage message = null;
            try {
                ve.setValue(context.getELContext(), getLocalValue());
                resetValue();
            } catch (Exception e) {
                caught = e;
                // ・・・
                setValid(false);
            }

            if (caught != null) {
                UpdateModelException toQueue = new UpdateModelException(message, caught);
                ExceptionQueuedEventContext eventContext =
                    new ExceptionQueuedEventContext(context, toQueue, this,PhaseId.UPDATE_MODEL_VALUES);
                context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, eventContext);
            }
        }
    }

ValueExpression の setValue() によりモデルへの反映が行われます。モデルが CDI の場合は WeldValueExpression を経由して値が設定されます(TagValueExpression を経由して WeldValueExpression が使われる流れ)。

そして実際の WeldValueExpression では、

    public void setValue(EvaluationContext ctx, Object value) throws ELException {
        AstValue.Target t = this.getTarget(ctx);
        Object property = t.suffixNode.getValue(ctx);
        ctx.setPropertyResolved(false);

        ELResolver elResolver = ctx.getELResolver();
        Class targetType = elResolver.getType(ctx, t.base, property);
        Object targetValue = elResolver.convertToType(ctx, value, targetType);

        if(ctx.isPropertyResolved()) {
            value = targetValue;
        } else {
            ctx.setPropertyResolved(false);
            if(value != null || targetType.isPrimitive()) {
                value = ELSupport.coerceToType(value, targetType);
            }
        }

        elResolver.setValue(ctx, t.base, property, value);
        if(!ctx.isPropertyResolved()) {
            ELSupport.throwUnhandled(t.base, property);
        }
    }

ELResolver でEL式を解決し、elResolver.setValue() でモデルに値を反映しています。

ELResolver は色々種類があり、BeanELResolver の場合は以下のようになっています。

public class BeanELResolver extends ELResolver {
    public void setValue(ELContext context, Object base, Object property, Object val) {
        BeanELResolver.BeanProperty bp = this.getBeanProperty(context, base, property);
        Method method = bp.getWriteMethod();
        method.invoke(base, new Object[]{val});
        context.setPropertyResolved(base, property);
    }

バッキングビーンのプロパティに対して method.invoke() で値を設定していますね。

InvokeApplicationPhase

このフェーズでは、Action と ActionListner が実行されます。

Action と ActionListner は 前のフェーズ ApplyRequestValuesPhase で、フェーズID PhaseId.INVOKE_APPLICATION を指定してイベントがキューイングしたものが実行されます。

ApplyRequestValuesPhase で以下のようにイベントがキューイング済みです。

e.setPhaseId(PhaseId.INVOKE_APPLICATION);
super.queueEvent(e);

InvokeApplicationPhase の execute() 以下のようになっており、

public class InvokeApplicationPhase extends Phase {

    public void execute(FacesContext facesContext) throws FacesException {
        UIViewRoot root = facesContext.getViewRoot();
        root.processApplication(facesContext);
    }
}

UIViewRoot の processApplication() を呼んでいます。この中身は以下のようになります。

public class UIViewRoot extends UIComponentBase implements UniqueIdVendor {
    public void processApplication(FacesContext context) {
        initState();
        notifyBefore(context, PhaseId.INVOKE_APPLICATION);
        try {
            if (!skipPhase) {
                broadcastEvents(context, PhaseId.INVOKE_APPLICATION);
            }
        } finally {
            clearFacesEvents(context);
            notifyAfter(context, PhaseId.INVOKE_APPLICATION);
        }
    }
}

PhaseId.INVOKE_APPLICATION でフェーズIDを指定して broadcastEvents() しているだけです。

RenderResponsePhase

最後のフェーズ RenderResponsePhase です。

execute() は以下のようになっています。

public class RenderResponsePhase extends Phase {

    public void execute(FacesContext facesContext) throws FacesException {

        facesContext.getPartialViewContext();
        
        ViewHandler vh = facesContext.getApplication().getViewHandler();
        ViewDeclarationLanguage vdl =
              vh.getViewDeclarationLanguage(facesContext,
                                                facesContext.getViewRoot().getViewId());
        if (vdl != null) {
            vdl.buildView(facesContext, facesContext.getViewRoot());
        }

        boolean viewIdsUnchanged;
        do {
            String beforePublishViewId = facesContext.getViewRoot().getViewId();
            facesContext.getApplication().publishEvent(facesContext,
                                                       PreRenderViewEvent.class,
                                                       facesContext.getViewRoot());
            String afterPublishViewId = facesContext.getViewRoot().getViewId();
            viewIdsUnchanged = beforePublishViewId == null && afterPublishViewId == null ||
                    (beforePublishViewId != null && afterPublishViewId != null) &&
                    beforePublishViewId.equals(afterPublishViewId);
            if (facesContext.getResponseComplete()) {
                return;
            }
        } while (!viewIdsUnchanged);
        
        vh.renderView(facesContext, facesContext.getViewRoot());
    }
}

色々とやってはいますが、ここでは最後の vh.renderView() に着目します。

この中でコンポーネントツリーをXHTMLなどクライアントに送信する形式にレンダリングします。

ViewHandler の実装である MultiViewHandlerrenderView() は以下のようになっています。

public class MultiViewHandler extends ViewHandler {
    
    private ViewDeclarationLanguageFactory vdlFactory;
    
    public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException, FacesException {
        vdlFactory.getViewDeclarationLanguage(viewToRender.getViewId())
              .renderView(context, viewToRender);
    }
}

getViewDeclarationLanguage()ViewDeclarationLanguage を取得し、renderView() を呼んでいます。 実際には、ViewDeclarationLanguage の子クラスである ViewHandlingStrategyrenderView() が呼ばれます。

この部分の実装は以下のようになっています。

public class FaceletViewHandlingStrategy extends ViewHandlingStrategy {

    public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOException {

        WriteBehindStateWriter stateWriter = null;
        try {

            if (!Util.isViewPopulated(ctx, viewToRender)) {
                ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewToRender.getViewId());
                vdl.buildView(ctx, viewToRender);
            }

            ResponseWriter origWriter = ctx.getResponseWriter();
            Writer outputWriter = extContext.getResponseOutputWriter();

            stateWriter = new WriteBehindStateWriter(outputWriter, ctx, responseBufferSize);

            ResponseWriter writer = origWriter.cloneWithWriter(stateWriter);
            ctx.setResponseWriter(writer);

            if (ctx.getPartialViewContext().isPartialRequest()) {
                viewToRender.encodeAll(ctx);
                ctx.getExternalContext().getFlash().doPostPhaseActions(ctx);

            } else {
                writer.startDocument();
                viewToRender.encodeAll(ctx);
                ctx.getExternalContext().getFlash().doPostPhaseActions(ctx);
                writer.endDocument();
            }
            writer.close();

            boolean writtenState = stateWriter.stateWritten();
            if (writtenState) {
                stateWriter.flushToWriter();
            }

        } catch (Exception e) {
            this.handleRenderException(ctx, e);
        } finally {
            stateWriter.release();
        }
    }

色々ありますが、Writer に書き込みを行うのが大きな流れです。

ここで注目するのは viewToRender.encodeAll(ctx)stateWriter.flushToWriter() です。

UIComponent の encodeAll() は以下のようになっています。

public abstract class UIComponent implements PartialStateHolder, TransientStateHolder, SystemEventListenerHolder,
        ComponentSystemEventListener {

    public void encodeAll(FacesContext context) throws IOException {

        if (!isRendered()) {
            return;
        }

        encodeBegin(context);
        if (getRendersChildren()) {
            encodeChildren(context);
        } else if (this.getChildCount() > 0) {
            for (UIComponent kid : getChildren()) {
                kid.encodeAll(context);
            }
        }

        encodeEnd(context);

    }

各コンポーネントに対して encodeBegin() encodeEnd() が呼ばれていきます。この中で FacesContext に設定されたレスポンスライタへ書き込みが行われていきます。

このあたりはカスタムコンポーネントを作成したことがあれば良くわかりますね。HTML のタグなどを書き込む処理です。


次に stateWriter.flushToWriter() ですが、ここではコンポーネントの状態の書き込みが行われます。

WriteBehindStateWriter の flushToWriter() を見てみましょう。

final class WriteBehindStateWriter extends Writer {

    public void flushToWriter() throws IOException {
        StateManager stateManager = Util.getStateManager(context);
        ResponseWriter origWriter = context.getResponseWriter();
        StringBuilder stateBuilder = getState(stateManager, origWriter);
        // ・・・
    }

大幅に省略して載せましたが、getState() したものを以下のように hidden で書き出す処理をしています。

<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="XXXX">

この getState() は以下のようになっています。

final class WriteBehindStateWriter extends Writer {

    private StringBuilder getState(StateManager stateManager, ResponseWriter origWriter) throws IOException {
        FastStringWriter stateWriter = // ・・・
        context.setResponseWriter(origWriter.cloneWithWriter(stateWriter));
        if(state == null) {
            state = stateManager.saveView(context);
        }
        stateManager.writeState(context, state);
        context.setResponseWriter(origWriter);
        StringBuilder stateBuilder = stateWriter.getBuffer();
        return stateBuilder;
    }

StateManager を使って saveView(context) をしていますね。この実装は、

public class StateManagerImpl extends StateManager {

    @Override
    public Object saveView(FacesContext context) {
        Object result = null;

        if (context != null && !context.getViewRoot().isTransient()) {
            UIViewRoot viewRoot = context.getViewRoot();
            StateManagementStrategy strategy = null;
            String viewId = viewRoot.getViewId();
            ViewDeclarationLanguage vdl = // ・・・

            StateManagementStrategy strategy = vdl.getStateManagementStrategy(context, viewId);
            Map<Object, Object> contextAttributes = context.getAttributes();

            contextAttributes.put(StateManager.IS_SAVING_STATE, Boolean.TRUE);
            result = strategy.saveView(context);
            contextAttributes.remove(StateManager.IS_SAVING_STATE);
        }
        return result;
    }

StateManagementStrategy で saveView() しています。

色々とある Strategy ですが、FaceletPartialStateManagementStrategy を見てみましょう。

public class FaceletPartialStateManagementStrategy extends StateManagementStrategy {
    @Override
    public Object saveView(FacesContext context) {

        UIViewRoot viewRoot = context.getViewRoot();
        if (viewRoot.isTransient()) {
            return null;
        }

        final Map<String, Object> stateMap = new HashMap<String, Object>();
        final StateContext stateContext = StateContext.getStateContext(context);

        context.getAttributes().put(SKIP_ITERATION_HINT, true);
        Set<VisitHint> hints = EnumSet.of(VisitHint.SKIP_ITERATION);
        VisitContext visitContext = VisitContext.createVisitContext(context, null, hints);
        final FacesContext finalContext = context;

        try {
            viewRoot.visitTree(visitContext, new VisitCallback() {

                public VisitResult visit(VisitContext context, UIComponent target) {
                    VisitResult result = VisitResult.ACCEPT;
                    Object stateObj;
                    if (!target.isTransient()) {
                        if (stateContext.componentAddedDynamically(target)) {
                            target.getAttributes().put(DYNAMIC_COMPONENT, target.getParent().getChildren().indexOf(target));
                            stateObj = new StateHolderSaver(finalContext, target);
                        } else {
                            stateObj = target.saveState(context.getFacesContext());
                        }
                        if (stateObj != null) {
                            stateMap.put(target.getClientId(context.getFacesContext()), stateObj);
                        }
                    } else {
                        return VisitResult.REJECT;
                    }
                    return result;
                }
            });
        } finally {
            context.getAttributes().remove(SKIP_ITERATION_HINT);
        }

        saveDynamicActions(context, stateContext, stateMap);
        StateContext.release(context);
        return new Object[]{null, stateMap};
    }
}

UIViewRoot から辿り、各 UIComponent を visit し、saveState() を呼んでいますね。

カスタムコンポーネントやカスタムバリデータを作成したことがあれば、おなじみのsaveState()restoreState() です。 コンポーネントの状態をセッション(設定によってはクライアント)にsaveState() で保存し、RestoreViewPhase でコンポーネントツリーの再構築の際に restoreState() で状態が復元されます。

注意すべきなのは、ここで保存されるのはあくまでもコンポーネントツリーの状態で、マネージドビーン側の値はマネージドビーン側のライフサイクルに応じた別管理となっている点です。

まとめ

  • JSF のライフサイクルは、Lifecycle の execute() で処理される
  • Lifecycle の中では、Phase のインスタンスが順番に処理される
    • RestoreViewPhase
    • ApplyRequestValuesPhase
    • ProcessValidationsPhase
    • UpdateModelValuesPhase
    • InvokeApplicationPhase
    • RenderResponsePhase
  • 各フェーズでは、ツリー上に構築された UIComponent に処理を(大体)委譲する
  • Process Event とはキューイングされたイベントを broadcastEvents() することである

JavaServer Faces完全ガイド

JavaServer Faces完全ガイド