‘manual 3.0’

1.9.2 Decoupling with application events

Friday, June 11th, 2010 Autor : gmateo

Código Fuente: Ejemplo 16

Para la programación orientada a eventos Spring provee las siguientes clases:
ApplicationEvent

Es cualquier tipo de evento, normalmente se extiende esta clase para definir los eventos específicos de la aplicación que se esté desarrollando.

ApplicationListener

Cualquier bean que desee ser notificado de un evento tiene que implementar esta interface.

Soporta el uso de "generics" de tal manera que podemos usar lo siguiente: ApplicationListener<EventoEspecifico> para ser notificados sólo cuando el evento "EventoEspecifico" sea lanzado. Si no se usa esto se notificará cada vez que se lance cualquier evento.

ApplicationEventPublisher

Es la clase necesaria para publicar los eventos.

Veamos el siguiente ejemplo:

Problema

Se desea que cada vez que la velocidad del carro supere un máximo el sistema de seguridad del carro active los frenos.

Solución

Se va a usar las siguientes clases:

CambioDeVelocidadEvento

Es el evento que se disparará cada vez que haya un cambio de velocidad.

public class CambioDeVelocidadEvento extends ApplicationEvent {
int velocidad = 0;
public CambioDeVelocidadEvento(Acelerador source) {
super(source);
velocidad = source.getVelocidadActual();
}
 

Acelerador

Esta clase es la que realiza el cambio de velocidades. Como se ve en el siguiente código; cada vez que se cambia la velocidad se publica el evento “CambioDeVelocidadEvento”

@Component
public class Acelerador {
int velocidadActual = 0;
@Autowired
ApplicationEventPublisher applicationEventPublisher;

public void setearVelocidad(int velocidad){
this.velocidadActual = velocidad;
applicationEventPublisher.publishEvent(new CambioDeVelocidadEvento(this));
}

Freno

Es la clase que se encargará de disminuir la velocidad del carro.

@Component
public class Freno {
public void frenar(){
System.out.println("Frenando...");
}
 

SistemaDeSeguridad

Esta clase implemente el “ApplicationListener” y lo tipifica con “CambioDeVelocidadEvento” de tal manera que este listener sólo será notificado cuando haya eventos de este tipo.

El método del listener es: “onApplicationEvent” ahí vemos que se verifica la velocidad actual y en base a eso se decide si frenar o no.

@Component
public class SistemaDeSeguridad implements ApplicationListener<CambioDeVelocidadEvento> {
private static final int VELOCIDAD_SEGURA_MAXIMA = 150;
@Autowired
Freno freno;

public void onApplicationEvent(CambioDeVelocidadEvento cambioDeVelocidadEvento) {
int velocidadActual = cambioDeVelocidadEvento.getVelocidad();
if (velocidadActual > VELOCIDAD_SEGURA_MAXIMA) {
System.out.println("La velocidad segura fue superada:" + velocidadActual);
freno.frenar();
} else {
System.out.println("Velocidad segura:" + velocidadActual);
}
}

A tener en Cuenta

Si queremos ser notificados de todos los eventos se debe implementar “ApplicationListener” sin usar “generics”. Como en el siguiente ejemplo:

@Component
public class AplicacionListener implements ApplicationListener{
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (applicationEvent instanceof ContextRefreshedEvent){
System.out.println("El ApplicationContext fue inicializado o refrescado");
}else if (applicationEvent instanceof ContextClosedEvent){
System.out.println("El ApplicationContext fue cerrado");
}else if (applicationEvent instanceof ContextStartedEvent){
System.out.println("El ApplicationContext fue inicializado");
}else if (applicationEvent instanceof ContextStoppedEvent){
System.out.println("El ApplicationContext fue detenido");
}else{
System.out.println("Otro evento fue lanzado:" + applicationEvent);
}
}

Ahora si deseamos tener cierta lógica que se ejecute al inicializarse el contenedor podríamos registrar un listener para ser notificados en caso de que se dé el evento “ContextRefreshedEvent”, y si queremos realizar cierta lógica antes de que el contenedor sea cerrado podríamos registrar otro listener para el evento: “ContextClosedEvent”.

1.9.1 FactoryBean

Friday, June 11th, 2010 Autor : gmateo

Código Fuente: Ejemplo 15

Spring provee la interface FactoryBean como un utilitario que nos permite implementar objetos que puedan servir como "factories". (Esto también se podría incluir en la sección 1.2.2 Beans – Agregando un bean al contenedor de Spring)

Estos beans se configuran como cualquier otro bean, sin embargo cuando se quiere acceder a ellos, Spring no devuelve el FactoryBean sino más bien invoca al método FactoryBean.getObject() y retorna el resultado de ese método. Para entender el concepto vamos a usar el siguiente ejemplo:

Problema

Se desea que de acuerdo al precio promedio del menú se establezca la carta de comidas adecuada a un Restaurante.

Solución

Tendremos las siguientes clases:

Restaurante

Abstrae el concepto del restaurante y tiene como atributo la clase CartaDeComidas.

@Component
public class Restaurante {
@Autowired
CartaDeComidas cartaDeComidas;

CartaDeComidas

Simboliza la lista de platos que brinda el restaurante.

public class CartaDeComidas {
private String titulo;

ProveedorDeCartaDeComidas

Esta clase hará el rol de "Factory" y será la encargada de proveer la instancia adecuada de: "CartaDeComidas" teniendo en cuenta el precio promedio.

@Component
public class ProveedorDeCartaDeComidas implements FactoryBean {
@Autowired
private Integer precioPromedio;

public Object getObject() throws Exception {
CartaDeComidas cartaDeComidas = new CartaDeComidas();
if (precioPromedio <= 5) {
cartaDeComidas.setTitulo("Menú Cómodo");
// se deberían hacer otros seteos para este tipo de menú
} else {
cartaDeComidas.setTitulo("Menú Caro");
// se deberían hacer otros seteos para este tipo de menú
}
return cartaDeComidas;
}

Acá lo importante es analizar el método “getObject” (interface FactoryBean), el cual en base al precio promedio seteará algunas propiedades de la cartaDeComidas y una vez inicializado con todos estos datos el objeto “cartaDeComidas” será seteado en el bean “restaurante”.

Se debe notar que el precio promedio también será inyectado por Spring.

precioPromedio

Es un bean de tipo Integer que tendrá el valor del precio promedio. Y este valor será inyectado al bean “ProveedorDeCartaDeComidas”

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
<context:component-scan base-package="com.aw.manual.ejemplo015"/>
<bean name="precioPromedio" class="java.lang.Integer">
<constructor-arg value="5"/>
</bean>
</beans>

Archivo: app-config.xml

1.9 Otros

Friday, June 11th, 2010 Autor : gmateo

En esta sección vamos a ver algunos puntos adicionales que no se incluyeron en las secciones anteriores.

1.8.2 BeanFactoryPostProcessor

Sunday, June 6th, 2010 Autor : gmateo

Código Fuente: Ejemplo 14

Para terminar de entender este concepto resolvamos el siguiente problema.

Problema

Se desea crear una anotación a nivel de método que le indique al contenedor que debe reemplazar el método que tiene la anotación por la implementación del MethodReplacer indicado como parámetro de la anotación.

Actualmente esto sólo se puede hacer usando configuración XML, como vimos en Method Replacement.

<bean id="sumadorAdulterado" class="com.aw.manual.ejemplo010.Sumador">
<replaced-method name="sumar" replacer="sumarAdulterado"/>
</bean>

Solución

Analizando el problema vemos que se necesita revisar las anotaciones del bean y en base a eso modificar la información que se tiene de la definición del bean. Entonces tendremos que implementar un BeanFactoryPostProcessor.

Definir la anotación

@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
public @interface ReplacedBy {
java.lang.String value() default "";
}

Definir el BeanFactoryPostProcessor

   1: @Component

   2: public class MethodReplacerDecoratorFactoryPostProcessor implements BeanFactoryPostProcessor {

   3:     private static final String REPLACED_BY_CLASS_NAME = ReplacedBy.class.getName();

   4:  

   5:     public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

   6:         String[] beanDefinitionNames = configurableListableBeanFactory.getBeanDefinitionNames();

   7:         for (String beanDefinitionName : beanDefinitionNames) {

   8:             BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanDefinitionName);

   9:             if (beanDefinition instanceof ScannedGenericBeanDefinition) {

  10:                 ScannedGenericBeanDefinition scannedBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;

  11:                 Set<MethodMetadata> methodsMetadata = scannedBeanDefinition.getMetadata().getAnnotatedMethods(REPLACED_BY_CLASS_NAME);

  12:                 if (methodsMetadata.size() > 0) {

  13:                     configureMethodsToBeOverridden(scannedBeanDefinition, methodsMetadata);

  14:                 }

  15:             }

  16:         }

  17:     }

  18:  

  19:     private void configureMethodsToBeOverridden(ScannedGenericBeanDefinition scannedBeanDefinition, Set<MethodMetadata> methodsMetadata) {

  20:         MethodOverrides mo = new MethodOverrides();

  21:         for (MethodMetadata methodMetadata : methodsMetadata) {

  22:             String methodName = methodMetadata.getMethodName();

  23:             Map<String, Object> attributes = methodMetadata.getAnnotationAttributes(REPLACED_BY_CLASS_NAME);

  24:             MethodOverride methodOverride = new ReplaceOverride(methodName, (String) attributes.get("value"));

  25:             mo.addOverride(methodOverride);

  26:         }

  27:         scannedBeanDefinition.setMethodOverrides(mo);

  28:     }

  29:  

  30: }

Línea 5-17 : Este es el único método de la interface. En esencia se hace lo siguiente :
Por cada BeanDefinition
    • Se obtiene todos los métodos que tengan la anotación @ReplacedBy.
    • Por cada uno de estos métodos se crea “MethodOverrides” y esto al final es lo que se setea al BeanDefinition.

Usar la anotación

@Component
public class Pintor {
@ReplacedBy("caminarReplacer")
public void caminar(){
}
}
Acá vemos al componente Pintor, del cual uno de sus métodos tiene la anotación @ReplacedBy, la cual tiene entre paréntesis el valor “caminarReplacer” que es el nombre del bean que debe implementar MethodReplacer.
@Component
public class CaminarReplacer implements MethodReplacer{
public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("Caminar Genérico");
return null;
}
}

Teniendo al bean “pintor” y “caminarReplacer” podemos decir que cada vez que se llame al método caminar del bean “pintor” se imprimirá en el out: “Caminar Genérico”, es decir se llamará al método “reimplement” del bean “CaminarReplacer”

1.8.1 BeanPostProcessor

Sunday, June 6th, 2010 Autor : gmateo

Código Fuente: Ejemplo 13

Para terminar de entender este concepto resolvamos el siguiente problema.

Problema

Se desea crear una anotación a nivel de atributo que le indique al contenedor que debe setear el nombre del bean en ese atributo. Actualmente para lograr esto no existe una anotación sino más bien se tiene que implementar la interface BeanNameAware.

Solución

Analizando el problema vemos que se necesita revisar las anotaciones del bean y setear alguno de sus valores entonces tendremos que implementar un BeanPostProcessor.

Definir la anotación

@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Documented
public @interface BeanName {
}

Definir el BeanPostProcessor

   1: @Component

   2: public class BeanNameSetterPostProcessor implements BeanPostProcessor {

   3:  

   4:     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

   5:         Field field = getFieldForBeanName(bean);

   6:         if (field != null) {

   7:             ReflectionUtils.makeAccessible(field);

   8:             ReflectionUtils.setField(field,bean,beanName);

   9:         }

  10:         return bean;

  11:     }

  12:  

  13:     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

  14:         return bean;

  15:     }

Clase: BeanNameSetterPostProcessor
Línea 4-11 : Acá implementamos el método que es llamado antes que se llame a los métodos de inicialización del bean. En esencia lo que hacemos es:
    • Obtener algún Field que tenga la anotación @BeanName.
    • Si existe, forzar que podamos setearlo y luego setearle el valor del nombre del bean.

Línea 13-15 : Es el otro método del BeanPostProcessor que se ejecuta luego de llamar a los métodos de inicialización del bean. Como ahí no necesitamos hacer nada, simplemente devolvemos el mismo bean.

Usar la anotación

@Component
public class Carro {
@BeanName
private String beanName;
}
 
Acá vemos cómo se puede usar esta anotación.

1.8 Post Processors

Sunday, June 6th, 2010 Autor : gmateo

Para facilitar el manejo de casos especiales Spring nos provee de interfaces "PostProcessor", las cuales al ser implementadas y declaradas como beans son tratadas de una manera especial.

Para entender mejor esto, vamos a definir los pasos que se dan al inicializar el contenedor, obviamente esto es una versión bastante simplificada:

Cargar la configuración de los beans

En esta etapa sólo se carga los archivos de configuración y en base a eso se crea las definiciones de todos los beans. Ningún bean es instanciado.

  • Cargar los archivos de configuración
  • Identificar los beans que implementen: BeanFactoryPostProcessor.
  • Llamar al método postProcessBeanFactory() de cada uno de los  BeanFactoryPostProcessor.

Instanciar e inicializar el bean

En esta etapa se usan las definiciones de los beans, cargados en la etapa anterior, y con esta información se procede a instanciar, setear todos los atributos e inicializar cada uno de los beans.

    • Crear las instancias de los beans.
    • Setear los valores simples y las referencias a los atributos del bean
    • Llamar a los métodos setter de las  Interfaces Aware
    • Pasar la instancia del bean al método postProcessBeforeInitialization() de cada uno de los BeanPostProcessor
    • Llamar a los métodos de inicialización del bean.
    • Pasar la instancia del bean al método postProcessAfterInitialization() de cada uno de los BeanPostProcessor
    • El bean está listo para ser usado.

Teniendo en cuenta lo anterior, vemos que si queremos modificar la definición de algunos beans bajo ciertas condiciones, lo que tenemos que hacer es implementar: BeanFactoryPostProcessor.

Y si queremos verificar, modificar o eliminar algún valor de los beans lo que tenemos que hacer es implementar: BeanPostProcessor.

Algunos ejemplos del propio Spring:

    • Para habilitar la funcionalidad del @Autowired se creó un BeanPostProcessor: AutowiredAnnotationBeanPostProcessor.
    • Para habilitar la funcionalidad de setear valores desde archivos properties se creo el BeanFactoryPostProcessor: PropertyPlaceholderConfigurer

1.7 Interfaces que indican al contenedor que se debe setear en el bean algunos componentes propios de Spring. (Interfaces Aware)

Sunday, June 6th, 2010 Autor : gmateo

Código Fuente: Ejemplo 12 

Esta interfaces nos permiten indicar al contenedor que los beans que las implementen necesitan tener una referencia a alguno de los componentes propios de Spring. Entre estas tenemos las siguientes:

BeanNameAware

Indica al contenedor que debe setear el nombre del bean

@Component
public class Puerta implements BeanNameAware {
private String beanName;
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}

Clase: Puerta

BeanFactoryAware

Indica al contenedor que debe setear el actual beanFactory en el bean que implemente esta interface.

@Component
public class Timon implements BeanFactoryAware{
BeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
Clase: Timon

ApplicationContextAware

Indica al contenedor que debe setear el ApplicationContext en el bean que implemente esta interface

@Component
public class Carro implements ApplicationContextAware{
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Clase: Carro

MessageSourceAware

Indica al contenedor que debe setear el MessageSource en el bean que implemente esta interface.

@Component
public class Freno implements MessageSourceAware{
MessageSource messageSource;
public void setMessageSource(MessageSource messageSource) {
this.messageSource=messageSource;
}
}

ApplicationEventPublisherAware

Indica al contenedor que debe setear el ApplicationEventPublisher en el bean que implemente esta interface. El ApplicationEventPublisher nos sirve para poder publicar distintos eventos dentro de la aplicación

@Component
public class Llanta implements ApplicationEventPublisherAware {
ApplicationEventPublisher applicationEventPublisher;
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}

ResourceLoaderAware

Indica al contenedor que debe setear el ResourceLoader en el bean que implemente esta interface. El ResourceLoader nos sirve para poder tener acceso a recursos externos como archivos y demás.

@Component
public class Motor implements ResourceLoaderAware {
ResourceLoader resourceLoader;
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}

A tener en cuenta

Desde la versión 2.5 podemos tener acceso a cualquiera de los componentes anteriores usando @Autowired, en este caso ya no es necesario implementar ninguna de las interfaces anteriores:

@Component
public class Ventana {
@Autowired
ApplicationContext applicationContext;
@Autowired
BeanFactory beanFactory;
@Autowired
ResourceLoader resourceLoader;
@Autowired
MessageSource messageSource;
@Autowired
ApplicationEventPublisher applicationEventPublisher;
Clase: Ventana
Cabe mencionar que si se tiene la referencia al ApplicationContext ya no es necesario tener las otras referencias, ya que con el ApplicationContext se tiene todo el soporte que los otros componentes brindan.

1.6 Initialization destruction callbacks

Wednesday, June 2nd, 2010 Autor : gmateo

Código Fuente: Ejemplo 11

Spring nos brinda este mecanismo para poder establecer qué método debe ser llamado luego de que el contenedor haya creado una instancia del bean; así como también qué método debe ser llamado antes de que el contenedor descarte el bean.

Existen varias maneras de indicar estos métodos, entre las cuales tenemos:

Implementando las interfaces InitializingBean, DisposableBean

@Component
public class Ventana implements InitializingBean, DisposableBean{
public void afterPropertiesSet() throws Exception {
System.out.println("Inicializando:"+this);
}

public void destroy() throws Exception {
System.out.println("Finalizando:"+this);
}
}
Clase: Ventana
 
Método de Inicialización:
“afterPropertiesSet” que es el método de la interface InitializingBean

Método de Destrucción:
“destroy” que es el método de la interface DisposableBean

Indicando los métodos de inicialización y destrucción en el archivo de configuración XML

 
<bean id="puerta" class="com.aw.manual.ejemplo011.Puerta"
init-method="inicializar" destroy-method="finalizar"/>
Archivo: app-config.xml
 
Método de Inicialización: “inicializar”
Método de Destrucción: “finalizar”

Usando las anotaciones @PostConstruct, @PreDestroy

 
@Component
public class Carro {
@PostConstruct
public void inicializar(){
System.out.println("Inicializando:"+this);
}
@PreDestroy
public void finalizar(){
System.out.println("Finalizando:"+this);
}
}
Clase: Carro
 
Método de Inicialización: “inicializar”
Método de Destrucción: “finalizar”

Apuntes a tener en cuenta

Para el caso de beans con scope=“prototype” el contenedor sólo llamará al método de inicialización, y este será llamado recién cuando se pida una referencia al bean.

@Component
@Scope("prototype")
public class Freno {
@PostConstruct
public void inicializar() {
System.out.println("Inicializando:"+this);
}
@PreDestroy
public void finalizar() {
System.out.println("Finalizando:"+this);
}
}
Clase: Freno
En este caso sólo se llama al método “inicializar”, el “finalizar” nunca es llamado por el contenedor.

1.5 Method injection

Wednesday, June 2nd, 2010 Autor : gmateo

Código Fuente: Ejemplo 10

Spring soporta 2 tipos de method injection:

Getter Injection (Lookup Method injection)

Soluciona los problemas encontrados cuando un bean de tipo singleton depende de otro bean que no es singleton.

Problema

Se necesita ejecutar distintas acciones, las cuales deben ser inicializadas con ciertos parámetros antes de ser ejecutadas.

Solución

Definición de la clase cuyo método será inyectado
   1: public abstract class AdministradorDeAcciones {

   2:     public void procesarAccion(Map accionParameters){

   3:         Accion accion = getAccion();

   4:         accion.setParameters(accionParameters);

   5:         accion.ejecutar();

   6:     }

   7:     public abstract Accion getAccion();

   8: }

Clase: AdministradorDeAcciones
 
Línea 2-6 : Este es el método principal, que se encarga de recibir los parámetros de la acción, pedir una instancia de la acción y finalmente ejecutar la acción.
Línea 7 : Acá se define el método que va a devolver la instancia de la acción. Se debe tener  en cuenta que este método es abstracto.
Configuración del bean indicando qué método debe ser inyectado
<bean id="administradorDeAcciones" class="com.aw.manual.ejemplo010.AdministradorDeAcciones">
<lookup-method name="getAccion" bean="accion"/>
</bean>
Archivo: app-config.xml

En este XML se está definiendo el bean “administradorDeAcciones” y se está indicando que cada vez que se llame al método “getAccion” se devuelva una referencia al bean “accion”.
Definición del bean que será devuelto cada vez que se llame al método “getAccion”
@Component
@Scope("prototype")
public class Accion {
Map accionParameters;
public void setParameters(Map accionParameters) {
this.accionParameters = accionParameters;
}
public void ejecutar() {
System.out.println("Ejecutando Acción:" + this);
}
}
Clase: Accion
Acá se está definiendo el bean “accion”.
Tener en cuenta que se debe setear scope=“prototype”, ya que sino todas las llamadas a “getAccion” del bean “administradorDeAcciones” devolverían la misma instancia de la clase Accion.

Method Replacement

Nos permite reemplazar la implementación de cualquier método del bean sin tener que cambiar el código fuente del bean.

Esto es posible ya que internamente se crea una subclase del bean. En esta subclase se sobrescribe el método a ser reemplazado y desde ahí se llama al bean que implementa la interface MethodReplacer. Veamos el siguiente caso:

Problema

Se necesita tener 2 sumadores, uno que sume adecuadamente y otro sumador adulterado que siempre agregue 1 a la suma verdadera.

Solución

Definición del bean “sumador”
@Component
public class Sumador {
public void sumar(int sum1, int sum2){
System.out.println("Sumandos:<"+sum1+","+sum2+"> Resultado:"+(sum1+sum2));
}
}
Clase: Sumador
Este bean tiene el método “sumar” que implementa la suma correcta.
Definición del bean “sumadorAdulterado”
<bean id="sumadorAdulterado" class="com.aw.manual.ejemplo010.Sumador">
<replaced-method name="sumar" replacer="sumarAdulterado"/>
</bean>

Archivo: app-config.xml

 
Acá estamos definiendo el bean “sumadorAdulterado”.
Notar que estamos usando la misma clase “Sumador”, pero además estamos indicando que las llamadas al método sumar deben ser atendidas por el método “reimplement” del bean “sumarAdulterado”. 
Definición del bean “sumarAdulterado”
@Component
public class SumarAdulterado implements MethodReplacer{
public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
Integer sum1 = (Integer) objects[0];
Integer sum2 = (Integer) objects[1];
System.out.println("Sumandos:<"+sum1+","+sum2+"> Resultado:"+(sum1+sum2+1));
return null;
}
}
Clase: SumarAdulterado
Este bean debe implementar la interface “MethodReplacer” ya que va a ser usado para reemplazar la llamada a un método de otro bean.
En el método “reimplement” de este bean se coloca la lógica de la suma adulterada.

1.4.1 Custom Scopes

Saturday, May 29th, 2010 Autor : gmateo

Código Fuente: Ejemplo 9

Desde la versión 2.0 ya se puede crear scopes personalizados.
Para esto se necesita hacer lo siguiente:
1) Crear y Registrar el Scope
   a)  Implementar la interface Scope.
   b) Registrar el nuevo scope, para que pueda ser usado por el     contenedor.

2) Definir qué beans van a tener este scope.
3) Definir el inicio y fin del scope:

Para mostrar todos estos pasos vamos resolver el siguiente problema:

Problema

Se necesita hacer entrevistas a distintos candidatos, en cada entrevista se debe validar las condiciones técnicas y psicológicas. Al final de cada entrevista se deben guardar los apuntes del entrevistador para su evaluación posterior.

Solución

Las clases principales serían:

AdministradorDeEntrevistas :
Se encargará de administrar las entrevistas. Entre sus funciones podemos mencionar:
    – Decidir a quien se entrevista
    – Definir el inicio y fin de la entrevista

Candidato :
Es la persona que será entrevistada

Entrevistador :
Será el encargado de realizar las entrevistas

CuadernoDeEvaluacionTecnica :
Se encargará de almacenar los apuntes que el entrevistador haga acerca del conocimiento técnico del candidato

CuadernoDeEvaluacionPsicologica :
Se encargará de almacenar los apuntes que el entrevistador haga acerca de las características psicológicas del candidato

EntrevistaScope :
Es el scope que se usará para demarcar el contexto de la entrevista. Se encargará de crear y almacenar los beans necesarios durante la entrevista.

Para este ejemplo tanto: CuadernoDeEvaluacionTecnica como CuadernoDeEvaluacionPsicologica, son beans que tienen scope="entrevista" y son atributos del bean "Entrevistador". Ambos serán creados cada vez que se inicialice el scope entrevista y serán destruidos cada vez que se finalice este scope.

Código Fuente

A continuación vamos a mostrar los 3 pasos indicados al inicio.

1) Crear y Registrar el Scope
a)  Implementar la interface Scope.
public class EntrevistaScope implements Scope {
private static Map<String, Cuaderno> cuadernosDeEntrevista = new ConcurrentHashMap();
private String id;

public Object get(String name, ObjectFactory objectFactory) {
if (!cuadernosDeEntrevista.containsKey(name)) {
Cuaderno obj = (Cuaderno) objectFactory.getObject();
cuadernosDeEntrevista.put(name, obj);
}
return cuadernosDeEntrevista.get(name);
}

public String getConversationId() {
return id;
}
Clase : EntrevistaScope
Acá vemos como esta clase implementa la interface Scope. Para el ejemplo sólo se ha implementado el método “get” y el “getConversationId”.
b) Registrar el nuevo scope, para que pueda ser usado por el contenedor.
<bean id="entrevistaScope" class="com.aw.manual.ejemplo9.EntrevistaScope"/>

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="entrevista">
<ref bean="entrevistaScope"/>
</entry>
</map>
</property>
</bean>
Archivo: app-config.xml
Debemos usar la clase CustomScopeConfigurer para indicarle al contenedor los custom scopes que se van a usar.
2) Definir qué beans van a tener este scope.
Usando anotaciones
@Component
@Scope(value = "entrevista", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CuadernoDeEvaluacionPsicologica extends Cuaderno {
Usando xml
<bean class="com.aw.manual.ejemplo9.CuadernoDeEvaluacionTecnica" scope="entrevista">
<aop:scoped-proxy/>
</bean>

Con lo anterior ya tenemos 2 beans con el scope=”entrevista”, ahora vamos a ver cómo son usados dentro de la clase Entrevistador.

@Component
public class Entrevistador {
@Autowired
private CuadernoDeEvaluacionTecnica cuadernoDeEvaluacionTecnica;
@Autowired
private CuadernoDeEvaluacionPsicologica cuadernoDeEvaluacionPsicologica;
Vemos que la clase Entrevistador no necesita saber nada del scope de sus atributos.
3) Definir el inicio y fin del scope:

Se define el inicio y fin del scope en la siguiente clase:

   1: @Component

   2: public class AdministradorDeEntrevistas {

   3:     @Autowired

   4:     EntrevistaScope entrevistaScope;

   5:     @Autowired

   6:     private Entrevistador entrevistador;

   7:     @Autowired

   8:     private ApplicationContext appCtx;

   9:  

  10:     public void realizarEntrevistas() {

  11:         Candidato primerCandidato = obtenerCandidato();

  12:         iniciarEntrevista(primerCandidato);

  13:         terminarEntrevista();

  14:  

  15:         Candidato segundoCandidato = obtenerCandidato();

  16:         iniciarEntrevista(segundoCandidato);

  17:         terminarEntrevista();

  18:     }

  19:  

  20:     private void iniciarEntrevista(Candidato candidato) {

  21:         entrevistaScope.inicializar();

  22:         entrevistador.entrevistar(candidato);

  23:     }

  24:  

  25:     private void terminarEntrevista() {

  26:         entrevistaScope.finalizar();

  27:     }

Clase: AdministradorDeEntrevistas
 
Línea 10 : Es el método principal que se encarga de realizar las entrevistas.
Línea 20 : Inicializa la entrevista.
Para esto se llevan a cabo 2 pasos:
a) Inicializar el scope
 
public class EntrevistaScope implements Scope {
private static Map<String, Cuaderno> cuadernosDeEntrevista = new ConcurrentHashMap();
private String id;

public void inicializar() {
System.out.println("Inicializar Scope");
id = "" + new Date().getTime();
}
El método inicializar sólo imprime “Inicializar Scope” y setea el id del scope.
 
b) El entrevistador comienza a hacer la entrevista.
 
@Component
public class Entrevistador {
@Autowired
private CuadernoDeEvaluacionTecnica cuadernoDeEvaluacionTecnica;
@Autowired
private CuadernoDeEvaluacionPsicologica cuadernoDeEvaluacionPsicologica;

public void entrevistar(Candidato candidato) {
String nombreCandidato = candidato.getNombre();
cuadernoDeEvaluacionTecnica.setNombreCandidato(nombreCandidato);
cuadernoDeEvaluacionTecnica.anotarObservaciones();
cuadernoDeEvaluacionPsicologica.setNombreCandidato(nombreCandidato);
cuadernoDeEvaluacionPsicologica.anotarObservaciones();
}
Acá debemos tener en cuenta que las instancias de ambos cuadernos ya sea el de evaluación técnica como el de psicológica son seteadas por el contenedor y su tiempo de vida es el tiempo que dure la entrevista. El scope = “entrevista” se encarga de indicar cuándo el contenedor debe proveer nuevas instancias de estos objetos. El programador de la clase Entrevistador no debe preocuparse por recrear estas instancias.
 
Línea 25 : Se termina la entrevista, en este caso sólo llamamos al método finalizar del scope, el cual graba la información contenida en los cuadernos actuales y limpia el repositorio de cuadernos, para que cuando se lleve a cabo la nueva entrevista se provea de nuevos cuadernos.
 
public class EntrevistaScope implements Scope {
private static Map<String, Cuaderno> cuadernosDeEntrevista = new ConcurrentHashMap();
private String id;

public void finalizar() {
System.out.println("Finalizar Scope");
grabarInformacionDeCuadernos();
cuadernosDeEntrevista = new ConcurrentHashMap();
}