Mi granito de java: Spring Core

jueves, 14 de julio de 2011

Spring Core

Continuando con el post anterior de introducción a Spring, que se puede ver en http://migranitodejava.blogspot.com/2011/06/introduccion-spring.html, vamos a profundizar algunos temas.

Creando Beans.

En Spring hay diversas formas de crear un bean. En el ejemplo de la introducción a Spring vimos una opción donde colocábamos la ruta del xml de configuración:
ApplicationContext respuesta =
new FileSystemXmlApplicationContext("C:/Users/max/eclipse workspace/spring.xml");


Esta forma de crear beans esta bien para un ejemplo o aplicaciones muy chicas, pero es poco práctico para el resto de los casos. Spring nos brinda formas alternativas a FileSystemXmlApplicationContext y las más utilizadas son:
ClassPathXmlApplicationContext: busca el xml en el classpath, para mi gusto es la mejor opción.
XmlWebApplicationContext: el xml se encuentra en un contexto de una aplicación web.

Ciclo de vida de un bean.

En Spring la vida de un objeto difiere de la forma tradicional: existen más oportunidades para aprovechar en la creación o destrucción de un bean. Las distintas etapas son:
  1. Instanciación del bean.
  2. Inyección de propiedades.
  3. Nombre del bean: si el bean implementa BeanNameAware, Spring le pasa el id en el método setBeanName(). De esta forma, sabe cual es su id según el xml de Spring.
  4. Contexto: si el bean implementa ApplicationContextAware, Spring llama al método setApplicationContext() y le pasa el contexto como parámetro.
  5. Post proccessor: si hay un BeanPostProccessor, Spring llama al método postProccessBeforeInitialization().
  6. Inicializar bean: si implementa InitializingBean, se llama a afterPropertiesSet().
  7. Post proccessor (de nuevo!): si hay un BeanPostProccessor, Spring también llama al método postProccessAfterInitialization().
  8. El bean esta listo para ser utilizado!
  9. Destrucción del bean: si implmenta DisposableBean se llama a destroy().

Y agregamos lo siguiente al xml:

<bean id="persona" class="ejemplo.Persona" >
     <property name="nombre" value="Maxi"/>
</bean>




Inyección por constructores.

Es posible instanciar objetos con un constructor distinto al vacío. Para ello existe un tag llamado <constructor-arg> que posee dos atributos importantes: ref y value. Por ejemplo, si tenemos un constructor que recibe un int podriamos hacer lo siguiente:
<constructor-arg value=”100” />
Pero si se recibe un objeto compuesto se deberá usar el atributo ref que hace referencia a otro bean de Spring y se lo llama por su id:
<constructor-arg ref=”persona” />

Veamos un ejemplo:
 Y el xml:
<bean id="persona" class="ejemplo.Persona" >
    <constructor-arg value="100" />
    <constructor-arg value="Perez" />
     <property name="nombre" value="Maxi"/>
</bean>




Entonces …¿conviene inyectar por constructor o por setter?

Es muy útil la inyección por constructor por varios motivos. Primero, a diferencia de la mayoría de frameworks que nos obligan a que casi todas nuestras clases sean pojos (y por lo tanto usen el constructor vacío), aquí esto no supone un problema y es Spring quien se adapta y nos da la libertad de trabajar como queremos. Por otro lado, obligamos a que, cuando se crea un objeto, se lo haga de la forma adecuada (con todas sus dependencias creadas si o si).
Por otro lado, inyectar por constructor puede hacer engorrosa la herencia y si el bean tiene muchas dependencias se vuelve cada vez más complicado.
Particularmente prefiero inyectar por setter, pero es bueno tener ambas posibilidades.

Inyectar beans exclusivos.

Hay situaciones donde un bean debería ser una dependencia de otro de manera exclusiva. El problema del archivo xml de Spring es que una vez declarado, el bean puede ser dependencia de cualquiera. Por ejemplo:

<bean id="liz" class="ejemplo.Persona" >
    <constructor-arg value="3" />
    <constructor-arg value="Juarez" />
     <property name="nombre" value="Liz"/>
     <property name="documento" ref="documentoLiz"/>
</bean>

<bean id="documentoLiz" class="ejemplo.Documento" >
    <property name="tipo" value="DNI"/>
     <property name="numero" value="123456789"/>
</bean>


Hasta aquí nada sobresaliente, simplemente hemos agregado a la clase Persona un objeto Documento y lo hemos vinculado para que funcionen en conjunto.




Pero hay un pequeño problema. ¿Que pasa si agregamos lo siguiente en el xml?
<bean id="leo" class="ejemplo.Persona" >
    <constructor-arg value="30" />
    <constructor-arg value="Gonzalez" />
     <property name="nombre" value="Leo"/>
     <property name="documento" ref="documentoLiz"/>
</bean>

Leo esta usando el documento de Liz!! Eso no es correcto y, por ello, Spring permite crear beans exclusivos de la siguiente manera:
<bean id="liz" class="ejemplo.Persona" >
    <constructor-arg value="3" />
    <constructor-arg value="Juarez" />
     <property name="nombre" value="Liz"/>
     <property name="documento">
              <bean class="ejemplo.Documento" >
                  <property name="tipo" value="DNI"/>
                  <property name="numero" value="123456789"/>
               </bean>
    </property>
</bean>
Observamos que Spring permite definir un bean dentro de una propiedad que no lleva el atributo id y, por lo tanto, no es posible referenciarlo de ninguna manera.
Hay que tener mucho cuidado al crear objetos de este tipo ya que estaremos impidiendo la reutilización del código.

Scope de un bean.

Por defecto, todos los beans de Spring son Singletons. Si no se conoce dicho patrón se puede leer en http://migranitodejava.blogspot.com/2011/05/singleton.html
Spring sólo crea una única instancia de cada objeto en su contenedor y luego la reutiliza. Todos sabemos que en ciertos casos esto no es conveniente, por lo que nos da la opción de cambiarlo. Para ello, en la etiqueta bean hay un atributo llamado scope que puede tomar los siguientes valores:
  • Singleton: es el valor default e implica una única instancia.
  • prototype: se instancia todas las veces que sea necesario.
  • request: válido en Spring MVC, donde es instanciado por cada petición HTTP.
  • session: válido en Spring MVC, donde es instanciado por cada usuario.
  • global-session: válido en Spring MVC, donde es instanciado en una session global HTTP.

Ojo si usan una versión anterior a Spring 2 que esto se trata de manera diferente.

Como se habrán dado cuenta, este singleton de Spring tiene una particularidad: no es realmente un Singleton. Es decir, Spring nos asegura que nos devolverá una única instancia pero nada impide que haga un new Object().
Muchas veces nosotros necesitamos controlar el Singleton para evitar problemas. Para ello Spring nos brinda una solución:

 Y en el xml definimos cual es el método encargado de crear el bean:
<bean id="fechaUtils" class="calendario.FechaUtils" factory-method="getInstance"/>
   


De esta forma, es posible adaptar a Spring para que instancie los objetos como nosotros le digamos. Este ya si es un Singleton propiamente dicho.

Autoconexión.

Vimos que es posible conectar las propiedades mediante un constructor o los métodos setters. Hasta aquí se hizo de manera explícita en el archivo xml. Pero se puede hacer que Spring resuelva esto por nosotros mediante el atributo autowire de cada bean. Hay 4 tipos de autoconexión:
  1. byName: es la más común, ya que conecta por ID (ver el ejemplo). Si no se encuentra un bean correspondiente la propiedad queda sin conectar.
  2. byType: busca un único bean en el contenedor cuyo tipo sea correspondiente con la propiedad a conectar. Si no se encuentra no conecta, pero si encuentra más de una lanza una UnsatisfiedDependenciException.
  3. constructor: busca uno o más beans del contenedor que correspondan con los parámetros del constructor del bean a conectar. Si no puede resolverlo lanza una UnsatisfiedDependenciException.
  4. autodetect: intenta primero conectar por constructor y luego byType.

Y en el xml:
<bean id="persona" class="ejemplo.Persona" scope="prototype" autowire="byName">
    <constructor-arg value="100" />
    <constructor-arg value="Perez" />
     <property name="nombre" value="Maxi"/>
</bean>
<bean id="domicilio" class="ejemplo.Domicilio" >
    <property name="calle" value="Av. Rivadavia"/>
     <property name="numero" value="948"/>
     <property name="localidad" value="Bs As"/>
</bean>



Autoconexión por defecto.

Por defecto, los beans no se conectan. Pero esto se puede cambiar en la etiqueta beans:
<beans default-autowire="byName">
...
</beans>
De esta forma, le estamos diciendo a Spring que conecte por byName por defecto, lo que se puede sobreescribir en cualquier etiqueta bean.

Colecciones.

Voy a asumir que ya todos conocen el concepto de List, Set, Map y las colecciones más importantes de Java, como por ejemplo Properties. Así que iré directamente a un ejemplo integrador donde se mostrará como configurar Spring para cada tipo de colección. De todas maneras, es muy intuitivo entenderlo.

List, set y arrays funcionan de manera similar. Cuando son array o list simplemente debo confgurar el xml de la siguiente manera (si son sets, solo hay que cambiar <list> por <set>):
<bean id="persona" class="ejemplo.Persona" >
  <property name="telefonos">
    <list>
          <ref="telefono1">
          <ref="telefono2">
          <ref="telefono3">
    </list>
  </property>
</bean>
Y un Map sería:
    <map>
          <entry key="clave1" value="valor1">
          <entry key="clave2" value="valor2">
    </map>

Tanto en la clave como en el valor es posible referenciar a otros beans en lugar de poner Strings. Para ello, en vez de key debemos colocar key-ref donde haremos referencia hacia el id de otro bean. Lo mismo ocurre en el valor: en vez de value debemos colocar value-ref.

En cuanto a los properties (clase java.util.Properties) se hace de la siguiente manera:

    <props>
          <prop key="clave1" >valor1</prop>
          <prop key="clave2">valor2</prop>
    </props>

Herencia entre beans.

Es posible utilizar herencia dentro de los beans declarados en el xml de Spring, lo cual nos puede permitir que nuestro archivo se reduzca en cuanto a la cantidad de líneas que tenemos que escribir. Para ello, el tag bean posee 2 atributos que, hasta ahora, no hemos utilizado. Estos son:
  • parent: indica quien es el padre (como un extends).
  • abstract: indica si es un bean abstracto que me sirve solo como modelo pero que nunca se va a instanciar. Toma valores true o false.
Por ejemplo, supongamos que en una empresa los empleados tienen, en su gran mayoría, el mismo domicilio y telefono laboral. Solo cambiar su interno.
<bean id="empleado" class="ejemplo.Empleado" abstract="true">
    <property name="domicilioLaboral" value="Av. Rivadavia 123"/>
     <property name="telefonoLaboral" value="49876648"/>
</bean>
Luego, cada vez que quiera crear un empleado solo debo hacer lo siguiente:
<bean id="juan" parent="empleado"/>

Si un empleado no tiene, por ejemplo, ese domicilio laboral, entonces se lo puede sobreescribir:
<bean id="sergio" parent="empleado">
        <property name="domicilioLaboral" value="Av. Cordoba 233"/>
</bean> 






Y el xml era así:
<bean id="saludoImpl" class="saludo.SaludoImpl">
      <property name="valor" value="Hola mundo!"/>
</bean>

<bean id="alumno" class="saludo.Alumno" >
      <property name="saludo" ref="saludoImpl"/>
</bean>


Ahora vamos a hacer un pequeño cambio en el xml y lo vamos a dejar así: 
<bean id="saludoImpl" class="saludo.SaludoImpl">
      <property name="valor" value="Hola mundo!"/>
    <replaced-method name="saluda" replacer="saludoAlternativo"/>
</bean>
<bean id="saludoAlternativo" class="saludo.SaludoAlternativo"/>

Y realizamos la implementación SaludoAlternativo:

Como se imaginarán hemos cambiado el "Hola mundo" por algo más actual. =)

2) Sustitución por getter: permite que los métodos sean sustituidos en tiempo de ejecución con nuevas implementaciones. En realidad es un caso especial de la sustitución anterior. Se suele utilizar en casos de métodos abstractos, aunque no es obligatorio.
Vamos a porner un ejemplo sencillo. Imaginemos que una clase llamada Reloj posee el siguiente método:
public abstract Tiempo getTiempo();


En el xml se podría escribir:
<bean id="reloj" class="ejemplo.Reloj">
    <lookup-method name="getTiempo" bean="tiempoImpl"/>
</bean>

De esta forma se sustituye lo que devuelve el método por la implementación de tiempoImpl. En la practica la sustitución por método no se suele utilizar demasiado ya que no son demasiados los casos donde se puede aplicar (¿para que voy a hacer un método que despues lo voy a cambiar no?), pero es bueno saber que existe.

Estas son las características principales del Core de Spring. Cuando tenga tiempo y ganas escribiré un post sobre Spring AOP.

5 comentarios:

Joss dijo...

Si me sirvio de mucho. Gracias!.
Dime cuando creas el contexto de Spring los objetos son creados o solo se tiene una base de codigo?
Es decir, recien se crean cuando hago getBean?

Max dijo...

Gracias Joss! Spring suele crear los objetos cuando esta levantando el contexto. Ojo que hay excepciones que trabajan de otra forma, por ejemplo cuando se usa el @Configurable.

JR dijo...

Muy buen post.
Gracias.

German dijo...

Max muy buena publicación, me sirvió de sobremanera!
Te felicito por el Blog, realmente ayuda a los que queremos iniciarnos en temas, y no queremos chocarnos con teoría muy técnica para arrancar.

Espero el blog siga creciendo y espero ansioso la segunda parte de hibernate, con temas avanzados!

Un gran abrazo y muchos éxitos!

Anónimo dijo...

Muy bueno! palabras sencillas y no muy extenso, me sirve para arrancar!