Desvendando o ciclo de vida do JSF

Quase todo programador que participou de um projeto baseado em JSF, pelo menos em algum momento, foi apresentado ao famoso ciclo de vida do framework. Na maior parte do tempo ouvimos falar das tais das fases, mas tenho percebido que muitas vezes essas mesmas fases ficam parecendo meio que mágicas. A ideia do post de hoje é mostrar um pouco do código que é implementado para possibilitar que esse ciclo de vida realmente aconteça. Vamos tomar como base a implementação padrão da especificação provida pela própria Oracle, a Mojarra.

Para começar, vamos dar uma olhada na classe que representa ciclo de vida em si, a LifecycleImpl.

    public class LifecycleImpl extends Lifecycle {

        // The Phase instance for the render() method
        private Phase response = new RenderResponsePhase();

        // The set of Phase instances that are executed by the execute() method
        // in order by the ordinal property of each phase
        private Phase[] phases = {
            null, // ANY_PHASE placeholder, not a real Phase
            new RestoreViewPhase(),
            new ApplyRequestValuesPhase(),
            new ProcessValidationsPhase(),
            new UpdateModelValuesPhase(),
            new InvokeApplicationPhase(),
            response
        };
        
        public LifecycleImpl() {
            
        }
        

        // Execute the phases up to but not including Render Response
        public void execute(FacesContext context) throws FacesException {

            if (context == null) {
                throw new NullPointerException
                    (MessageUtils.getExceptionMessageString
                     (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context"));
            }

            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("execute(" + context + ")");
            }

            for (int i = 1, len = phases.length -1 ; i < len; i++) { // Skip ANY_PHASE placeholder

                if (context.getRenderResponse() ||
                    context.getResponseComplete()) {
                    break;
                }

                phases[i].doPhase(context, this, listeners.listIterator());

            }

        }

        //muito resto de código aqui...
            
    }

Perceba que existe um atributo chamado de phases, contendo exatamente as referências para cada uma das fases do JSF. Um outro trecho interessante é o que segue abaixo:

        // Execute the phases up to but not including Render Response
        public void execute(FacesContext context) throws FacesException {

            if (context == null) {
                throw new NullPointerException
                    (MessageUtils.getExceptionMessageString
                     (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context"));
            }

            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("execute(" + context + ")");
            }

            for (int i = 1, len = phases.length -1 ; i < len; i++) { // Skip ANY_PHASE placeholder

                if (context.getRenderResponse() ||
                    context.getResponseComplete()) {
                    break;
                }

                phases[i].doPhase(context, this, listeners.listIterator());

            }

        }

Perceba que a execução de cada uma das fases é resumida a um simples for, chamando cada uma das fases, depois da outra :). Perceba que o método doPhase inclusive recebe como terceiro argumento uma lista PhaseListener. Caso você já tenha criado um deles, eles está no meio da lista passada como parâmetro! Um outro ponto a se destacar é a má prática dentro do código deles, verificando se um argumento é nulo e lançando NPE, ao invés de IllegalArgumentException :/.

Agora vamos analisar um pouco como é a implementação da fase em si. A classe abstrata Phase, representa a abstração mais genérica de uma fase do JSF.

    public abstract class Phase {

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

            context.setCurrentPhaseId(getId());
            PhaseEvent event = null;
            if (listeners.hasNext()) {
                event = new PhaseEvent(context, this.getId(), lifecycle);
            }

            // start timing - include before and after phase processing
            Timer timer = Timer.getInstance();
            if (timer != null) {
                timer.startTiming();
            }

            try {
                handleBeforePhase(context, listeners, event);
                if (!shouldSkip(context)) {
                    //método abstrato
                    execute(context);
                }
            } catch (Throwable e) {
                queueException(context, e);
            } finally {
                try {
                    handleAfterPhase(context, listeners, event);
                } catch (Throwable e) {
                    queueException(context, e);
                }
                // stop timing
                if (timer != null) {
                    timer.stopTiming();
                    timer.logResult(
                          "Execution time for phase (including any PhaseListeners) -> "
                          + this.getId().toString());
                }

                context.getExceptionHandler().handle();
            }

        }


        public abstract void execute(FacesContext context) throws FacesException;

        public abstract PhaseId getId();


        //muito resto de código aqui...

    }

Para quem curte Design Patterns, esse é a típica implementação do Template Method. Perceba que o método doPhase, que é invocado lá pelo ciclo de vida, tem um algoritmo bem definido e abre espaço para especialização através do método execute. Esse é o método que vai ser implementado por cada uma das fases do JSF. Um outro método abstrato importante é o getId, que retorna uma enum representando a fase associado com o objeto é em questão. É ele que é usado pelos métodos handleBeforePhase handleAfterPhase  para descobrir se um PhaseListener deve ser executado.

Como acabamos de ver na classe LifecycleImpl, existem seis implementações da   classe Phase, representando justamente as seis etapas do ciclo de vida do JSF.

  1. RestoreViewPhase

  2. ApplyRequestValuesPhase

  3. ProcessValidationsPhase

  4. UpdateModelValuesPhase

  5. InvokeApplicationPhase

  6. RenderResponsePhase

Cada uma delas implementa o método execute getId. Vamos dar uma olhada na RestoreViewPhase.

    public class RestoreViewPhase extends Phase {

        public PhaseId getId() {

            return PhaseId.RESTORE_VIEW;

        }

        public void execute(FacesContext facesContext) throws FacesException {

            //já vamos olhar um pouco dele
        }
        
        //muito resto de código aqui...

    } // end of class RestoreViewPhase

O método getId, como era esperado, retorna RESTORE_VIEW. Lembre que usamos essa enum para informarmos em quais das fases o nosso PhaseListener deve ser executado. O método execute é um tanto quanto grande, então eu decidi pegar apenas uma parte da implementação, para podermos discutir.

        try {

            // Reconstitute or create the request tree
            Map requestMap = facesContext.getExternalContext().getRequestMap();
            String viewId = (String)
              requestMap.get("javax.servlet.include.path_info");
            if (viewId == null) {
                viewId = facesContext.getExternalContext().getRequestPathInfo();
            }

            // It could be that this request was mapped using
            // a prefix mapping in which case there would be no
            // path_info.  Query the servlet path.
            if (viewId == null) {
                viewId = (String)
                  requestMap.get("javax.servlet.include.servlet_path");
            }

            if (viewId == null) {
                viewId = facesContext.getExternalContext().getRequestServletPath();
            }

            if (viewId == null) {
                throw new FacesException(MessageUtils.getExceptionMessageString(
                  MessageUtils.NULL_REQUEST_VIEW_ERROR_MESSAGE_ID));
            }

            ViewHandler viewHandler = Util.getViewHandler(facesContext);

            boolean isPostBack = (facesContext.isPostback() && !isErrorPage(facesContext));
            if (isPostBack) {
                facesContext.setProcessingEvents(false);
                // try to restore the view
                viewRoot = viewHandler.restoreView(facesContext, viewId);
                if (viewRoot == null) {
                ....

Ele tenta de várias formas descobrir a origem da requisição para a view. Depois disso, ainda existe a verificação de postback. Essa é a verificação para descobrir se a requisição veio em função de um formulário do JSF ou se é apenas a primeira requisição para a view. Inclusive, se é um postback, o JSF tenta restaurar o estado da tela! Caso tenha ficado curioso, tente passear pelas outras implementações!

Esse foi mais um post daqueles que tentam te ajudar a entender mais do ambiente que você está executando o seu código. Estamos cercados por diversos frameworks e, em algum momento, é importante compreender o que acontece além da visão de simples usuário.

Advertisements

One comment on “Desvendando o ciclo de vida do JSF

  1. rponte says:

    Muito bom, Alberto.

    Como disse no Twitter, quando o bicho pega é importante ter uma idéia de como a coisa funciona, nesse caso, conhecer um pouco dos detalhes de implementação.

    De qualquer forma, para maioria dos desenvolvedores entender bem as responsabilidades de cada fase é suficiente para resolver os problemas do dia a dia. Já para quem desenvolve componentes, não tem para onde correr, tem que conhecer detalhes de implementação.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s