comment
IRC Chat
play_arrow
Este sitio utiliza cookies propias y de terceros. Si continúa navegando consideramos que acepta el uso de cookies. OK Más Información.

CDI 1.1 - Context and Dependecy Injection

  • 0 Respuestas
  • 2359 Vistas

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado Gus Garsaky

  • *
  • Underc0der
  • Mensajes: 93
  • Actividad:
    0%
  • Reputación -1
    • Ver Perfil
  • Skype: gus.garsaky
« en: Julio 14, 2015, 10:49:24 pm »
CDI

Context and Dependency Injection o Contexto e inyección de depencias en español, es un poderoso elemento de la plataforma Java EE el cual nos provee un poderoso mecanismo de inyección de depencias. Además, nos provee de muchas utilidades, como interceptores, calificadores, eventos, decoradores, productores y trituradores, entre otros.

CDI es un concepto incomprendido por gran parte de los programadores Java EE. En el presente documento, intentaremos despejarnos las dudas. Empecemos.


Dependency Injection

Dependency Injection (DI) es un patrón de diseño que desacopla componentes depenientes. Es parte de inversion of control (IoC) donde el objetivo que es invertido es el proceso de obtención de dependencias necesarias. El término fue originalmente dicho por Martin Fowler. Una manera de pensar en DI en un entorno manejado como Java EE es pensar en JNDI al revés. En lugar que un objeto busque por otros, el contenedor inyecta su dependencia por ti. Esto es llamado Principio Hollywood, "Don't call us (buscar objetos), we'll call you (inyectar objetos)".


Gestión del ciclo de vida

El ciclo de vid de un POJO es muy simple, creas una instancia de una clase usando la palabra reservada new y esperas a la recolección de basura para que la elimine y libere algo de memoria. En su lugar, necesitas inyectar el bean y el contenedor hace el resto, esto quiere decir que el contenedor es el único responsable de gestionar el ciclo de vida de un bean: este lo crea, este lo destruye. Así que, ¿como inicializas un bean si tu no llamas a un constructor? El contenedor te brinda un manejo luego de construir la instancia y antes de destruirla. Estas anotaciones son:

  • @PostConstruct: un método anotado con esta anotación es llamado inmediatamente después que el contenedor haya inyectado el bean (instanciado).
  • @PreDestroy: un método anotado con esta anotación es llamado justo antes que el recolector de basura elimine el bean.


CDI Bean

Un bean CDI puede ser cualquier tipo de clase que contenga lógica de negocio. Este puede ser llamado directamente por código Java vía inyección o puede ser invocado vía El desde una página JSF. Un bean es un POJO que no extiende de nada, puede inyectar referencias en otros beans (@Inject), tiene su ciclo de vida manejado por el contenedor y puede tener sus propios metodos incerptores:

Código: Java
  1. public class BankLoan {
  2.     @Inject
  3.     private AccountChecker accountChecker;
  4.     private No tienes permisos para ver links. Registrate o Entra con tu cuenta dateOfLoan;
  5.  
  6.     @PostConstruct
  7.     private void initializeDate() {
  8.         dateOfLoan = new No tienes permisos para ver links. Registrate o Entra con tu cuenta();
  9.     }
  10. }

En el ejemplo anterior, se está inyectando la dependencia del bean accountChecker, esto lo hace el contenedor. El método initializeDate anotado con @PostConstruct será llamado automáticamente luego de que se instancie la clase BankLoan. Los CDI beans tienen ciertas características que deben cumplirse de manera obligatoria. Estas son:

  • No debe ser una interna no estática.
  • Tiene un constructor por defecto sin parámetros o debe declarar un constructor anotando @Inject.
  • Es una clase concreta (no debe extender) o  es anotada con @Decorator


Para mayor comprensión, veamos el siguiente diagrama de clases:
 

¿Cómo conectamos CDStore y CreditCard? La forma que todos conocemos es mediante el operador new:

Código: Java
  1. public class CDStore {
  2.     private CreditCard creditCard;
  3.    
  4.     public CDStore() {
  5.         creditCard = new Visa();
  6.     }
  7.     public void buyItems() {
  8.         // pagar con la tarjeta
  9.           creditCard.pay();
  10.     }
  11. }

El código anterior es bien simple  y es lo que nosotros hacemos y conocemos. Pero, ¿que pasaría si quisieras pagar con MasterCard?, ¿Necesitarías crear una (otra) instancia de MasterCard? Una solución pasar su dependencia por medio del constructor de una clase externa que haga uso de la clase CDStore:

Código: Java
  1. public class CDStore {
  2.     private CreditCard creditCard;
  3.  
  4.     public CDStore(CreditCard creditCard) {
  5.         this.creditCard = creditCard;
  6.     }
  7.     ...
  8. }

En otra clase:

Código: Java
  1. CDStore cdStore = new CDStore(new MasterCard());

Ésto es lo que se conoce como inversion of control (IoC) o inversión de control (en español). La dependencia se inyecta desde una tercera clase, de ésta manera no creamos un vínculo entre CDStore y CreditCard.

Inyección (@Inject)

Java EE es un entorno gestionado, así que quizás no necesitas construir las dependencias tú mismo, en su lugar, puedes dejar que el contenedor inyecte las dependencias de los beans por tí. CDI tiene la habilidad de inyectar dependencias de prácticamente lo que sea. Veamos un ejemplo:

Código: Java
  1. public class CDStore {
  2.     @Inject
  3.     private CreditCard creditCard;
  4.    
  5.     public void buyItems() {
  6.         creditCard.pay();
  7.     }
  8.     ...
  9. }

Como se puede observar, no necesitamos construir la dependencia de creditCard, se lo dejamos al contenedor. La implementación se vería así:

Código: Java
  1. public class Visa implements CreditCard {
  2.     @Override
  3.     public void pay() {
  4.          // logica de negocio
  5.     }
  6. }

Nota que solo hemos especificado una implementación de la interface CreditCard. Si hubiéramos creado otra implementación, la inyección no se podría realizar porque el contenedor no sabría qué dependencia inyectar. Esto, lo resolveremos más adelante con calificadores (@Qualifier).



ANEXO

Puntos de inyección

Una inyección puede llevarse a cabo en tres puntos:

  • Por propiedad:
Código: Java
  1. @Inject
  2.         private CreditCard creditCard;

   Nota que no es necesario especificar setter para la propiedad.
  • Por constructor:
Código: Java
  1. @Inject
  2.          private CDStore (CreditCard creditCard) {
  3.              this.creditCard = creditCard;
  4.         }

   La regla aquí es que la inyección por constructor se puede realizar solo una vez (por    clase).

  • Por setter:
Código: Java
  1. @Inject
  2.         public void setCreditCard(CreditCard creditCard) {
  3.             this.creditCard = creditCard;
  4.         }


Ésto es un anexo, porque en realidad no importa en qué punto hagas la inyección. Por propiedad, constructor o setter, para el contenedor le da igual. Por lo tanto, escoger uno u otro, es simplemente opción personal.



Injección por defecto (@Default)

Tu puedes tener una dependencia para inyectar por defecto. Por ejemplo, nosotros tenemos solo una implementación, aquella, tendrá implícitamente el modificador @Default. Así, ésto:

Código: Java
  1. @Inject
  2. private CreditCard creditCard;

Es igual que:

Código: Java
  1. @Inject @Default
  2. private CreditCard creditCard;

Podemos apreciarlo en una imagen:
 

Si tienes una implementación, el contenedor inyectará la única dependencia existente. Si por otro lado, tienes muchas implementaciones de una interface, el contenedor no sabrá cuál implementación debe inyectar, y aquí es cuando toman tanta importancia los calificadores.


Calificadores (@Qualifier)

Cuando el sistema inicia, el contenedor CDI debe validar que todos los beans satisfasgan cada punto de inyección existente. Si alguna dependencia de un bean no puede ser satisfecha por el contenedor CDI, entonces la aplicación no se podrá desplegar informando de una insatisfacción de dependencia. Si hay una sola implementación disponible, la inyección funcionará aplicando la dependencia única, que es marcada implícitamente con @Default.

Si hay más de usa implementación y tratamos de desplegar, la aplicación no desplegará porque dectectará ambiguación en las dependencias, es decir, si hay 2 o más implementaciones, ¿cómo sabe el contenedor qué dependencia inyectar? Aquí es donde toman importancia los calificadores. Veamos el siguiente diagrama de clases que ilustra la situación actual:


Podemos ver que hemos creado una nueva implementación, llamada MasterCard, la misma está anotada bajo el calificador MasterCardPay. Además, vemos como una nueva clase, CDStoreWithQualifier inyecta la nueva dependencia. Esto lo hará mediante el calificador. Veamos el ejemplo en código (solo las nuevas clases):

Calificador MasterCardPay:

Código: Java
  1. @Qualifier
  2. @Retention(RUNTIME)
  3. @Target({FIELD, TYPE, PARAMETER, METHOD})
  4. public @interface MasterCardPay {}

Implementación MasterCard:

Código: Java
  1. @MasterCardPay
  2. public class MasterCard implements CreditCard {
  3.     private Logger logger = Logger.getLogger(MasterCard.class.getName());
  4.    
  5.     @Override
  6.     public void pay() {
  7.         logger.log(Level.INFO, "Pagando con MasterCard");
  8.     }
  9. }

La anotación @Retention, especifica el nivel de retención de la anotación. Existen 3 tipos:

  • SOURCE: la anotación será visible en el código fuente, una vez que se compila la aplicación, es descartada. La anotación no será escrita en bytecode. Por ejemplo, @Override.
  • CLASS: La anotación se descarta durante la carga de la clase por parte del ClassLoader. Sí se escribe en bytecode.
  • RUTNIME: No se descarta. Ésta anotación estará siempre disponible por reflection en tiempo de ejecución.


Las primeras dos anotaciones no podrán ser inspeccionadas en tiempo de ejecución, mientras que la última sí. Esta es la diferencia entre las 3.
La anotación @Target, especifica que elementos podrán ser afectados por la anotación. Hay varias opciones:

  • ElementType.ANNOTATION_TYPE
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • ElementType.LOCAL_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE


Creo que la mayoría se explican solas. Las dos anotaciones que son un poco confusas son ANNOTATION_TYPE y TYPE. ANNOTATION_TYPE significa que la anotación solo podrá ser usada para anotar otras anotaciones, como @Retention. TYPE significa cualquier tipo de elemento (clases, interfaces, enums, anotaciones, etc).

Comprendidos ya estos conceptos, vamos a ver la clase que inyecta la nueva implementación:

Código: Java
  1. public class CDStoreWithQualifier {
  2.     @Inject @MasterCardPay
  3.     private CreditCard masterCard;
  4.    
  5.     public void buyItems() {
  6.         masterCard.pay();
  7.     }
  8. }

Observa lo sencillo que es inyectar múltiples dependencias, solo basta con indicar el calificador de la dependencia que debe ser inyectada. Es posible también anotar un elemento con múltiples anotaciones. Java no nos limita en este aspecto. Por ejemplo:

Código: Java
  1. @Inject @Visa @Gold
  2. private CreditCard creditCard;


Productores (@Produces)

Como ya hemos visto, podemos inyectar CDI beans dentro de otros CDI beans. Pero, si quisiéramos inyectar POJOs, o tipo de datos primitivos, ¿sería eso posible? La respuesta corta es no y la razón es la siguiente.

Por defecto, no puedes inyectar clases como String o Date y la razón, es porque ellas están empaquetadas en el archivo rt.jar (Java runtime enviroment classes) y este no contiene un descriptor de beans beans.xml. Esto es importante tenerlo presente, si un archivo no tiene un descriptor beans.xml bajo el directorio META-INF, CDI no activará el descrubimiento de beans (bean discovery) y los POJOs no podrán ser tratados como beans y por ende, no podrán ser inyectados. Afortunadamente, podemos revertir esta situación con ayuda de los productores. Veamos un ejemplo:


Veamos que la clase CDSerialProducer tiene 3 anotaciones, las cuales pasamos a describir:

  • @Produces: utilizada para producir un bean listo para inyección.
  • @CDSerialPrefix: anotación propia.
  • @RandomNumber: anotación propia.


Primer veamos nuestras anotaciones, serán igual que los calificadores que ya hemos visto:

Código: Java
  1. @Retention(RUNTIME)
  2. @Target({FIELD, TYPE, PARAMETER, METHOD})
  3. public @interface CDSerialPrefix { }

Código: Java
  1. @Retention(RUNTIME)
  2. @Target({FIELD, TYPE, PARAMETER, METHOD})
  3. public @interface RandomNumber { }

Ahora, veamos como producimos beans para inyectar de tipo String e int:

Código: Java
  1. public class CDSerialProducer {
  2.     @Produces @CDSerialPrefix
  3.     private final No tienes permisos para ver links. Registrate o Entra con tu cuenta prefix = "T9-SK534";
  4.    
  5.     @Produces @RandomNumber
  6.     public int generateNumber() {
  7.         return new No tienes permisos para ver links. Registrate o Entra con tu cuenta().nextInt(10000);
  8.     }
  9. }

La anotación @Produces, producirá una propiedad y un método para inyectar. Seguido de @Produces, debemos de especificar el calificador con el cual se va a inyectar la propiedad y el método. En este caso son @CDSerialPrefix y @RandomNumber. Ahora veamos como los inyectamos:

Código: Java
  1. public class CDService {
  2.     @Inject @CDSerialPrefix
  3.     private No tienes permisos para ver links. Registrate o Entra con tu cuenta prefix;
  4.     @Inject @RandomNumber
  5.     private int number;
  6.     private Logger logger = Logger.getLogger(getClass().getName());
  7.    
  8.     public void createCD() {
  9.         logger.log(Level.INFO, "Creando CD con serial: {0}", (prefix + number));
  10.     }
  11. }

Si ejecutamos el proyecto, podemos ver que se inyectan satisfactoriamente los valores de la propiedad y el método dentro de las propiedades de CDService. Gracias a los productores, podemos inyectar prácticamente lo que sea.



ANEXO

API de InjectionPoint

Los atributos y propidades producidad por productores (@Produces) no necesitan saber ningún tipo de información acerca de donde ellos son inyectados. Sin embargo, hay ciertos casos  donde podría ser de utilidad que el bean a inyectar sepa en qué punto de inyección se ha llevado a cabo la inyección. Un ejemplo común de ésto, es generar una dependencia dinámica de Logger (para crear logs). Generalmente, siempre creamos un Logger así:

Código: Java
  1. Logger logger = Logger.getLogger(TuClase.class.getName());

Lo anterior funciona correctamente, pero también podríamos generar depenencias dinámicamente. Veamos primero la API de InyectionPoint:


Podemos usar esta API para generar una dependencia dinámica para Logger. Veamos un ejemplo:

Código: Java
  1. public class LoggerProducer {
  2.     @Produces
  3.     private Logger createLogger(InyectionPoint ip) {
  4.         return Logger.getLogger(ip.getMember().getDeclaringClass().getName());
  5.     }
  6. }

Perfecto. Ahora tan solo debemos inyectar al logger de ésta manera:

Código: Java
  1. @Inject Logger log;

¿Cómo ocurre la magia? Bien, el contenedor CDI buscará cualquier bean de tipo Logger listo para inyectar, y lo encuentra gracias a que hemos anotado el método con @Produces, lo que deja ese método listo para su inyección (como con @CDSerialPrefix y @RandomNumber del ejemplo anterior).




Trituradores (@Disposes)

Ahora sabemos como producir beans o valores para inyectar, pero algunos beans podrían necesitar ser cerrados, como es el caso de conexiones JDBC o un EntityManager. Por lo tanto, algunos beans producidos deberían asegurarse de ser cerrados. Aquí es donde toman importancia los Disposers. Los Disposers son lo  contrario a Producers, éste último crea beans o valores para inyectar, mientras que el primero se encarga de destruirlo.
Veamos un ejemplo con una clásica conexión JDBC:

Código: Java
  1. public class DerbyJdbcProducer {
  2.     private final static No tienes permisos para ver links. Registrate o Entra con tu cuenta DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
  3.     private final static No tienes permisos para ver links. Registrate o Entra con tu cuenta No tienes permisos para ver links. Registrate o Entra con tu cuenta = "jdbc:derby://localhost:1527/CDI";
  4.     private final static No tienes permisos para ver links. Registrate o Entra con tu cuenta USER = "duke";
  5.     private final static No tienes permisos para ver links. Registrate o Entra con tu cuenta PASS = "1234";
  6.     @Inject
  7.     private Logger logger;
  8.    
  9.     @Produces
  10.     public No tienes permisos para ver links. Registrate o Entra con tu cuenta createConnection() {
  11.         No tienes permisos para ver links. Registrate o Entra con tu cuenta con = null;
  12.         try {
  13.             Class.forName(DRIVER).newInstance();
  14.             con = No tienes permisos para ver links. Registrate o Entra con tu cuenta.getConnection(No tienes permisos para ver links. Registrate o Entra con tu cuenta, USER, PASS);
  15.             logger.log(Level.INFO, "Creando conexión JDBC con Derby");
  16.         } catch(No tienes permisos para ver links. Registrate o Entra con tu cuenta | No tienes permisos para ver links. Registrate o Entra con tu cuenta |
  17.                 No tienes permisos para ver links. Registrate o Entra con tu cuenta | No tienes permisos para ver links. Registrate o Entra con tu cuenta e) {
  18.             e.printStackTrace();
  19.         }
  20.         return con;
  21.     }
  22.     public void closeConnection(@Disposes No tienes permisos para ver links. Registrate o Entra con tu cuenta con) {
  23.         try {
  24.             con.close();
  25.             logger.log(Level.INFO, "Cerrando conexión JDBC con Derby");
  26.         } catch (No tienes permisos para ver links. Registrate o Entra con tu cuenta e) {
  27.             e.printStackTrace();
  28.         }
  29.     }
  30. }

Un método disposer debe obligatoriamente recibir un parámetro @Disposes y el tipo del parámetro debe ser el mismo que el tipo devuelto con el método productor (@Produces) y su calificador (@Qualifier) en caso haya más métodos productores con el mismo tipo de retorno. Dado que el ciclo de vida de un bean inyectado depende del contexto en donde se inyecta, cuando se elimina el bean que contiene el bean inyectado, éste se destruirá también. Pero si no hubiésemos escrito el método disposer, la conexión nunca se hubiese cerrado por ser ésta independiente del contexto. Cuando el bean que contiene el bean inyectado es eliminado, antes que eso ocurra, se ejecutará el método disposer del bean inyectado, en este caso, cerrando la conexión JDBC.



ANEXO

Alcances (scopes)

Todo objeto cuyo ciclo de vida es manejado por CDI tiene un alcance definido y un ciclo de vida que está ligado a un contexto específico. En Java, el alcance de un POJO es muy simple: tu creas una instancia de una clase usando la palabra reservada new y tu confías en el recolección de basura para deshacerse  de él y liberar alguna memoria. Con CDI, un bean es ligado al contexto y este lo mantiene en ese contexto hasta que el bean es destruido por el contenedor. No hay manera para remover manualmente un bean desde un contexto.

CDI define los siguientes alcances y permite puntos de extesión para crear tu propio alcance:

  • Application scope(@ApplicationScoped): Se extiende por la duración entera de una aplicación. El bean es creado solo una vez por duración de la aplicación y es descartado cuando la aplicación se apaga. Este alcance es util para cases utilitarias o helpers, u objetos que guardan datos compartidos por la aplicación entera (pero deberías ser cuidadoso con los problemas de concurrencia cuando los datos tienen que ser accesados por muchos threads).
  • Session scope(@SessionScoped): Se extiene a través de muchas peticiones HTTP o muchas invocaciones de métodos por una sola sesión de usuario. El bean es creado por la duración de una sesión HTTP y es descartado cuando la sesión termina. Este ámbito es para objectos que son necesarios a lo largo de la sesión como preferencias de usuario o credenciales de login.
  • Request scope(@RequestScoped): Corresponde a una simple petición HTTP o a una invocación de un método. El bean es creado para la duración de una invocación a un métod y es descartado cuando el método termina. Esto es usado para clases de servicio o beans JSF que son solo necesitados durante la duración de una petición HTTP.
  • Conversation scope(@ConversationScoped): Se extiende entre múltiples invocaciones dentro de los límites de una sesión con puntos de inicio y fin determinados por la aplicación. Este alcance es usado a través de múltiples páginas como parte de un flujo de trabajo de múltiplos pasos.
  • Dependent pseudo-scope(@Dependent): El ciclo de vida es el mismo que del cliente. Un bean @Dependent es creado cada vez que este es injectado y la referencia es eliminada cuando el objetivo de la inyección es eliminado. Éste es el alcance por defecto para CDI.


Como puedes ver, todos los alcances tienen una anotación que tu puedes usar en tus beans CDI (todas ellas están en el paquete javax.enterprise.context). Los primeros tres alcances son bien conocidos. Por ejemplo, si tu tienes un bean de alcance de sesión como de un carro de compras, el bean será automáticamente creado cuando la sesión inicia (primera vez que un usuario se loguea) y es  destruido automáticamente cuando la sesión termina.

Citar
Notas: El ciclo de vida de un bean @Dependent está ligado al ciclo de vida del bean que lo contiene. Se crea cuando el bean que lo contiene es instanciado y es eliminado cuando el mismo es eliminado. Es el scope por defecto.


Conversation (@ConversationScoped)

El alcande conversation es un poco diferente al resto. Este alcance mantiene estados asociados en un intérvalo de tiempo, a través de múltiples peticiones y es eliminado programáticamente (explícitamente) por la aplicación. Este alcance es usado mayormente en procesos  donde se requiere mantener valores durante múltiples peticiones HTTP pero sin extenderse demasiado, como un alcance @SessionScoped.

API de Conversation:


La principal diferencia con otros alcances, es que ellos son eliminados automáticamente por el contenedor al cabo de un tiempo cuando termina la petición, sesión, aplicación, etc. En Conversation, nosotros debemos encargarnos terminar su ciclo de vida. Por ejemplo, un bean @ConversationScoped puede ser usado para un asistence de registro de usuario. Deseamos mantener los datos que se van registrando del usuario a través de distintas páginas y que elimine el bean al terminar el registro:

Código: Java
  1. @Named
  2. @ConversationScoped
  3. public class UserRegistrationWizard implements No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  4.     @Inject
  5.     private Conversation conversation;
  6.     @Inject
  7.     private User user;
  8.     @Inject
  9.     private transient Logger logger;
  10.    
  11.     @PostConstruct
  12.     public void init() {
  13.         conversation.begin();
  14.     }
  15.     public User getUser() {
  16.         return user;
  17.     }
  18.     public void register() {
  19.         conversation.end();
  20.         logger.log(Level.INFO, "Usuario registrado:");
  21.         logger.log(Level.INFO, "Email: {0}", user.getEmail());
  22.         logger.log(Level.INFO, "Password: {0}", user.getPassword());
  23.         logger.log(Level.INFO, "Fecha Nacimiento: {0}", user.getBirthDate());
  24.     }
  25. }

Para probarlo, crea distintas páginas JSF y en cada una pon un formulario con un input text para el email, un input secret para el password y un input text para la fecha de cumpleaños:

start.xhtml

Código: HTML5
  1. <No tienes permisos para ver links. Registrate o Entra con tu cuenta>Inicio de registro</No tienes permisos para ver links. Registrate o Entra con tu cuenta>
  2.         <h:form>
  3.             <h:outputLabel for="email" value="Email:"/>
  4.             <h:inputText id="email" value="#{userRegistrationWizard.user.email}"/>
  5.             <h:commandButton value="Siguiente" action="step2"/>
  6.         </h:form>

step2.xhtml

Código: HTML5
  1. <No tienes permisos para ver links. Registrate o Entra con tu cuenta>Paso 2</No tienes permisos para ver links. Registrate o Entra con tu cuenta>
  2.         <h:form>
  3.             <h:outputLabel for="password" value="Contraseña"/>
  4.             <h:inputSecret id="password" value="#{userRegistrationWizard.user.password}"/>
  5.             <h:commandButton value="Siguiente" action="end"/>
  6.         </h:form>

end.xhtml

Código: HTML5
  1. <No tienes permisos para ver links. Registrate o Entra con tu cuenta>Paso 3</No tienes permisos para ver links. Registrate o Entra con tu cuenta>
  2.         <h:form>
  3.             <h:outputLabel for="birthDate" value="F. Nacimiento:"/>
  4.             <h:inputText id="birthDate" value="#{userRegistrationWizard.user.birthDate}">
  5.                 <f:convertDateTime pattern="dd-MM-yyyy"/>
  6.             </h:inputText>
  7.             <h:commandButton value="Registrar" action="#{userRegistrationWizard.register()}">
  8.                 <f:ajax execute="@form" />
  9.             </h:commandButton>
  10.         </h:form>

Al llegar a la últma página y pulsar el botón registrar, se ejecuta el método registrar(), donde termina la conversación y se imprime toda la información del usuario recolectada durante la misma.


Interceptores (@Interceptor)

Los interceptores te permiten agregar conceptos a través de tus beans. Cuando un cliente invoca a un método en un Managed Bean (y por lo tanto a CDI beans, EJB, RESTful, ...), el contenedor puede interceptar la invocación y procesar lógica de negocio antes que el método del bean sea invocado. Los interceptores vienen en cuatro tipos:

  • Interceptores a nivel de constructor: el interceptor es asociado con un constructor de la clase objetivo (@AroundConstruct)
  • Interceptores a nivel  de método: el interceptor es asociado con un método de negocio específico (@AroundInovke)
  • Interceptores por tiempo límite: el interceptor se interpone en el tiempo límite de los métodos (@AroundTimeout (usado solo con EJB timer)
  • Interceptores de ciclo de vida: el interceptor se interpone en las llamadas de eventos del ciclo de vida del objetivo (@PostConstruct y @PreDestroy).


Un método anotado con @AroundInvoke, debe tener el siguiete patrón de firma:

Código: Java
  1. @AroundInvoke
  2. No tienes permisos para ver links. Registrate o Entra con tu cuenta <Método> (InvocationContext ic) throws No tienes permisos para ver links. Registrate o Entra con tu cuenta;

Las siguientes reglas aplican a un método @AroundInvoke:

  • El método puede tener cualquier modificador de visibilidad pero no debe ser static o final.
  • El método debe tener un parámeto InvocationContext y debe retornar Object, el cual es el resultado del método objetivo invocado.
  • El método debe lanzar una excepción comprobada.
  • El objeto InvocationContext permite a los interceptores controlar el comportamiento de la cadena de invocaciones. Si muchos interceptores son encadenados, la misma instancia de InvocationContext es pasada a cada interceptor, quien puede agregar datos contextuales para ser procesador por otros interceptores.


Echememosle un ojo a la API de InvocationContext:


Otra forma de trabajar con interceptores, es que estos pueden estar en una clase y luego ser referenciada por la clase que los necesite:

Código: Java
  1. @Interceptors(MisInterceptores.class)
  2. public class MiClase { ... }

O en algún método:

Código: Java
  1. @Interceptors(MisInterceptores.class)
  2. public void doSomething() { ... }

Si queremos que un método no sea interceptado, lo anotamos con @ExcludeClassInterceptors.

Veamos un ejemplo:



Vamos con nuestro interceptor:

Código: Java
  1. public class LoginInterceptor {
  2.     private @Inject Logger logger;
  3.  
  4.     @AroundConstruct
  5.     private No tienes permisos para ver links. Registrate o Entra con tu cuenta init(InvocationContext ic) throws No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  6.         logger.log(Level.INFO, "--- ConstructInterceptor ---");
  7.         logger.log(Level.INFO, "Entrando al constructor");
  8.         try {
  9.             return ic.proceed();
  10.         } finally {
  11.             logger.log(Level.INFO, "Saliendo del constructor");
  12.         }
  13.     }
  14.     @AroundInvoke
  15.     private No tienes permisos para ver links. Registrate o Entra con tu cuenta interceptMethod(InvocationContext ic) throws No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  16.         logger.entering(ic.getTarget().toString(), ic.getMethod().getName());
  17.         try {
  18.             logger.log(Level.INFO, "método interceptado");
  19.             return ic.proceed();
  20.         } finally {
  21.             logger.exiting(ic.getTarget().toString(), ic.getMethod().getName());
  22.         }
  23.     }  
  24. }

Y nuestra clase que se verá afectada por nuestro inteceptor:

Código: Java
  1. @Interceptors(LoginInterceptor.class)
  2. public class LoginService {
  3.     private @Inject Logger logger;
  4.    
  5.     public void login() {
  6.         logger.log(Level.INFO, "Haciendo login");
  7.     }
  8. }

Cuando se instancie ésta clase y cuando llamemos al método login(), se ejecutarán los interceptores que hemos definido en LoginInterceptor, que son uno a nivel de método y otro a nivel de constructor.

Citar
También se pueden proporcionar más de un interceptor, deben separarse por comas:

Código: Java
  1. @Interceptors(LoginInterceptor.class, AccountcheckerInterceptor.class)
  2. public class LoginService { ... }

Los interceptores serán ejecutados en el orden que son escritos.


Vinculación de interceptores (@InterceptorBinding)

Una manera más práctica y que es la más aconsejada por su bajo acoplamiento, es Interceptor Binding. Es muy fácil de hacer, solo basta crear un Interceptor Binding que es muy parecido a un calificador:

Código: Java
  1. @InterceptorBinding
  2. @Retention(RUNTIME)
  3. @Target({METHOD, TYPE})
  4. public @interface Loggable { }

El interceptor cambia muy poco, solo debemos anotar con @Interceptor y el interceptor binding que hemos creado, en nuestro caso, @Loggable:

Código: Java
  1. @Interceptor
  2. @Loggable
  3. public class LoginInterceptorBinding { ... }

Ahora, en nuestra clase objetivo (donde se interceptarán los métodos), solo debemos anotar la clase o los métodos/constructor que queremos interceptar. Por ejemplo:

Código: Java
  1. public class LoginServiceBinding {
  2.     @Loggable
  3.     public void Login() {
  4.         ...
  5.     }
  6. }

Como se puede observar, es mucho más práctico de esta forma y además, más agradable a la vista. Para poder utilizar ésta manera, debemos de agregar nuestro interceptor binding en el descriptor de beans (beans.xml):

Código: XML
  1. <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  2.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  4.       http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
  5.       bean-discovery-mode="all">
  6.     <interceptors>
  7.         <class>com.acme.interceptors.binding.LoginInterceptorBinding</class>
  8.     </interceptors>
  9. </beans>

Prioridad de interceptores

Los interceptores sigue el orden en el que los hemos escrito en la clase interceptada, pero podemos crear nuestro propio orden. Por ejemplo, tenemos dos interceptores con prioridad diferente:

Código: Java
  1. @Interceptor
  2. @CdDisponibility
  3. @Priority(200)
  4. public class CdDisponibilityInterceptor {
  5.     @Inject
  6.     private Logger logger;
  7.    
  8.     @AroundInvoke
  9.     public No tienes permisos para ver links. Registrate o Entra con tu cuenta checkCdDisponibility(InvocationContext ic) throws No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  10.         try {
  11.             logger.log(Level.INFO, "Ejecuando método interceptor checkCdDisponibility");
  12.             return ic.proceed();
  13.         } finally {
  14.             logger.log(Level.INFO, "Terminando ejecución método interceptor checkCdDisponibility");
  15.         }
  16.     }
  17. }

Código: Java
  1. @Interceptor
  2. @DvdDisponibility
  3. @Priority(300)
  4. public class DvdDisponibilityInterceptor {
  5.     @Inject
  6.     private Logger logger;
  7.    
  8.     @AroundInvoke
  9.     public No tienes permisos para ver links. Registrate o Entra con tu cuenta checkDvdDisponibility(InvocationContext ic) throws No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  10.         try {
  11.             logger.log(Level.INFO, "Ejecutando método interceptor checkDvdDisponibility");
  12.             return ic.proceed();
  13.         } finally {
  14.             logger.log(Level.INFO, "Terminando ejecución del método interceptor checkDvdDisponibility");
  15.         }
  16.     }
  17. }

Ahora, nuestra clase interceptada:

Código: Java
  1. @CdDisponibility @DvdDisponibility
  2. public class SellService {
  3.     public void checkDisponibility() {
  4.         ...
  5.     }
  6. }

Si ejecutamos, veremos la siguiente salida:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
Información:   Ejecuando método interceptor checkCdDisponibility
Información:   Ejecutando método interceptor checkDvdDisponibility
Información:   Terminando ejecución del método interceptor checkDvdDisponibility
Información:   Terminando ejecución método interceptor checkCdDisponibility

Como vemos, el primer interceptor (menor prioridad) se ejecuta primero y el segundo (mayor prioridad), se ejecuta después de éste, pero el segundo termina primero. Luego que termina el interceptor de mayor prioridad, el flujo vuelve hacia el de menor prioridad.


Decoradores (@Decorator)

Decorador es un patrón de diseño muy común, originalmente acuñado por Gang of Four (la banda de los cuatro). La idea detrás de este patrón es tomar una clase y envolverla con otra (se dice que esta la decora). De esta manera, cuando tu llamas a una clase decorada, debes pasar a través del decorador antes que alcances la clase objetivo. Los decoradores son usados para añadir lógica a un método.

Tomemos como ejemplo un registro para una tienda de CDs online. Hay la membresía básica, que los permite acceder gratis a 15 CDs. Luego, cuando hay ofertas, viene el decorador que lo que hace es incrementar la cantidad de CDs a 40 (+25 del decorador).

interface Membership

Código: Java
  1. public interface Membership {
  2.     public int freeCDs();
  3. }

Implementación BasicMembership

Código: Java
  1. @Basic
  2. public class BasicMembership implements Membership {
  3.     @Inject
  4.     private Logger logger;
  5.    
  6.     @Override
  7.     public int freeCDs() {
  8.         logger.log(Level.INFO, "[+] Clase BasicMembership");
  9.         logger.log(Level.INFO, "Gratis 15 CDs");
  10.         return 15;
  11.     }
  12. }

@Basic es un calificador, que como ya sabemos sigue ésta anatomía:

Código: Java
  1. @Qualifier
  2. @Retention(RUNTIME)
  3. @Target({FIELD, TYPE, METHOD, PARAMETER})
  4. public @interface Basic { }

Ahora, veamos nuestro decorador:

Decorador MembershipGiftDecorator

Código: Java
  1. @Decorator
  2. public class MembershipGiftDecorator implements Membership {
  3.     @Inject @No tienes permisos para ver links. Registrate o Entra con tu cuenta @Basic
  4.     private Membership membership;
  5.     @Inject
  6.     private Logger logger;
  7.    
  8.     public int orderGift() {
  9.         logger.log(Level.INFO, "[+] Clase decoradora para Membership");
  10.         logger.log(Level.INFO, "Regalo momentáneo de 25 CDs");
  11.         return membership.freeCDs() + 25;
  12.     }
  13.     @Override
  14.     public int freeCDs() {
  15.         return orderGift();
  16.     }
  17. }

Lo primero que vemos, es que hemos anotado el decorador con @Decorator. Esta anotación le dirá al contenedor que es decorador. Un decorador debe implementar a la interface de la implementación que quiere decorar, es por eso que el decorador implementa también a Membership. Dentro del decorador, vemos que inyectamos la implementación BasicMempership (@Basic) y la anotamos con @Delegate para indicar que ese bean será decorado. A continuación, sobreescribimos el método de la interface y añadimos la lógica de negocio que deseamos. En nuestro caso, agregar 25 CDs gratis.

Por último hacemos uso del decorador:

Código: Java
  1. @Named
  2. public class MembershipBean {
  3.     @Inject @Basic
  4.     private Membership gift;
  5.    
  6.     public void orderGift() {
  7.         gift.freeCDs();
  8.     }
  9. }

Vemos que NO inyectamos al decorador, si no al bean normal (@Basic). El contenedor se encarga de añadir la lógica del decorador al método del bean. Si ejecutamos, vemos ésta salida:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
Información:   [+] Clase decoradora para Membership
Información:   Regalo momentáneo de 25 CDs
Información:   [+] Clase BasicMembership
Información:   Gratis 15 CDs

El orden es porque yo he ejecutado primero la lógica del decorador y al último del bean. Si hubiera sido al revés, el resultado hubiera sido invertido.
Los decoradores no están activados por defecto en CDI, así que debemos listarlos en el descriptor beans.xml:

Código: XML
  1. <decorators>
  2.         <class>com.acme.decorator.MembershipGiftDecorator</class>
  3. </decorators>

Eventos

Los eventos permiten a los beans interactuar con sus dependencias en tiempo de no-compilación. Cuando un bean define un evento, otro bean puede disparar el evento e incluso otro bean puede manejar el evento. Los beans pueden estar en paquetes y capas distintos dentro de la aplicación. El esquema que siguen éstos eventos es el observer/observable, de Gand of four.

Los productores de los eventos disparan los eventos usando la interface Event (javax.enterprise.event.Event). Un productor eleva eventos llamando el método fire(), pasando el objeto asociado al evento.

Veamos un ejemplo. En nuestra tienda online de CDs, cada vez que se quiera agregar un ítem al carro, deseamos disparar un evento CDI que envíe el ítem que se desee agregar al método observador addItem y reciba el ítem elegido por el usuario para que se agrege al carro.

Vamos con nuestro bean CDShopCart:

Código: Java
  1. @Named("cdShopCart")
  2. @RequestScoped
  3. public class CDShopCart {
  4.     @Inject
  5.     private Event<CD> cdAddedEvent;
  6.    
  7.     public void addItem2cart() {
  8.         List<String> songs = new ArrayList<>();
  9.         No tienes permisos para ver links. Registrate o Entra con tu cuenta.addAll(songs,
  10.                 "The day that never comes",
  11.                 "Enter Sandman",
  12.                 "The fourth horseman",
  13.                 "Whiskey in the jar");
  14.         CD cd = new CD("Metallica", songs);
  15.         cdAddedEvent.fire(cd);
  16.     }
  17. }

Este bean tendrá un alcande de petición (@RequestScoped), es decir que su ciclo de vida será igual al de una petición HTTP. Esto es así porque solo vamos a utilizar este bean para agregar un ítem (CD) al carro. Vemos que hemos definido una propiedad tipo Event que acepta datos tipo CD. Cuando se ejecute el método addItem2cart, se ejecutará el evento CDI y se enviará un bean CD para que sea procesado por el método observador, que lo define la siguiente clase:

Código: Java
  1. @SessionScoped
  2. public class Cart implements No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  3.     @Inject
  4.     private transient Logger logger;
  5.     private List<CD> items;
  6.    
  7.     @PostConstruct
  8.     public void init() {
  9.         items = new ArrayList<>();
  10.     }
  11.    
  12.     public void addItem(@Observes CD cd) {
  13.         logger.log(Level.INFO, "Nuevo CD añadido al carro");
  14.         logger.log(Level.INFO, "Autor: {0}", cd.getAuthor());
  15.         logger.log(Level.INFO, "Canciones:");
  16.         for(No tienes permisos para ver links. Registrate o Entra con tu cuenta song : cd.getSongs()) {
  17.             logger.log(Level.INFO, song);
  18.         }
  19.     }
  20. }

El método addItem() es el método observador. Este método escucha el evento y cuando se lanza lo captura y recibe el objeto asociado con el evento lanzado, en este caso CD. Es conveniente que la entidad CD sea pasivada (implemente Serializable). Además, vemos que el carro de compras está marcado como @SessionScoped ya que el carro de compras estará ligado siempre a la sesión de un usuario.

Para disparar el evento, podemos usar JSF, por ejemplo:

Código: HTML5
  1. <h:form>
  2.             <h:commandButton value="Agregar CD al carro" actionListener="#{cdShopCart.addItem2cart()}">
  3.                 <f:ajax execute="@form"/>
  4.             </h:commandButton>
  5.         </h:form>

Cuando se haga click en el botón, se ejecutará el método y se lanzará el evento. Si hacemos click, veremos lo siguiente en el log:

Código: No tienes permisos para ver links. Registrate o Entra con tu cuenta
Información:   Nuevo CD añadido al carro
Información:   Autor: Metallica
Información:   Canciones:
Información:   The day that never comes
Información:   Enter Sandman
Información:   The fourth horseman
Información:   Whiskey in the jar

Ahora, para hacer más interesante nuestro ejemplo, podemos crear calificadores llamados @Added y @Removed para identificar a cada evento: Agregar y Eliminar. Para esto, debemos hacer unos pequeños cambios:

Debemos cambiar el alcance de CDShopCart de @RequestScoped a @SessionScoped:

Código: Java
  1. @Named("cdShopCart")
  2. @SessionScoped
  3. public class CDShopCart implements No tienes permisos para ver links. Registrate o Entra con tu cuenta {
  4.     @Inject @Added
  5.     private Event<CD> cdAddedEvent;
  6.     @Inject @Removed
  7.     private Event<CD> cdRemovedEvent;
  8.     @Inject
  9.     private CD cd;
  10.    
  11.     public void addItem2cart() {
  12.         List<String> songs = new ArrayList<>();
  13.         No tienes permisos para ver links. Registrate o Entra con tu cuenta.addAll(songs,
  14.                 "The day that never comes",
  15.                 "Enter Sandman",
  16.                 "The fourth horseman",
  17.                 "Whiskey in the jar");
  18.         cd.setAuthor("Metallica");
  19.         cd.setSongs(songs);
  20.         cdAddedEvent.fire(cd);
  21.     }
  22.     public void removeItemFromCart() {
  23.         cdRemovedEvent.fire(cd);
  24.     }
  25. }

Los métodos observadores del carro, se verán así:

Código: Java
  1. public void addItem(@Observes @Added CD cd) {
  2.         logger.log(Level.INFO, "Nuevo CD añadido al carro");
  3.         logger.log(Level.INFO, "Autor: {0}", cd.getAuthor());
  4.         logger.log(Level.INFO, "Canciones:");
  5.         for(No tienes permisos para ver links. Registrate o Entra con tu cuenta song : cd.getSongs()) {
  6.             logger.log(Level.INFO, song);
  7.         }
  8.     }
  9.     public void removeItem(@Observes @Removed CD cd) {
  10.         logger.log(Level.INFO, "CD Removido");
  11.         logger.log(Level.INFO, "Autor: {0}", cd.getAuthor());
  12.     }

Prestar atención al calificador después de @Observes. Esto identifica al evento disparado, @Added representa un nuevo CD agreado y @Removed eliminará un CD del carro.

Nuestra web, se verá así:

Código: HTML5
  1. <h:form>
  2.             <h:commandButton value="Agregar CD al carro" action="#{cdShopCart.addItem2cart()}">
  3.                 <f:ajax execute="@form"/>
  4.             </h:commandButton>
  5.             <h:commandButton value="Eliminar CD del carro" action="#{cdShopCart.removeItemFromCart()}">
  6.                 <f:ajax execute="@form"/>
  7.             </h:commandButton>
  8.         </h:form>




Eso es todo lo que quería compatirles acerca de CDI. Es un tema muy importante y que pocos entienden y comprenden cómo funciona. Espero que el presente documento es ayuda a entender un poquito mejor cómo funciona CDI y sus nuevas características.

Nos vemos en otra oportunidad, hasta la próxima.
« Última modificación: Julio 24, 2015, 11:36:49 am por Gus Garsaky »

 

¿Te gustó el post? COMPARTILO!



SQLSentinel, SQL Injection Vulnerability Scanner

Iniciado por ZanGetsu

Respuestas: 2
Vistas: 1859
Último mensaje Junio 24, 2013, 10:18:19 am
por DLV
Jsql-injection

Iniciado por ZanGetsu

Respuestas: 4
Vistas: 2460
Último mensaje Junio 28, 2013, 05:48:45 am
por hdbreaker