JSF2

RECU-0131 (Recurso Referencia)

Descripción

Java Server Faces es un tecnología y framework para aplicaciones Java basadas en web que simplifica el desarrollo de interfaces de usuario en aplicaciones Java EE. JSF usa Java Server Pages como la tecnología que permite hacer el despliegue de las páginas.

Características

JSF incluye las siguientes características principales:

  • Un conjunto de APIs para representar componentes de una interfaz de usuario y administrar su estado, manejar eventos, validar entradas, definir un esquema de navegación de las páginas y dar soporte para internacionalización y accesibilidad.
  • Un conjunto por defecto de componentes para la interfaz de usuario.
  • Dos bibliotecas de etiquetas personalizadas para Java Server Pages que permiten expresar una interfaz Java Server Faces dentro de una página JSP.
  • Un modelo de eventos en el lado del servidor.
  • Administración de estados.
  • Beans administrados.

La especificación de JSF fue desarrollada por la Java Community Process. Versiones de JSF:

  • JSF 1.0 (11032004) lanzamiento inicial de las especificaciones de JSF.
  • JSF 1.1 (27052004) lanzamiento que solucionaba errores. Sin cambios en las especificaciones ni en el renderkit de HTML.
  • JSF 1.2 (11052006) lanzamiento con mejoras y corrección de errores.
  • JSF 2.0 (12082009) último lanzamiento.

Las principales implementaciones de JSF son:

  • JSF Reference Implementation de Sun Microsystems.
  • MyFaces proyecto de Apache Software Foundation.
  • RichFaces
  • ICEfaces Contiene diversos componentes para interfaces de usuarios más enriquecidas, tales como editores de texto enriquecidos, reproductores de multimedia, entre otros.
  • jQuery4jsf Contiene diversos componentes sobre la base de uno de los más populares framework javascript jQuery.

A continuación pasamos a describir las principales características introducidas en JSF 2.0

AJAX en JSF2

JSF2 ya viene con soporte para AJAX (este está basado en el soporte proporcionado por RichFaces). De esta forma disponemos de una nueva etiqueta f:ajax que pondremos en el componente donde queremos tener comportamiento AJAX; de forma que si la ponemos en un h:commandButton se disparará al pulsar el botón, y si la ponemos en un h:inputText se disparará al cambiar de valor (cuando el input text pierde el foco).

Básicamente lo que vamos ha hacer con esta etiqueta es indicar que otros componentes queremos que se repinten cuando se produzca el evento (al pulsar el botón, al cambiar de valor, ...). Esto lo haremos con el atributo render. Como ventajas encontramos:

Del lado del cliente:

  • Se puede actualizar los elementos JSF (h:outputText, h:inputText, h:selectOneMenu, etc) a partir de eventos.
  • No es necesario escribir en JavaScript

Del lado del servidor

  • Los backed beans están disponibles en las llamadas AJAX
  • No es necesario escribir servlets y analizar parámetros

Sin embargo también presenta desventajas:

  • Existen limitaciones al uso de h:outputText con AJAX
  • Tecnología muy nueva (> menos fuentes para aprendizaje autodidacto, depuración), solo JSF 2.0
  • Todavía puede ser difícil de depurar en el cliente (esto es porque el código de cliente se representa desde el cliente, y se tiene poco o ningún control sobre este)

Uso del tag f:ajax

Descripción general:

<h:commandButton … action="…">
<f:ajax render="id1 id2" execute="id3 id4"
event="blah" onevent="javaScriptHandler"/>
</h:commandButton>
  • Render: especificar los elementos a actualizar en el cliente
  • Execute: especificar elementos para procesar en el servidor
  • Event: especificar los eventos de usuario que inician la llamada AJAX
  • onEvent: especificar los scripts secundarios (JavaScript) a iniciar la llamada AJAX

Vamos a estudiar las distintas posibilidades que ofrece JSF 2.0 para tratar la navegación entre vistas de JSF

Con JSF2 se simplifica enormemente la navegación. Podemos seguir usando las reglas de navegación en el faces-config.xml, pero JSF2 añade soporte para "Convención frente a Configuración". Desde el inicio de la especificación, JSF 1.x cualquier caso de navegación por trivial que fuese, requería una entrada en el fichero faces-config.xml. Cuando se navegaba de la pagina1 a pagina2 en respuesta a un éxito en un componente, se introducía el siguiente código XML:

<navigation-rule>
 <from-view-id>/page1.xhtml</from-view-id>
 <navigation-case>
   <from-outcome>success</from-outcome>
   <to-view-id>/page2.xhtml</to-view-id>
 </navigation-case>
</navigation-rule>

JSF 2.0 introduce una simplificación que reduce la complejidad en la navegación. Introduce el concepto de navegación implícita. Si no hay ningún caso de navegación coincidente después de comprobar todas las reglas disponibles, el controlador de los controles de navegación comprueba que el resultado de la acción corresponde al identificador de una vista. Si se encuentra una visión coincidente de los resultados de acción , se navega de forma implícita a la vista encontrada.

En el siguiente ejemplo vemos como en el h:commandButton, en el atributo action, indicamos una cadena. Esta no es EL, por lo que no estamos haciendo referencia a un backbean. Esta cadena correspondería con el "outcome" que serviría para determinar la regla de navegación a disparar. Pero como no hemos escrito ninguna regla de navegación ¿qué es lo que va ha hacer JSF2? Sencillo, simplemente se limitará a buscar una página con el mismo nombre y la extensión .xhtml. Es decir, si en nuestro ejemplo hemos puesto action="listTutorialsView", JSF 2 intentará saltar a la vista listTutorialsView.xhtml

<h:commandButton action="listTutorialsView"  value="Submit" />

Otra mejora para el subsistema de navegación es la aparición de los casos de navegación condicional. La función de navegación condicional permite a los casos de navegación especificar una condición que debe cumplirse para que el caso de navegación sea aceptado. La condición se especifica como una expresión EL utilizando el nuevo elemento de configuración

<navigation-case>
 <from-outcome>success</from-outcome>
 <to-view-id>/page2.xhtml</to-view-id>     
 <!-- Only accept this case if the following condition is true -->
 <if>#{foo.someCondition}</if>
</navigation-case>
<navigation-rule>
  <from-view-id>/pages/course.xhtml</from-view-id>
   <navigation-case>
      <from-action>#{bean.register}</from-action>
      <if>#{bean.prerequisiteCompleted}</if>
      <to-view-id>/pages/registered.xhtml</to-view-id>
  </navigation-case>
  <navigation-case>
   <from-action>#{bean.register}</from-action>
   <if>#{bean.advisingHold}</if>
   <to-view-id>/pages/scheduleAdvisingSession.xhtml</to-view-id>
  </navigation-case>
  <navigation-case>
   <from-action>#{bean.register}</from-action>
   <if>#{not bean.payment}</if>
   <to-view-id>/pages/payForCourse.xhtml</to-view-id>
  </navigation-case>
</navigation-rule>

El sistema de navegación 1.x JSF es una caja negra. El único punto de entrada, NavigationHandler.handleNavigation(), simplemente evalúa las reglas de navegación y las causas de una navegación a ocurrir, sin dar ninguna idea de cómo se determina el objetivo de navegación.

JSF 2.0 proporciona una visión más transparente del sistema de navegación. La nueva API ConfigurableNavigationHandler proporciona acceso a los metadatos que describen las normas de navegación disponibles de los casos. En particular, el método getNavigationCase permite a los clientes mediante preguntas , sobre el manejador ConfigurableNavigationHandler, determinar qué caso de navegación coincide con un resultado concreto de una acción. Con este nuevo contrato, es posible "preventivamente" evaluar las normas de navegación y obtener el resultado ID de vista de destino y la URL.

¿Por qué es interesante? Bueno, antes de JSF 2.0, las normas de navegación fueron explícitamente en el dominio de las peticiones POST. Anteriormente, la única vez que las normas de navegación entraban en juego era durante la fase de invocación de la aplicación durante la manipulación de un POST. Al hacer que las normas de navegación estén disponibles fuera de la invocación de la aplicación, abrimos la posibilidad de aprovechar esta información en otros puntos del ciclo de vida, por ejemplo, en la respuesta que renderiza la aplicación.

Almacenamiento del estado

El almacenamiento del estado en JSF ha sido un punto delicado, tanto para desarrolladores de aplicaciones, así como para desarrolladores de componentes. El principal problema para los desarrolladores de aplicaciones es que el tamaño del estado guardado puede llegar a ser grande. Esto hace que para el lado del cliente, el almacenamiento del estado sea poco práctico y conduzca a la sobrecarga del estado de sesión. Para los desarrolladores de componentes, el problema es que desarrollar los métodos SaveState y restoreState de la aplicación es tedioso y propenso a errores.

JSF 2.0 se ocupa de estas cuestiones con la introducción de un nuevo mecanismo de almacenamiento "parcial" del estado. Esta solución se inspira en una propuesta que hizo Adam Winer (y aplicado en Apache Trinidad) hace más de 3 años. El concepto clave es que el almacenamiento de todo el estado del árbol de componentes es redundante, ya que el árbol de componentes siempre se puede restaurar a su estado inicial para volver a ejecutar la vista (es decir, volver a ejecutar los controladores del Facelet para volver a crear el árbol de componentes).

Si utilizamos la definición de la vista para restaurar el árbol de componentes a su estado inicial, entonces el único estado que necesita ser salvado, es el estado que ha sido modificado desde el punto en el que se crea inicialmente la vista. Y puesto que en la mayoría de los casos el número de componentes que se han modificado después de la creación del componente del árbol es pequeño, el tamaño de este estado modificado es generalmente mucho menor que el estado completo de componente árbol.

Un requisito para el enfoque parcial del almacenamiento del estado es que las implementaciones de los componentes deben conocer cuando ha sido totalmente configurado su estado inicial. JSF2 introduce el contrato PartialStateHolder para ayudar con este requisito. Se llama al método MarkInitialState de PartialStateHolder() para notificar a la componente de ejecución que su estado inicial se ha establecido. Sólo las modificaciones que se producen después de esta notificación necesitarán ser salvadas.

Una segunda API se ha introducido para ayudar a gestionar las implementaciones de los componentes del Estado: StateHelper. El StateHelper proporciona almacenamiento para el estado de los componentes (tales como los atributos, los listener, etc...) y alivia al autor del componente de tener que proporcionar las implementaciones de restoreState y SaveState. Como resultado de estas nuevas APIs, el almacenamiento del estado es a la vez más eficiente y más fácil de usar.

Uso de JSP y Facelets

Ver JSP como la tecnología principal para JSF no ha sido sencillo. Los problemas se han debatido durante años, y han existido mejoras para facilitar la integración. Mientras, la comunidad de JSF no ha estado en reposo. Han surgido varias alternativas para optimizar JSF para JSP , incluyendo Clay Apache Shale (ahora desechado), JSFTemplating y Facelets. Sin embargo, la falta de una solución estándar sigue siendo un punto delicado para los usuarios de JSF. JSF 2.0 reconoce la necesidad de una alternativa estándar para JSP y se ocupa de esta mediante dos pasos.

En primer lugar, JSF2 proporciona una base genérica para la integración de las lenguas de declaración de vista en el entorno de ejecución JSF. La API de ViewDeclarationLanguage define el contrato a través del tiempo de ejecución de JSF para interactuar con un lenguaje de declaración de implementaciones vista con el fin de completar tareas tales como la construcción del árbol de componentes. Este contrato permite a los autores definir sus propios lenguajes de declaración de la vista e integrar a estos con JSF de una manera estándar.

En segundo lugar, JSF introduce como primera declaración para vistas no estándar de JSP: Facelets. JSF 2.0 incluye una nueva versión prevista de la de la API 1.x Facelets. Esta versión 2.0 de Facelets le será muy familiar a cualquiera que haya estado utilizando Facelets 1.x. La mayoría de los métodos de la API 1.x están presentes, aunque ha sido necesario ajustar algunos parámetros como parte del proceso de normalización.

La inclusión de Facelets como un lenguaje estándar para ver la declaración debería aliviar las preocupaciones que los equipos de desarrollo puedan tener en hacer uso de esta tecnología.

Mientras Facelets es un nuevo enfoque para la especificación JSF, el soporte JSP está disponible para aquellos usuarios que no están dispuestos a dar el salto a una nueva tecnología de definición de la vista. Nótese, sin embargo, que la parte JSP de la especificación JSF esta básicamente parada. Ninguna de las características nuevas que implican nuevas etiquetas (componentes compuestos, los eventos del sistema, Ajax, etc ...) están expuestos a través de JSP

Configuración

A continuación se presentan las novedades propuestas en JSF 2.0 para mejorar la configuración

Declaración de los beans en JSF2

Uno de los problemas detectados en la especificación JSF 1.2, es la complejidad en la configuración . Esta situación produce una sobrecarga del fichero de configuración faces-config.xml. Para evitarlo, la nueva especificación hace uso de las anotaciones. JSF2 introduce las anotaciones @ManagedBean y @RequestScoped.

  • @ManagedBean, marca el bean para ser un managed bean con el nombre específico en el atributo nombre. En el caso de no especificar el nombre en la anotación, el nombre del managed bean será por defecto el nombre de la clase.
  • @RequestScoped, establece el ámbito en el que se sitúa el bean. Si no se establece, por defecto el ámbito será un requestscope. Los posibles ámbitos son @NoneScoped, @RequestScoped, @ViewScoped, @SessionScoped, @ApplicationScoped, y @CustomScope.
package example; 

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
 
@ManagedBean(name="userBean")
@RequestScoped
public class UserBean {
 
   private String name;
 
   public String getName() {
    return name;
   }
   public void setName(String name) {
    this.name = name;
   }
   public UserBean() {}
}

Inicialización de propiedades de los beans

En ocasiones es interesante poder inicializar propiedades de los beans. Un bean inicializado es aquel que cuando se crea pasa a los métodos set, las propiedades definidas por defecto. En la especificación JSF 1.2, era necesario realizarlo en la declaración del bean en el faces-config.xml. Esta situación ha sido modificada en la especificación JSF 2.0 , incluyendo anotaciones. Para marcar una propiedad de los beans es recomendable hacer uso de la anotación @ManagedProperty. A continuación se presenta un ejemplo de su uso.

@ManagedProperty(value="Madeja")
private String name;

Cuando se quiere inicializar una propiedad como una lista o un map, solo puede hacerse como en la especificación de JSF 1.2, en el fichero de configuración.

Declaración de los Managed Bean mediante anotaciones

JSF2 proporciona una esperada mejora de la usabilidad con la introducción de la configuración basada en la anotación. El objetivo de estas anotaciones es reducir el tamaño y la complejidad de los archivos faces-config.xml, que tienen cierta tendencia a ser bastante complejos. El primer conjunto de anotaciones permite a los desarrolladores configurar los managed beans.Con el viejo estilo XML de configuración:

<managed-bean>
 <managed-bean-name>foo</managed-bean-name>
 <managed-bean-class>com.foo.Foo</managed-bean-class>
 <managed-bean-scope>session</managed-bean>
</managed-bean>

Con el nuevo estilo de anotaciones para la configuración se sustituye por :

@ManagedBean
@SessionScoped
public class Foo {
}

El nombre para el bean gestionado automáticamente se deriva del nombre de la clase anotada. En el ejemplo anterior, la presencia de la anotación @ManagedBean de la clase Foo crea un bean gestionado con el nombre "foo" . Alternativamente, la anotación @ManagedBean también permite que que se especifique un nombre de forma explícita.

Se ha realizado un esfuerzo para unificar los beans y el ámbito de las anotaciones de las aplicaciones a través de las especificaciones (por ejemplo, JSF, JCDI) para Java EE 6. Mojarra y MyFaces ya proporcionan implementaciones de estas anotaciones de configuración.

Anotaciones generales

Como parte del esfuerzo para reducir la complejidad de la configuración XML, JSF2 también incluye anotaciones dirigidas a los autores de los componentes personalizados (y los objetos asociados). Estas anotaciones se incluyen:

  • @FacesComponent
  • @FacesRenderer
  • @FacesConverter
  • @FacesValidator
  • @FacesBehavior

Por supuesto, los elementos de faces-config.xml todavía están presentes para aquellos que prefieren ir por ese camino.

Orden de los elementos del Faces-config.xml

Un problema conocido con la carga faces-config.xml es que el orden en que se cargan estos archivos no se especifica. Para la mayor parte (por ejemplo, para los managed bean o la configuración de navegación) el orden no es significativo. Sin embargo, hay ciertos casos, tales como objetos para decorar el nivel de aplicación (por ejemplo, ViewHandlers), donde el orden puede ser importante.

Durante el periodo JSF 1.2, tanto MyFaces y el CI JSF adoptaron un convenio por el que los archivos faces-config.xml se cargan sobre la base de un orden derivado del nombre del archivo jar contenedor. Sin embargo, esto fue sólo una solución temporal que se puso en marcha hasta que la cuestión pudiera abordarse mediante la especificación.

JSF2 resuelve este problema al permitir que los faces-config.xml proporcionen una relación de orden en los archivos. Cada archivo faces-config.xml ahora puede declarar un nombre (a través de un nuevo elemento ) que puede ser referenciado por otros archivos faces-config.xml para ordenar propósitos. Un nuevo elemento y sub-elementos permiten ordenar los requisitos relativos a especificar. Esta adición a la especificación proporciona una forma más segura para los diversos componente conjuntos para jugar juntos en el ecosistema de JSF.

Validaciones en JSF2

La especificación JSR Bean Validation (JSR-303) define de forma genérica, mecanismos independientes para especificar las restricciones de datos de validación. La especificación incluye varias anotaciones para utilizarlas como restricciones estándar (por ejemplo, @NOTNULL @Size, @Min , @Max, etc ...) y también permite restricciones personalizadas por definir.

JSF2 proporciona la integración con las restricciones propuestas por JSR-303. En ambientes donde la aplicación de validación del bean está presente, JSF valida automáticamente las restricciones para los beans de los que hacen referencia los valores UIInput.

Además, <f:validateBean> se puede utilizar para ajustar el comportamiento de la validación en el bean. Por ejemplo, el atributo validationGroups puede utilizarse para especificar manualmente los grupos de validación que deben tenerse en cuenta en la validación de un componente particular:

 <h:inputText value="#{bean.foo}">
 <f:validateBean validationGroups="com.foo.validation.groups.Billable"/>
 </ h: inputText>

Cuando no falla la validación, los mensajes de error asociados se traducen automáticamente en FacesMessages por la aplicación JSF, por lo tanto la comunicación de los errores para el usuario final se realizan sin ninguna carga para el desarrollador de aplicaciones.

Buen uso de los validadores

La especificación JSR Bean Validation (JSR-303) define de forma genérica, mecanismos independientes para especificar las restricciones de datos de validación. Una nueva etiqueta <f:validateBean>, permite indicar que queremos usar una validación de Bean, es decir, que queremos usar una validación basada en la JSR-303. Estas validaciones se definen con anotaciones en el propio Bean.

La especificación incluye varias anotaciones para utilizarlas como restricciones estándar (por ejemplo, @NOTNULL @Size, @Min , @Max, etc ...) y también permite restricciones personalizadas por definir. Se definen las siguientes anotaciones significativas:

AnotacionesEspecificación del BeanÁmbitoDescripción
@AssertFalseSiCampo-propiedadComprobar que el elemento anotado es falso
@AssertTrueSiCampo-propiedadComprobar que el elemento anotado es true
@CreditCardNumberNoCampo-propiedadEl tipo soportado es String. Comprueba que la cadena anotada pasa el test de comprobación Luhn
@DecimalMaxSiCampo-propiedadEl tipo soportado es BigDecimal , BigInteger, String, byte, short, int, long y los respectivos wrappers. El elemento anotado debe de tener un valor menor o igual al máximo especificado
@DecimalMinSiCampo-propiedadEl tipo soportado es BigDecimal , BigInteger, String, byte, short, int, long y los respectivos wrappers. El elemento anotado debe de tener un valor menor o igual al mínimo especificado
@EmailNoCampo-propiedadComprueba que sea una cadena y que tenga formato de dirección de correo válido
@Length(min=, max=)NoCampo-propiedadNecesita ser una cadena y se comprueba que es de tamaño entre el mínimo y el máximo descrito
@MaxSiCampo-propiedadEl valor del elemento anotado es menor o igual que el máximo definido
@MinSiCampo-propiedadEl valor del elemento anotado es mayor o igual que el mínimo definido
@NotNullSiCampo-propiedadComprueba que el elemento no es null
@NotBlankNoCampo-propiedadComprueba que el elemento anotado es una cadena no nula y que el tamaño es mayor que cero.
@NotEmptyNoCampo-propiedadComprueba que el elemento anotado no esta vacío o no es null
@NullSiCampo-propiedadComprueba que el valor es nulo
@Size(min=, max=)SiCampo-propiedadTiene que ser un string, o una collection o un map. Comprueba que el tamaño del elemento esta entre el máximo y el mínimo

Además, la nueva especificación introduce nuevos validadores de carácter general, entre ellos <f:validateRequired> que comprueba que un componente sea requerido.

Validación de campo vacío

En anteriores versiones de JSF, los validadores no se aplican a los componentes de EditableValueHolder con valores presentados como null o vacío. Lamentablemente, este comportamiento limita la utilidad de las restricciones que realmente comprueban los valores vacíos. En la especificación JSR 303, con la restricción @NOTNULL puede realizarse este propósito.

A fin de dar soporte a la restricción @NOTNULL y otras limitaciones similares, JSF2 cambia el comportamiento de la validación nula. A partir de JSF2, cuando una aplicación JSR-303 está presente, los valores son validados.

Ya que esto puede causar problemas para la herencia de las implementaciones Validator que no esperan valores vacíos el parámetro de contexto javax.faces.VALIDATE_EMPTY_FIELDS puede ser utilizado para deshabilitar este comportamiento.

Nuevos Validadores

Además de <f:validateBean>, JSF2 incluye dos validadores otros nuevos:

  • <f:validateRequired> proporciona la validación de campo requerido.
  • <f:validateRegexp> proporciona la validación basada en la expresión regular.

Cargas de recursos

Si se pasa tiempo implementando componentes JSF personalizados de cualquier complejidad, con el tiempo se van a realizar en la pregunta: ¿qué hago con mis imágenes (o bibliotecas JavaScript, o las hojas de estilo)? Casi todos los desarrolladores de componentes JSF se han topado con este problema y, en ausencia de una solución estándar, ha resuelto este problema de forma individual.

JSF2 proporciona una solución común a este problema con la introducción de la API ResourceHandler. El ResourceHandler es responsable de servir a los recursos (imágenes, archivos JavaScript, hojas de estilo, etc ...) cuya situación es conocida en el classpath. Las peticiones de recursos se realizan a través de la FacesServlet, que pasa estas solicitudes al manejador ResourceHandler para su procesamiento. Esta solución permite a los componentes y sus dependencias de recursos ser incluidos en el mismo fichero JAR, sin necesidad de un servlet de bonificación, filtro de servlet o fase de escucha para servir estos artefactos.

JSF2 también ofrece varias características nuevas a fin de facilitar la inserción de referencias de recursos. Los autores de implementaciones de componentes de Java pueden anotar sus subclases UIComponent con la anotación @ResourceDependency para identificar los recursos que necesitan para ser arrastrados en cuando se utiliza el componente. Los autores pueden utilizar la nueva <h:outputScript> y <h:outputStylesheet> para ejecutar los scripts y hojas de estilo. El # {resource} de los recursos proporciona acceso directo a las URL de los recursos.

La anotación @ResourceDependency y los componentes <h:outputScript> y <h:outputStylesheet> permiten otra novedad: la reubicación de los recursos. La reubicación de recursos da al autor del componente el control sobre donde se insertan las referencias de los recursos dentro de la página. Por ejemplo, las referencias de recursos pueden ser insertada en la cabecera del documento o al final del cuerpo. Además de proporcionar la flexibilidad de donde se insertan los recursos, un efecto secundario beneficioso de la reubicación de recursos es que las referencias de recursos duplicados automáticamente se eliminan.

Buenas prácticas y recomendaciones de uso

Resumen de las recomendaciones establecidas en el recurso:

  • Hacer uso de la navegación implícita y condicional
  • Comprender y utilizar AJAX dentro de la especificación
  • Hacer uso de las anotaciones generales
  • Establecer una relación de orden en los archivos dentro del fichero de configuración faces-config.xml.
  • Hacer uso de las nuevos validadores que facilita la implementación
  • Hacer uso de de la API ResourceHandler para el manejo de recursos
  • Hacer uso de a anotación @ResourceDependency y los componentes <h:outputScript> y <h:outputStylesheet> para la reubicación de los recursos.

Contenidos relacionados