Diferencia entre revisiones de «Inicializacion Lazy De Spring Para Tests»

De Dos Ideas.
Saltar a: navegación, buscar
 
(No se muestran 5 ediciones intermedias de otro usuario)
Línea 1: Línea 1:
Spring provee un mecanismo por el cual se le puede indicar que un bean sea consturído lo más tardíamente posbile.
+
[[Spring Framework]] provee un mecanismo por el cual se le puede indicar que un bean sea consturído lo más tardíamente posbile.  
  
De esta manera el bean no se instanciará hasta que no se necesite en el código en tiempo de ejecución en forma directa o porque es la dependencia de otro bean que es necesario.
+
De esta manera el bean no se instanciará hasta que no se necesite en el código en tiempo de ejecución en forma directa o porque es la dependencia de otro bean que es necesario.  
  
Se puede utilizar a nivel de bean:
+
Se puede utilizar a nivel de bean: <code xml="xml"><bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/></code>  
<code xml><bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/></code>
 
  
o a nivel del contenedor
+
o a nivel del contenedor <code xml="xml">
<code xml>
 
 
<beans default-lazy-init="true">
 
<beans default-lazy-init="true">
 
   ...
 
   ...
 
</beans>
 
</beans>
</code>
+
</code>  
  
Esto es muy útil durante los tests de componentes ya que nos permite en el test utilizar la configuración lo más similar a la que existirá en producción. Si marcamos el contenedor como lazy en el test de componentes no se instanciarán beans que no se usen directa o indirectamente durante el test, evitando que fallen porque pueden existir componentes que no estén listos, o que estén fallando pero que no participen en los tests, o porque tengan una dependencia con un entorno inexistente.
+
Esto es muy útil durante los tests de componentes ya que nos permite inicializar e involucrar en el test solamente los beans relacionados.  
  
El problema es que en la versión 2.5 de Spring (habria que verificar si en alguna sub versión posterior o en la 3.x esto fue cambiado) cuando importamos recursos para componer varios xml en uno sólo, la propiedad lazy no se propaga por los imports.
+
Durante los tests de componentes es conveniente utilizar los mismos archivos de configuración que se utilizarán en producción. Así se estará comprobando que la configuración de Spring es correcta.  
Esto hace que no sea posible hacer el contenedor lazy a menos que reemplacemos todos los archivos de configuración involucrados. Pero en este caso perderíamos la ventaja que buscábamos: tener los archivos de configuración lo más similar a los de producción.
 
  
Por ejemplo, si tuviésemos los siguientes archivos de configuración en el proyecto:
+
El problema que surge es que si se quiere indicar en la configuración que la incialización sea lazy se estará duplicando los archivos de configuración perdiendo esta posibilidad.
  
business
+
Imaginemos los siguientes archivos de configuración:
<code xml>
+
 
 +
app-business.xml <code xml="xml">
 
<beans>
 
<beans>
 
     ...
 
     ...
 
     <bean id="UnBo" ...>
 
     <bean id="UnBo" ...>
    ...
 
    <bean id="OtroBo" ...>
 
 
     ...
 
     ...
 
</beans>
 
</beans>
</code>
+
</code>  
  
dao
+
app-dao.xml <code xml="xml">
<code xml>
 
 
<beans>
 
<beans>
 
     ...
 
     ...
 
     <bean id="UnDao" ...>
 
     <bean id="UnDao" ...>
    ...
 
    <bean id="OtroDao" ...>
 
 
     ...
 
     ...
 
</beans>
 
</beans>
</code>
+
</code>  
  
jms
+
app-jms.xml <code xml="xml">
<code xml>
 
 
<beans>
 
<beans>
 
     ...
 
     ...
 
     <bean id="UnJmsTemplate" ...>
 
     <bean id="UnJmsTemplate" ...>
 
     ...
 
     ...
    <bean id="OtroJmsTemplate" ...>
+
</beans>
 +
</code>
 +
 
 +
Un test [[JUnit]] de componentes comenzaría con algo así:
 +
 
 +
<code java="java">
 +
@ContextConfiguration(locations =
 +
    "classpath:app-business.xml",
 +
    "classpath:app-dao.xml",
 +
    "classpath:app-jms.xml"
 +
    )
 +
public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {
 
     ...
 
     ...
</beans>
+
}
</code>
+
</code>
 +
 
 +
Si queremos que la inicialización sea lazy, cada uno de los archivos de configuración debería comenzar con: <code xml="xml">
 +
<beans default-lazy-init=true>
 +
</code>  
 +
 
 +
Esto implica que tengamos que tener archivos alternativos para los tests: <code java="java">
 +
@ContextConfiguration(locations =
 +
    "classpath:app-business-componente-test.xml",
 +
    "classpath:app-dao-componente-test.xml",
 +
    "classpath:app-jms-componente-test.xml"
 +
    )
 +
public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {
 +
    ...
 +
}
 +
</code>  
 +
 
 +
=== Una solución que no funciona ===
  
podríamos incluirlos todos en un archivo de configuración general para los tests
+
Es muy común intentar solucionar el problema de tener que duplicar los archivos de configuración importando los tres archivos en otro y hacer a este lazy:
  
app-spring-config-componente-test.xml
+
<br> app-spring-config-componente-test.xml <code xml="xml">
<code xml>
 
 
<beans default-lazy-init="true">
 
<beans default-lazy-init="true">
 
     <import resource="app-business.xml" />
 
     <import resource="app-business.xml" />
Línea 62: Línea 81:
 
     <import resource="app-jms.xml" />
 
     <import resource="app-jms.xml" />
 
</beans>
 
</beans>
</code>
+
</code>  
  
y lo usamos en un test de la siguiente manera:
+
Y después lo incluímos como un sólo archivo:  
  
<code java>
+
<code java="java">
  
 
@ContextConfiguration(locations =  
 
@ContextConfiguration(locations =  
Línea 74: Línea 93:
 
     ...
 
     ...
 
}
 
}
</code>
+
</code>  
  
Si funcionase como imaginamos, todos los beans (a menos que se haya indicado lo contrario), se inicilizarïan sólo si se usan durante el test en cuestión.
+
Lo que no funciona es que la propiedad default-lazy-init no se propaga por las configuraciones importadas (Verificar si esto sigue siendo así en versiones más nuevas de Spring).  
  
El problema es que el atributo default-lazy-init no se propaga a las configuraciones de business, dao y jms que están improtadas.
+
=== Solución  ===
  
Una solución para esto es definir en la anotación @ContextConfiguration un ContextLoader propio con un leve cambio en la forma en que levanta la configuración.
+
Una alternativa para hacer la inicialización lazy durante los tests sin duplicar los archivos de configuración es crear un ContextLoader propio y especificarlo en la anotación @ContextConfiguration.  
  
<code java>
+
El ContextLoader utilizará un DefinitionDocumentReader que le seteará a cualquier definición que lea, el atributo DEFAULT_LAZY_INIT_ATTRIBUTE como true.
 +
 
 +
<code java="java">
 
package com.tm.cma.web.base;
 
package com.tm.cma.web.base;
  
Línea 96: Línea 117:
 
public class LazyContextLoader extends GenericXmlContextLoader {
 
public class LazyContextLoader extends GenericXmlContextLoader {
  
     protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
+
     protected BeanDefinitionReader  
         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(context);
+
            createBeanDefinitionReader(GenericApplicationContext context) {
 +
         XmlBeanDefinitionReader beanDefinitionReader =  
 +
            new XmlBeanDefinitionReader(context);
 
         beanDefinitionReader.setDocumentReaderClass(LazyBeanDocumentReader.class);
 
         beanDefinitionReader.setDocumentReaderClass(LazyBeanDocumentReader.class);
 
         return beanDefinitionReader;
 
         return beanDefinitionReader;
Línea 106: Línea 129:
 
class LazyBeanDocumentReader extends DefaultBeanDefinitionDocumentReader {
 
class LazyBeanDocumentReader extends DefaultBeanDefinitionDocumentReader {
  
     protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
+
     protected BeanDefinitionParserDelegate  
         root.setAttribute(BeanDefinitionParserDelegate.DEFAULT_LAZY_INIT_ATTRIBUTE, "true");
+
            createHelper(XmlReaderContext readerContext, Element root) {
 +
         root.setAttribute(
 +
            BeanDefinitionParserDelegate.DEFAULT_LAZY_INIT_ATTRIBUTE,
 +
            "true"
 +
            );
 
         return super.createHelper(readerContext, root);
 
         return super.createHelper(readerContext, root);
 
     }
 
     }
  
</code>
+
</code>  
 +
 
 +
Y así quedaría el test en cuestión: <code java="java">
 +
 
 +
@ContextConfiguration(
 +
        loader = "LazyContextLoader.class"
 +
        locations = "classpath:app-spring-config-componente-test.xml"
 +
    )
 +
public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {
 +
    ...
 +
}
 +
</code>
 +
 
 +
[[Category:Spring_Framework]] [[Category:JUnit]]

Revisión actual del 13:10 8 oct 2009

Spring Framework provee un mecanismo por el cual se le puede indicar que un bean sea consturído lo más tardíamente posbile.

De esta manera el bean no se instanciará hasta que no se necesite en el código en tiempo de ejecución en forma directa o porque es la dependencia de otro bean que es necesario.

Se puede utilizar a nivel de bean: <bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>

o a nivel del contenedor <beans default-lazy-init="true">

  ...

</beans>

Esto es muy útil durante los tests de componentes ya que nos permite inicializar e involucrar en el test solamente los beans relacionados.

Durante los tests de componentes es conveniente utilizar los mismos archivos de configuración que se utilizarán en producción. Así se estará comprobando que la configuración de Spring es correcta.

El problema que surge es que si se quiere indicar en la configuración que la incialización sea lazy se estará duplicando los archivos de configuración perdiendo esta posibilidad.

Imaginemos los siguientes archivos de configuración:

app-business.xml <beans>

   ...
   <bean id="UnBo" ...>
   ...

</beans>

app-dao.xml <beans>

   ...
   <bean id="UnDao" ...>
   ...

</beans>

app-jms.xml <beans>

   ...
   <bean id="UnJmsTemplate" ...>
   ...

</beans>

Un test JUnit de componentes comenzaría con algo así:

@ContextConfiguration(locations =

   "classpath:app-business.xml",
   "classpath:app-dao.xml",
   "classpath:app-jms.xml"
   )

public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {

   ...

}

Si queremos que la inicialización sea lazy, cada uno de los archivos de configuración debería comenzar con: <beans default-lazy-init=true>

Esto implica que tengamos que tener archivos alternativos para los tests: @ContextConfiguration(locations =

   "classpath:app-business-componente-test.xml",
   "classpath:app-dao-componente-test.xml",
   "classpath:app-jms-componente-test.xml"
   )

public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {

   ...

}

Una solución que no funciona

Es muy común intentar solucionar el problema de tener que duplicar los archivos de configuración importando los tres archivos en otro y hacer a este lazy:


app-spring-config-componente-test.xml <beans default-lazy-init="true">

   <import resource="app-business.xml" />
   <import resource="app-dao.xml" />
   <import resource="app-jms.xml" />

</beans>

Y después lo incluímos como un sólo archivo:

@ContextConfiguration(locations =

   "classpath:app-spring-config-componente-test.xml"
   )

public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {

   ...

}

Lo que no funciona es que la propiedad default-lazy-init no se propaga por las configuraciones importadas (Verificar si esto sigue siendo así en versiones más nuevas de Spring).

Solución

Una alternativa para hacer la inicialización lazy durante los tests sin duplicar los archivos de configuración es crear un ContextLoader propio y especificarlo en la anotación @ContextConfiguration.

El ContextLoader utilizará un DefinitionDocumentReader que le seteará a cualquier definición que lea, el atributo DEFAULT_LAZY_INIT_ATTRIBUTE como true.

package com.tm.cma.web.base;

import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlReaderContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.test.context.support.GenericXmlContextLoader; import org.w3c.dom.Element;

public class LazyContextLoader extends GenericXmlContextLoader {

   protected BeanDefinitionReader 
           createBeanDefinitionReader(GenericApplicationContext context) {
       XmlBeanDefinitionReader beanDefinitionReader = 
           new XmlBeanDefinitionReader(context);
       beanDefinitionReader.setDocumentReaderClass(LazyBeanDocumentReader.class);
       return beanDefinitionReader;
   }

}

class LazyBeanDocumentReader extends DefaultBeanDefinitionDocumentReader {

   protected BeanDefinitionParserDelegate 
           createHelper(XmlReaderContext readerContext, Element root) {
       root.setAttribute(
           BeanDefinitionParserDelegate.DEFAULT_LAZY_INIT_ATTRIBUTE,
           "true"
           );
       return super.createHelper(readerContext, root);
   }

Y así quedaría el test en cuestión:

@ContextConfiguration(

       loader = "LazyContextLoader.class"
       locations = "classpath:app-spring-config-componente-test.xml"
   )

public class BlahComponenteTest extends AbstractJUnit4SpringContextTests {

   ...

}