Thursday, February 18, 2010

Ejemplo Aplicación JSF 2.0 con CDI 1.0

Como había comentado en una entrada anterior, Java EE 6 nos ofrece varias opciones para guardar el estado de los componentes UI cuando utilizamos JavaServer Faces.

Mi opción preferida es utilizar Contexts and Dependency Injection (CDI) para los backing beans JSF. Por lo tanto, en esta entrada voy a presentar un pequeño ejemplo de como utilizar JSF + CDI y voy a ilustrar algunas de las bondades de CDI.
No voy a explicar como configurar el proyecto para que utilice CDI, eso pueden verlo en esta entrada del blog.

El código fuente del ejemplo completo JSF-CDI.zip se encuentra en el proyecto creado, para los ejemplos del blog, en la plataforma kenai.com.

Aplicación de Ejemplo

La aplicacion de ejemplo es sumamente sencilla, esta compuesta solamente de dos paginas. La primera pagina tiene una caja de texto en el que se puede definir una entrada. Al presionar un botón se muestra en una etiqueta el texto ingresado. La segunda pagina permite ver el texto ingresado en la primera pagina y su función es ilustrar como se puede guardar el estado entre peticiones de distintas paginas.

JSF 2.0 utiliza como tecnología de vista Facelets ya que se integra mejor con JSF que JSP, por lo tanto las paginas del ejemplo son Facelets. A continuación se muestra la porción relevante del código fuente de ambas paginas:


<h:form>
<h:panelGrid columns="3">
<h:outputLabel value="Please enter an input:"/>
<h:inputText value="#{controller.input}"/>
<h:commandButton action="#{controller.setOutputAction}"
value="Set Output"/>
<h:outputLabel value="You entered: "/>
<h:outputText value="#{controller.output}"/>
</h:panelGrid>
<br/>
<h:commandButton action="/result" value="See output in another page"/>
</h:form>



<h:form>
<h:panelGrid columns="3">
<h:outputLabel value="You entered: "/>
<h:outputText value="#{resultController.output}"/>
</h:panelGrid>
<br/>
<h:commandButton action="/index" value="Go to main page"/>
</h:form>


No hay grandes cambios aquí, las paginas Facelets lucen igual que en JSF 1.2, están compuesta de componentes UI JSF y utilizan expresiones EL (Expression Language) para referirse a instancias de las clases que se utilizan como backing beans.

El cambio está en los backing beans, que son clases manejadas por CDI.

CDI como Backing Bean JSF

CDI ofrece un conjunto de servicios que permiten que clases manejadas puedan existir durante el ciclo de vida de la aplicación en ámbitos bien definidos.

Cuando un jar cuenta con un archivo beans.xml el contenedor registra todas las clases del jar con el motor CDI automáticamente. No es necesaria ninguna anotación para especificar que una clase tiene que ser manejada por CDI.

De todas maneras, si queremos que las instancias de las clases sean accesibles a través de expresiones EL, tenemos que decorar las clases con la anotación


@javax.inject.Named


De esta manera podemos acceder a los miembros de las clases manejadas a través de expresiones EL.
Por defecto el nombre de la instancia va a ser el nombre de la clase con su primera letra en minúscula, aunque podemos especificar otro nombre para referirnos a la instancia de la clase manejada. Ej:


@javax.inject.Named
public class Controller { ... }

#{controller.input}

@javax.inject.Named("testController")
public class Controller { ... }

#{testController.input}


CDI permite que las instancias existan en ámbitos bien definidos. Para marcar el ámbito de una clase manejada CDI, se utilizan anotaciones que se encuentran en el paquete javax.enterprise.context. Los ámbitos soportados por CDI son los siguientes:


  • @ApplicationScoped (Ámbito de Aplicación): Las instancias existen durante todo el ciclo de vida de la aplicacion. Estas instancias son globales y accesibles desde cualquier sesión HTTP.

  • @SessionScoped (Ámbito de Sesión): Las instancias son accesibles por todas las peticiones que ocurren durante una sesión HTTP.

  • @ConverstationScoped (Ámbito de Conversación): Las instancias existen durante una conversación. Una conversación esta compuesta de varias peticiones y pueden existir varias conversaciones por sesión.
    Realmente, cada petición tiene asociada una conversación. Solamente que si la conversación no es marcada como "long-running" el contexto de conversación se libera al termino de la petición.
    Por el contrario si la conversación fue marcada como "long-running" en alguna petición, el contexto de conversación se propaga a las siguientes peticiones hasta que este contexto sea destruido explícitamente.

  • @RequestScoped (Ámbito de Petición): Las instancias solamente existen durante la peticion HTTP. Una vez que termina el ciclo de vida de la petición, también termina su contexto.


El ámbito de petición (@RequestScoped) es muy común, por lo que existe una anotación que incluye las anotaciones @Named y @RequestScoped, esta anotación es:


@javax.enterprise.inject.Model


En el ejemplo, queremos que la aplicacion tenga el siguiente comportamiento: cuando se presione el botón, se establezca el valor del campo asociado con la caja de texto, a la etiqueta de salida. Además, queremos que ese valor sea accesible desde otra pagina.

Con estos requerimientos, el valor tendría que poder existir durante varias peticiones HTTP. Por lo tanto la instancia del backing bean de la pagina principal tiene que ser anotada con una de las anotaciones @SessionScoped o @ConversationScoped, para que sea accesible durante varias peticiones.

Para este ejemplo se usa @SessionScoped para hacer mas claro el ejemplo y en próximas entradas se utilizara otro ejemplo para mostrar como funcionan las conversaciones en CDI.

Por lo tanto la clase backing bean de la pagina principal se llama Controller y esta definida de la siguiente manera:


@javax.inject.Named
@javax.enterprise.context.SessionScoped
public class Controller implements Serializable {
private String input;
private String output;

@EJB UpperCaseEJB testEJB;

/*
Resto de la declaración de la clase omitida.
*/
}


Implementa la interfaz Serializable porque CDI exige que las clases que se definen en un ámbito que no sea @RequestScoped, implementen esta interfaz.
Los campos input y output están asociados a los controles UI inputText y outputText respectivamente.
Las clases manejadas por CDI cuentan con varios servicios, la inyección es una de ellas y por eso utilizamos un EJB (UpperCaseEJB) para demostrar eso.
La instancia testEJB va a ser inyectada por el contenedor cuando se instancie la clase Controller.

Cuando hacemos click en el botón:


<h:commandButton action="#{controller.setOutputAction}"
value="Set Output"/>


se ejecuta el método setOutputAction() de la clase Controller, el método es el encargado de establecer el valor de la leyenda para mostrar en pantalla:


this.output = testEJB.toUpper(this.input);


El método toUpper() del EJB TestEJB convierte la cadena a mayúsculas, esto se utilizo solamente con fines académicos para demostrar lo sencillo que es utilizar un EJB desde una clase CDI.

Eso es todo, cuando se visualice la pagina nuevamente se va a ver el valor del campo output:


<h:outputLabel value="You entered: "/>
<h:outputText value="#{controller.output}"/>


Contextos e Inyección en CDI

La verdad que hasta ahora no vimos nada tan novedoso, después de todo los @ManagedBean JSF 2.0 también utilizan anotaciones para registrarse y definir su ámbito, son accesibles desde expresiones EL y también pueden inyectar instancias de EJBs.

Una de las novedades de CDI es su mecanismo de biyeccion, es decir la capacidad de publicar una instancia de una clase en algún ámbito y poder referirse a esa instancia desde otra clase.

En el ejemplo, queremos que se pueda ver el valor ingresado en la caja de texto, pero desde otra pagina.
Para esto, el backing bean de la pagina result.xhtml tiene que poder acceder a este valor.

Entonces, lo que queremos es publicar la instancia de la clase Controller que tiene este valor en un ámbito y que luego sea inyectado en la clase ResultController. Cuando se crea una instancia de la clase ResulController, automáticamente el contenedor deberia asignar la referencia a la instancia de la clase publicada.

La clase Controller tiene un ámbito de Sesión ya que fue decorada con la anotacion @SessionScoped.
Además, todas las clases son manejadas por CDI automáticamente, recordemos que la anotacion @Named es tan solo para hacer que la clase sea visible por las expresiones EL.
Por lo tanto, lo único que tenemos que hacer en la clase ResultController es inyectar una instancia de la clase Controller con la anotacion:


@javax.inject.Inject


Ej:


@Named
public class ResultController {

private @Inject Controller controller;

public String getOutput()
{
return controller.getOutput();
}
}
Eso es todo! El contenedor automáticamente inyecta una instancia de la clase Controller.

Por ultimo obtenemos el valor de output con el método getOutput().

Puede darse el caso de que tengamos mas de una instancia de una clase en particular. Por ejemplo, podríamos tener un sistema de control de versiones que tenga que comparar la diferencia entre dos archivos.

En estos casos tenemos que tener una manera para discriminar cual es la instancia que queremos inyectar.

Otros frameworks como Jboss Seam utilizan cadenas de texto como identificadores, pero esto es propenso a errores y los problemas se identifican recien en fase de ejecución.

Por el contrario, CDI utiliza un esquema de discriminación fuertemente tipado. Se utilizan anotaciones para especificar cual es la instancia que queremos inyectar.

En el ejemplo de los archivos tendríamos dos anotaciones, una que se podría llamar @Modificado y otra que podría llamarse @Base.

No solo tenemos que utilizar la anotacion a la hora de inyectar una instancia, sino que la publicación de una instancia ya no puede ser automática.
Tenemos que declarar explícitamente que tipo de instancia estamos publicando.

Por lo tanto, modificamos un poco la aplicacion de ejemplo para utilizar inyección fuertemente tipada. Lo primero que tenemos que hacer es definir una anotacion para discriminar a nuestra instancia que queremos publicar.

La anotacion que creamos se llama @SetupController, esta anotacion nos dice que el Controller que queremos inyectar es el utilizado para establecer el valor del input.

La manera de publicar instancias en CDI es definiendo un método cuyo valor de retorno este decorado con la anotacion:


@javax.enterprise.inject.Produces


Por ejemplo si quisiéramos publicar explícitamente nuestra instancia de la clase Controller podemos definir el siguiente método en la misma clase Controller:


public @Produces @SetupController Controller getController()
{
return this;
}


La semántica del método es que esta produciendo una instancia de una clase Controller con tipo @SetupController.

Cuando uno quiere inyectar una instancia de un tipo en particular, el contenedor busca en todas las instancias que existen en algún ámbito, aquella que cuenta con un método que produce el tipo de instancia que nosotros queremos inyectar.
Una vez que encuentra el método, lo ejecuta, obtiene el valor y lo inyecta en la instancia definida en nuestra clase.

Entonces, si quisiéramos inyectar la instancia de tipo @SetupController en nuestra clase ResultController, tendríamos que cambiar la declaración del campo a la siguiente:


private @Inject @SetupController Controller controller;


También podríamos haber publicado e inyectado tan solo el String que representaba nuestro output, pero me pareció mas interesante ver como se publicaba la propia clase Controller.

Eso es todo, en las siguientes entradas veremos como funcionan las conversaciones en CDI y otras cuestiones como agregar validaciones a nivel de modelo utilizando Bean Validation 1.0.