Como vimos en el primer tutorial de la serie, el core de Spring se basa en el concepto de Inversión de Control (IoC por sus siglas en inglés), más específicamente en la Inyección de Dependencias (DI). Este es un proceso en el cual los objetos definen sus dependencias (o sea, los otros objetos con los que trabajan) solo a través de los argumentos de su constructor, argumentos a un método de factory, o métodos setter que son invocados después de que el objeto se ha construido. En este caso en vez de ser el mismo objeto quien se encargue de instanciar, o localizar, las dependencias con las que trabaja (usando directamente su constructor o un localizador de servicios), es el contenedor el que inyecta estas dependencias cuando crea al bean. Este proceso, como podemos observar, es el inverso al que normalmente se hace, y de ahí el nombre de Inversión de Control (es el framework el que hace el trabajo, no el programador ^_^).
En este tutorial veremos como configurar Spring de 4 formas distintas para que realice este proceso de Inyección de Dependencias de forma automática
Spring coloca las clases básicas de su contenedor de IoC en dos paquetes:
Estos paquetes contienen dos interfaces que son las que realizan la "magia" de la instanciación de objetos. La primera es "org.springframework.beans.factory.BeanFactory", que proporciona un mecanismo de configuración avanzada capaz de manejar cualquier tipo de objeto. Además proporciona una sub-interface de esta última, "org.springframework.context.ApplicationContext", la cual agrega una integración más fácil con algunas de las características más interesantes de Spring, como su módulo de Programación Orientada a Aspectos, manejo de recursos de mensajes (para la internacionalización), publicación de eventos, y contextos específicos para ciertas capas de aplicaciones (como aplicaciones web), entre otras.
"BeanFactory" proporciona el framework de configuración y la funcionalidad básica, mientras que "ApplicationContext" agrega más funcionalidad específica para ciertos productos empresariales. Ambas interfaces representan contenedores de beans, solo que de tipos distintos.
"BeanFactory" representa las fábricas de beans y son el tipo de contenedor más simple. Este proporciona el soporte básico para DI. "ApplicationContext" representa... el contexto de la aplicación, estos están construidos con las mismas nociones de las fábricas de beans, pero proporcionan a la aplicación servicios adicionales.
En Spring, los objetos que forman la columna vertebral de las aplicaciones, y que son administradas por el contenedor de IoC de Spring, son llamados "Beans". Un bean es un objeto que es instanciado, ensamblado (cuando sus dependencias son inyectadas), y en general, administrado por el contenedor de IoC. Dicho de otra forma: un bean es simplemente uno de muchos objetos de nuestra aplicación. Los Beans, y las dependencias entre ellos, las declaramos en los metadatos del contenedor de IoC (lo cual puede ser mediante anotaciones o archivos de mapeo).
Como dije antes: la interface "ApplicationContext" representa un tipo de contenedor de IoC de Spring, y es el responsable de instanciar, configurar, y ensamblar los antes mencionados beans. El contenedor obtiene la información de qué objetos debe crear leyendo los metadatos de la configuración, los cuales, como dije antes, pueden ser colocados en archivos XML, anotaciones, o dentro del código Java.
Spring proporciona muchas implementaciones de "ApplicationContext". En particular en las aplicaciones que NO son web, usamos "org.springframework.context.support.ClassPathXmlApplicationContext", que busca los archivos de configuración dentro del classpath, o "org.springframework.context.support.FileSystemXmlApplicationContext", que los busca en el sistema de archivos de la máquina donde se ejecute la aplicación.
Para ejemplificar todo esto, haremos una aplicación la cual tendrá una clase de prueba que invocará a un "ServicioRemoto". Este servicio no hará algo especial, solamente regresará un número aleatorio entre 0 y 10; lo interesante de esto es que el cliente obtendrá una instancia del ServicioRemoto sin tener que crearla de manera directa, sino usando la DI de Spring.
Lo primero que haremos es crear un nuevo proyecto en NetBeans. Para esto vamos al Menú "File->New Project...". En la ventana que se abre seleccionamos la categoría "Java" y en el tipo de proyecto "Java Application". Le damos una ubicación y un nombre al proyecto, en mi caso será "SpringIoC". Nos aseguramos que las opciones "Create Main Class" y "Set as Main Project" estén habilitadas. Presionamos el botón "Finish" y veremos aparecer en el editor nuestra clase "Main".
Nota: Yo usaré dos proyectos, uno para usar archivos de mapeo y otro para usar anotaciones y que el código de ambos no se mezcle.
Agregamos la biblioteca de "Spring 3" que creamos en el primer tutorial de la serie. Hacemos clic derecho sobre el nodo "Libraries" del proyecto, en el menú que aparece elegimos la opción "Add Library..."
En la ventana que se abre seleccionamos la biblioteca "Spring 3":
Y presionamos el botón "Add Library". Con esto los archivos de la biblioteca deben agregarse a nuestro proyecto:
Ahora crearemos un nuevo paquete que contendrá a nuestros beans, este paquete se llamará "beans". Hacemos clic derecho en el paquete en el que se encuentra nuestra clase "Main" (que en mi caso es "ejemplos.spring.ioc") y en el menú que se abre seleccionamos la opción "New -> Java package..." y al nombre del paquete le agregamos "beans". Presionamos el botón "Finish" y con esto aparecerá nuestro nuevo paquete:
Crearemos nuestra clase "ServicioRemoto", dentro del paquete "beans". Esta clase será muy sencilla, y solo tendrá un método llamado "consultaDato" el cual no recibirá ningún parámetro y regresará un "int" aleatorio entre 1 y 10. La clase "ServicioRemoto" queda de esta forma:
Como ven es una clase sumamente sencilla, así que no hará falta explicar mucho.
Normalmente para obtener una nueva instancia de "ServicioRemoto" tendríamos que colocar la siguiente línea en alguna parte de la clase de prueba:
Como había dicho antes: el cliente no debería ser responsable de la instanciación de este ServicioRemoto, ya que es una tarea que no le corresponde. Gracias a la DI podremos evitarnos el escribir la línea anterior, y por lo tanto (como veremos un poco más adelante) muchos problemas potenciales que pueden surgir con ella. En Spring, los objetos no son responsables de encontrar o crear los otros objetos que necesitan para hacer su trabajo. En vez de eso, el contenedor les da las referencias de los objetos con los que colaborarán.
El acto de crear estas asociaciones entre objetos de la aplicación, usando DI, es llamado "wiring" (cableado en español). En el resto del tutorial veremos la configuración básica de este wiring con Spring, y veremos las posibilidades que este nos ofrece.
En las aplicaciones basadas en Spring, los objetos de la aplicación viven dentro del contenedor de beans. El contenedor se encarga de crear los objetos y "cablearlos", configurarlos, y administrar su ciclo de vida completo. Entender la forma en la que funciona este contenedor, nos permitirá comprender la forma en la que nuestros objetos serán manejados, y por lo tanto cómo podemos aprovecharlos mejor dentro de nuestra aplicación.
Estas definiciones de beans corresponden con los objetos que componen nuestra aplicación. Cuando creamos una aplicación, típicamente definimos una capa de servicios, una capa de acceso a datos (o DAOs) una capa de presentación, y una capa de infraestructura (con objetos como el "SessionFactory" de Hibernate). En Spring normalmente declaramos objetos que definen estas 4 capas (y no objetos muy específicos del dominio), ya que son las capas anteriores quienes se encargan de crear y manejar los objetos del dominio.
Crearemos el archivo de configuración en nuestro proyecto, así que hacemos clic derecho sobre el nodo "Source Packages" de nuestro proyecto, y en el menú contextual que se abre seleccionamos "new -> Other...":
En la ventana que se abre, seleccionamos la categoría "Other" y como tipo de archivo "Spring XML Configuration File":
Le damos un nombre al archivo, en mi caso será "applicationContext". Hacemos clic en el botón "Next". En la siguiente pantalla debemos seleccionar los namespaces que queremos que tenga nuestro archivo. No seleccionaremos ninguno. Más adelante en este mismo tutorial explicaré qué es esto. Presionamos el botón "Finish" y con eso aparecerá en nuestro editor el archivo "applicationContext.xml" con el siguiente contenido:
El archivo contiene únicamente el elemento raíz "<beans>" dentro del cual declararemos cada uno de nuestros beans usando un elemento "<bean>" para cada uno ^_^.
Lo siguiente que haremos es declarar nuestro bean "ServicioRemoto" para que el contenedor de Spring lo administre. Para esto declaramos un elemento "<bean>" en nuestro archivo de configuración:
El elemento "<bean>" es la unidad de configuración más básica en Spring, le dice que cree y administre un objeto.
Debemos darle un identificador a cada uno de nuestros beans dentro de la aplicación; esto no es obligatorio pero si no lo hacemos no podremos distinguir a los beans con los que trabajamos (normalmente NO especificamos un identificador cuando los usamos como beans internos). Estos deben estar identificados de manera única dentro del contenedor que tiene al bean. Un bean por lo regular solo tiene un identificador (aunque hay formas de colocarle más en caso de ser necesario). En esta configuración basada en XML usamos el atributo "id" o el atributo "name" para especificar el identificador del bean. El atributo "id" nos permite especificar exactamente un identificador, esto es porque hace uso del elemento "id" real de la especificación XML, lo que nos permite algunas validaciones extra con parsers normales de XML, sin embargo nos limita al uso de caracteres que son legales en XML para definir este identificador. Si necesitamos usar algún carácter especial como identificador, o si queremos indicar algún alias para nuestro bean, podemos usar el atributo "name", separando los alias con una coma (,), un punto y coma (;), o un espacio en blanco.
Para este bean, usaremos el identificador "servicioRemoto":
Ahora nuestro bean puede ser referenciado por el nombre de "servicioRemoto".
El último paso que debemos hacer en nuestro archivo de configuración es decirle a Spring de qué tipo es el bean, esto es, la clase de la que queremos que cree los objetos. Para hacer esto usamos el atributo "class". En este caso el bean será de tipo "ejemplos.spring.ioc.beans.ServicioRemoto". Por lo que la configuración de nuestro bean queda de la siguiente forma:
Ahora que tenemos nuestro archivo de configuración listo ha llegado el momento tan esperado, ahora usaremos uno de los contenedores de Spring para crear nuestros beans. Como había dicho, hay dos tipos de contenedores los "BeanFactory" y los "ApplicationContext". Veremos cómo usar estos dos tipos de contenedores.
BeanFactory hace más que solo crear objetos. Como un BeanFactory conoce a los objetos que componen nuestra aplicación (los declarados en el archivo de configuración), es capaz de crear las asociaciones entre estos objetos en el momento de ser instanciados. Además, como el BeanFactory tiene el control del ciclo de vida del bean, puede hacer llamadas a métodos personalizados de inicialización y destrucción (si estos métodos están definidos).
Hay muchas implementaciones de la interface "BeanFactory" (supuestamente, porque yo solo conozco una). La más usada (y la única que conozco) es "org.springframework.beans.factory.xml.XmlBeanFactory", el cual carga los beans basándose en la configuración del archivo XML que creamos hace un momento.
Para crear un "XmlBeanFactory" debemos indicar de dónde se leerá este XML, ya que podemos obtenerlo de un stream, de un archivo en nuestro classpath, hasta de un arreglo de bytes. Para esto usamos una instancia de "org.springframework.core.io.Resource".
Spring proporciona varias implementaciones de esta interface:
Nosotros usaremos "ClassPathResource" para cargar nuestra configuración, ya que el archivo "applicationContext.xml" que creamos está dentro del classpath de nuestra aplicación (más específicamente en la raíz de la misma). A este "ClassPathResource" le pasamos una cadena que contiene la ruta (dentro del classpath) de nuestro archivo de configuración, de esta forma:
Ahora que tenemos esta "BeanFactory" ya podremos obtener nuestro bean.
Como una nota importante hay que decir que en el momento en el que creamos nuestro BeanFactory, este lee las definiciones de los beans del archivo indicado, sin embargo los beans NO son creados en ese momento. Los beans se crean hasta el momento que son necesitados (cuando los pedimos en nuestro código).
Ahora, con nuestro "BeanFactory" podemos obtener un bean haciendo uso del identificador que le dimos en el archivo XML, en este caso "servicioRemoto". Para esto usamos el método "getBean" del BeanFactory. A este método podemos (opcionalmente) indicarle la clase que esperamos que devuelva el método (y con esto ahorrarnos un cast ^_^).
En el momento en el que invocamos a "getBean", la fabrica instanciará el bean y establecerá los valores de sus propiedades usando DI. Es aquí cuando comienza el ciclo de vida de un bean en el contenedor de Spring. Veremos este ciclo de vida más adelante.
Ahora que ya tenemos una instancia de "ServicioRemoto" podemos invocar sus métodos como lo hacemos normalmente:
Todo esto lo hacemos dentro de nuestro método "main", que queda de esta forma:
Si ejecutamos nuestra aplicación obtenemos la siguiente salida:
Como podemos ver, obtenemos un mensaje que indica que las definiciones de beans del archivo "applicationContext.xml". Además obtenemos el valor que está regresando nuestro servicio remoto (en mi caso "7").
Todo esto lo logramos sin hacer ni un solo "new". Este ejemplo aunque es muy simple (por no decir tonto ^_^) nos muestra las bases de Spring. Un poco más adelante veremos, en este mismo tutorial, dónde está la verdadera ventaja de Spring (solo confíen en mi y continúen leyendo).
Ahora veremos cómo hacer esto mismo usando un "ApplicationContext".
Usar "ApplicationContext" es similar a usar "BeanFactory". Ambos cargan definiciones de beans, cablean los beans, y los envían a quienes se los piden. Pero "ApplicationContext" ofrece más cosas:
Debido a que "ApplicationContext" proporciona más funcionalidades, es preferible el uso de este sobre "BeanFactory" en casi todo tipo de aplicaciones, la única excepción es cuando estamos ejecutando una aplicación donde los recursos son escasos, como en un dispositivo móvil.
Aunque hay muchas implementaciones de "ApplicationContext" (de esta si =) ), hay 3 que son las más usadas comúnmente:
Hablaremos de "XmlWebApplicationContext" cuando hablemos de aplicaciones web en uno de los siguientes tutoriales.
En este caso usaremos "ClassPathXmlApplicationContext". En este caso el constructor recibe una cadena (o cadenas) indicando la ubicación de este archivo (o archivos):
Con este ApplicationContext creado ahora podemos recuperar beans de la misma forma que lo hicimos cuando usamos el "BeanFactory", usando el método "getBean", ya que "ApplicationContext" es una interface que extiende de "BeanFactory":
Además de la funcionalidad extra, mencionada hace un momento, existe otra diferencia entre "ApplicationContext" y "BeanFactory", y es la manera en la que cargan los beans. Hay que decir que por default todos los beans manejados por Spring son Singletons, esto es, solo existe una instancia de ellos por aplicación. Como dije antes: "BeanFactory" crea los beans al momento que estos son pedidos (con la invocación de "getBean"). "ApplicationContext" trabaja de una forma un poco mejor, ya carga todos los singletons cuando se inicia el contexto, y de esta forma se asegura de que estarán listos en el momento en el que sean solicitados.
Ahora que tenemos nuestra instancia de "ServicioRemoto", proporcionada por Spring, podemos hacer uso de sus métodos de forma normal:
Si ejecutamos nuestra aplicación, obtendremos una salida similar a esta:
Como podemos ver, hacer el cambio de "BeanFactory" a "ApplicationContext" nos llevó solamente una línea. Y la configuración en el archivo XML también es bastante pequeña.
Cuando el contenido de nuestro archivo de configuración comience a crecer podemos separar nuestra configuración en varios archivos (por ejemplo, uno por cada capa de nuestra aplicación) y después hacer referencia a estos archivos en un archivo "maestro" usando el elemento "<import>", por ejemplo, en nuestro archivo "applicationContext.xml" podríamos tener:
Esto también puede ayudarnos cuando queramos organizar mejor los beans de nuestra aplicación para agruparlos de alguna manera, y seguir cargando solo un archivo XML de configuración.
Existe una alternativa para el uso de estos archivos XML, y es realizar la configuración a través de las anotaciones que nos proporciona Spring, que es lo que veremos a continuación.
Spring proporciona una serie (relativamente grande) de anotaciones con las cuales podemos indicar exactamente cómo queremos que maneje la DI de cada uno de los componentes. Existen en realidad varias formas de manejar estas anotaciones, pero en este momento solo nos centraremos en anotar nuestros beans. Para esto existen tres anotaciones básicas, las cuales se conocen como "estereotipos":
Las tres anotaciones anteriores extienden de "@Component", la cual indica que la clase es un componente, y por lo tanto son candidatas para ser auto-detectadas cuando usamos una configuración basada en anotaciones.
Veremos dos formas de configuración con anotaciones, en la primera (irónicamente) usaremos un archivo XML para indicar dónde están nuestras clases anotadas. En la segunda forma veremos cómo hacer esto mismo, pero sin necesidad de el archivo XML.
Ahora agregaremos un namespace especial a este archivo de configuración que hace referencia a los elementos de configuración del contexto de Spring. En XML un namespace es una forma que se usa para proporcionar elementos con nombres únicos. Spring utiliza estos namespaces para separar conjuntos de etiquetas que están relacionadas y que ofrecen ciertas funcionalidades especiales. Además esto hace que leer y escribir los archivos de configuración sea más fácil. Algunas veces escucharán que se refieren a estos namespaces como "esquemas"
Cada uno de estos namespaces hace referencia a un esquema XML distinto. Así por ejemplo tenemos un namespace para programación orientada a aspectos ("aop"), otro para manejo de configuración de elementos jee ("jee"), otro para manejo de mensajes ("jme"), otro para transacciones ("tx"), etc.
El namespace "context" se encarga de la configuración del "ApplicationContext". Para indicar que haremos uso de este namespace, solo lo agregamos la siguiente línea dentro del elemento "beans" de archivo "applicationContext.xml":
Dejándolo de la siguiente forma:
Ahora indicaremos, usando el namespace "context", que queremos activar la configuración de Spring para que detecte las anotaciones (propias de Spring) en las clases. Para eso usamos el elemento "annotation-config":
Ahora debemos indicar en dónde (en qué paquetes) se encuentran las clases que hemos anotado. Si no indicamos este paquete Spring no buscará de forma automática las clases (no sé realmente por qué). Usamos el elemento "component-scan" para indicar, mediante su elemento "base-package", el paquete raíz en el que se encuentran nuestros beans anotados. Para este ejemplo el paquete raíz es "ejemplos.spring.ioc.beans". Por lo que el elemento queda de la siguiente forma:
Y el archivo de configuración así:
Como vemos no hemos definido ni un solo bean en el archivo, así que ahora deberemos indicarlo directamente en las clases que se convertirán en nuestros beans (que en ese caso solo es "ServicioRemoto". Para indicar esto usaremos las anotaciones que mencioné antes ("@Repository", "@Service", "@Controller"). Como "ServicioRemoto" pertenece a la capa de servicios, lo mercaremos con la anotación "@Service", de esta forma:
Si recuerdan, hacemos referencia a los beans mediante su identificador. Pero "ServicioRemoto" no tiene indicado ninguno. Spring asigna un nombre por default a sus beans, que es el nombre de la case pero con la primer letra en minúscula. Así que en este caso el nombre del bean será "servicioRemoto". Si quisiéramos darle a nuestro bean un nombre distinto podemos hacerlo mediante el atributo "value" de la anotación "@Service", de la siguiente forma:
Por lo que nuestra clase "ServicioRemoto" queda de la siguiente forma:
No hay que hacerle ningún cambio al código de la clase "Main". Seguimos obteniendo una instancia de "ApplicationContext" (o de "BeanFactory") y le indicamos la ubicación del archivo que contiene la configuración de Spring, que en este caso es "applicationContext.xml". De la misma forma seguimos obteniendo el bean "servicioRemoto" invocando al método "getBean" del objeto "ApplicationContext". Finalmente llamamos al método "consultaDato" del bean "servicioRemoto". Nuestro Método "main" queda de la siguiente forma:
Podemos ver que ahora hay más información en la consola pero que finalmente nuestro código se ejecuta y obtenemos la salida esperada. Por lo que nuestra aplicación ha quedado configurada de forma adecuada ^_^.
Como vemos, usando anotaciones, nuestro archivo de configuración ha quedado aún más pequeño. Sin embargo, aún seguimos dependiendo de un archivo XML para que nuestra aplicación funcione correctamente. Ahora veremos cómo configurar nuestra aplicación, pero sin depender de ningún XML.
Para esta configuración, el único cambio necesario es en el código que crea el objeto "ApplicationContext" (en el método "main"). En este caso necesitamos un objeto especial ya que, como no existe un archivo XML, debemos indicar cuáles son las clases anotadas (o el paquete que las contiene).
El objeto "especial" que necesitamos crear es una implementación de "ApplicationContext" (y por lo tanto de "BeanFactory"), la clase "AnnotationConfigApplicationContext". Esta clase tiene dos constructores que nos interesan; el primero recibe una lista, separada por comas, de las clases que tenemos anotadas con @Component (o alguna de sus subclases), como es el caso de "ServicioRemoto". Por lo que crearíamos un nuevo objeto de esta clase de la siguiente manera:
Esto es muy cómodo si solo tenemos dos o tres clases anotadas, pero ¿qué pasa en el caso de que tengamos más? Bueno, en este caso podemos usar el segundo constructor de "AnnotationConfigApplicationContext" en el cual indicamos el paquete (o paquetes) raíz en el que se encuentran las clases anotadas, como en el caso del elemento "context:component-scan" del archivo XML, de la siguiente forma:
Con este cambio nuestro método "main" queda de la siguiente forma:
Si ejecutamos nuestra aplicación veremos la siguiente salida en la consola:
Que como podemos observar indica que todo quedó configurado correctamente ^_^.
De esta forma podemos declarar todas las dependencias y la configuración (casi toda) de nuestros beans directamente en las clases. Esta manera de configuración de beans, aunque es más cómoda y fácil para nosotros como desarrolladores al momento de estar escribiendo el código, es mucho menos clara y (para ser sinceros) no nos permite usar todo el poder que Spring nos proporciona. Es por esto que en estos tutoriales nos centraremos más en cómo hacer las cosas usando el archivo de configuración, haciendo las respectivas indicaciones para hacer lo mismo con anotaciones (en caso de que sea posible hacerlo).
Ahora que hemos visto 4 formas distintas de configurar el contenedor de beans de Spring para indicarle cuales son los beans que debe manejar, veremos algunos detalles un poco más interesantes de este framework con respecto a la inyección de dependencias.
Cuando usamos el mecanismo de DI de Spring, el código es más limpio, y está desacoplado de una forma más efectiva. El objeto no busca sus dependencias, de hecho ni siquiera conoce la ubicación o clase de su dependencia. Por esta misma razón, las clases se hacen más fáciles de probar, en particular cuando las dependencias las definimos como interfaces o clases abstractas, ya que podemos usar objetos mock (que veremos en algún otro tutorial sobre pruebas) para realizar pruebas.
En Spring, la DI existe en dos formas principales:
Lo primero que haremos es crear una nueva interface llamada "Proceso". Esta interface representará... bueno el proceso que se realizará dentro del servicio remoto. Tendrá un método llamado "ejecuta()", que será el que se encargará de realizar el proceso. Colocamos esta interface en el paquete de nuestros beans. "Proceso" queda de la siguiente forma:
Como vemos, "ejecuta" regresa un "Object". Este irá cambiando de acuerdo a las implementaciones que realicemos de esta interface, de las cuales por cierto haremos 3. La primer implementación será la clase "Calculo". La cual solo regresará un número al azar entre 0 y 100 (que es lo que ya actualmente está haciendo la clase "ServicioRemoto"), quedando de la siguiente forma:
La siguiente implementación de "Proceso" se llamará "Concatenacion" y simplemente concatenerá las cadenas "Hola " y "mundo" y nos regresará esta cadena. La clase queda de la siguiente forma:
La tercer y última implementación de "Proceso" se llamará "Ordenamiento" y lo que hará es regresar una lista de enteros de forma ordenada. La clase "Ordenamiento" queda de la siguiente forma:
Ahora que tenemos nuestros tres "Procesos" modifiquemos un poco nuestra clase "ServicioRemoto" para que en vez de ser él quien realice la funcionalidad, delegue este trabajo a una de las implementaciones de "Proceso". "ServicioRemoto" podrá recibir el Proceso que ejecutará de dos formas, la primera es mediante su constructor, y la segunda es pasando este Proceso mediante un setter.
Como ahora "ServicioRemoto" delegará la ejecución de los Procesos a otras clases, su única función será invocar el método "ejecuta" del Proceso correspondiente, y como estos regresan Objects, cambiará su tipo de retorno.
Con las modificaciones hechas, la clase "ServicioRemoto" queda de la siguiente forma:
Ahora sí, veremos cómo hacer que Spring inyecte estas dependencias de forma automática, haciendo uso de sus dos formas de DI.
Spring invocará al constructor correspondiente dependiendo del número y tipo de dependencias que pasemos, ya sea a través de los archivos de configuración en XML (que será la primera forma que veremos) o haciendo uso de anotaciones.
Ahora modificaremos la declaración actual de nuestro bean "servicioRemoto" que actualmente está de esta forma:
Lo primero que haremos es que en vez de que el elemento se auto-cierre, agregar una etiqueta de cierre, esto es porque agregaremos un contenido en el cuerpo de la etiqueta:
Para indicar que haremos inyección de dependencia basada en constructor, usamos el elemento "<constructor-arg>". Si no indicamos este elemento se usa el constructor por default de la clase (como en los ejemplos anteriores), pero si se usa, Spring buscará un constructor que reciba el tipo de objeto indicado en él. Tenemos dos maneras de usar este elemento, la primera es indicando el valor que pasaremos al constructor (útil para "constantes") usando el atributo "value". Por ejemplo, si "ServicioRemoto" tuviera un constructor que recibiera una cadena, podríamos usar "<constructor-arg>" de la siguiente forma:
Pero como no es el caso, usaremos la otra forma de "<constructor-arg>" que recibe una referencia a otro bean declarado en el archivo de configuración, usando su elemento "ref", de esta forma:
En "ref" indicamos el identificador del bean al que haremos referencia, en este caso es el bean "proceso" que creamos antes. La declaración del bean "servicioRemoto" queda de la siguiente forma:
¿Recuerdan que anteriormente dijimos que también podían declararse beans internos para usarse dentro de otras declaraciones? Pues aquí podemos hacerlo para ahorrarnos la declaración del bean "proceso", de esta forma:
Pero para hacer las cosas más claras lo dejaremos declarado como lo teníamos ^_^. Nuestro archivo de configuración queda de la siguiente forma:
Si nuestro constructor tuviera más de un argumento, bastaría con agregar otro elemento "<constructor-arg>".
Ahora ejecutaremos nuestra aplicación de forma normal. Con el siguiente contenido en el método "main":
Que es el mismo código que ya teníamos. Al correr nuestro código obtendremos la siguiente salida (le he quitado algo de la "basura" que genera Spring, para poder concentrarnos en la parte importante):
Como vemos, hemos obtenido la salida esperada, sin tener que escribir ninguna línea de código para resolver las dependencias que usa la clase "ServicioRemoto", que en este caso es el proceso. Es más, "ServicioRemoto" ni siquiera sabe de qué tipo es el "Proceso" que está ejecutando, ni cómo encontrarlo, es el motor de IoC de Spring, que haciendo uso de DI se encarga de crear una instancia de la clase apropiada e inyectarla, en este caso mediante el constructor, a "ServicioRemoto".
Ahora veremos otro de los encantos de Spring. Sin tocar una sola línea de nuestro código (o más bien dicho, sin mover ninguna clase) haremos que "ServicioRemoto" obtenga una dependencia distinta. Para esto cambiaremos la línea:
Por
Si volvemos a ejecutar nuestra aplicación ahora obtendremos la siguiente salida:
Ahora se ha inyectado una instancia de "Concatenacion" y "ServicioRemoto" la usa para ejecutar su Proceso. ¿Por qué es útil esto? Pues imagínense el caso que tengamos nuestra aplicación ya compilada y en producción. Si quisiéramos hacer un cambio sería solo cuestión de modificar el archivo XML de configuración (que siempre está en texto plano) y reiniciar la aplicación para que haya un cambio. Esta es una de las ventajas que tiene la configuración en XML sobre las anotaciones.
Pero, para efectos académicos, veamos cómo hacer la inyección por constructor, usando anotaciones.
Ahora modificaremos nuestra clase "ServicioRemoto". Lo primero que haremos es indicarle que queremos que Spring realice el wiring de forma automática para el constructor de "ServicioRemoto" que recibe como parámetro la instancia de "Proceso". La anotación "@Autowired" nos permite hacer esto.
"@Autowired" nos permite marcar un constructor, campo, setter, o método de configuración para ser auto-cableado por el proceso de inyección de dependencia de Spring. Si colocamos esta anotación en un constructor, solo podemos anotar uno de los que tenga la clase. Así que será este constructor el que se invoque, aunque la clase tenga más.
Colocamos la anotación justo sobre el constructor, de esta forma:
Si ejecutamos nuestra aplicación en este momento veremos que en la consola obtenemos el siguiente mensaje de error:
Lo que este error trata, tan sutilmente, de decirnos, es que Spring no sabe de cuál de las clases que implementa la interface "Proceso" debe inyectar ya que encuentra 3 clases candidatas: "calculo", "concatencacion", y "ordenamiento". Debemos indicar de cuál de estas tres clases queremos que sea inyectada una instancia. Para hacer esto Spring proporciona la anotación "@Qualifier" para poder referirnos a un bean a través de su nombre (que si recordamos, cuando usamos anotaciones, por default es el mismo nombre que la clase, pero iniciando con la primer letra en minúscula). Esta anotación la colocamos directamente dentro de los paréntesis que representan el parámetro al que haremos referencia (esta anotación también puede colocarse a nivel de campo) e indicamos el nombre del bean que queremos usar, que en este caso será "ordenamiento":
Finalmente, nuestra clase "ServicioRemoto" queda de la siguiente forma:
Ahora sí, si ejecutamos nuestra aplicación veremos la siguiente salida en la consola:
Como vemos, también ha resultado bastante simple hacer la inyección basada en constructor mediante anotaciones. De la misma forma en la que, usando el archivo XML, pudimos hacer que nuestra aplicación usara una instancia de una clase o de otra moviendo solo una línea, aquí podemos hacer lo mismo; si cambiamos el valor de la anotación "@Qualifier" de "ordenamiento" a "calculo", de esta forma:
Obtendremos la siguiente salida:
Como vemos, todo ha salido perfectamente usando la inyección de dependencia por constructor. Ahora veamos la segunda forma de inyección de dependencia que nos proporciona Spring: inyección de dependencia basada en setter.
Para mostrar este ejemplo modificaremos nuestra clase "ServicioRemoto" para que ahora ejecute el Proceso correspondiente un cierto número de veces. Este número de veces será representado por un objeto de tipo Integer, que será insertado haciendo uso de la inyección de dependencia por setter.
Agregaremos esta variable, llamada "repeticiones", junto con su setter, de la siguiente forma:
Ahora modificaremos el método "consultaDato" para colocar un ciclo que ejecutará el método el número de veces indicado, a través de la variable "repeticiones":
Ahora haremos que Spring inyecte el valor de la propiedad "repeticiones" haciendo uso de inyección por setter. Primero veremos cómo hacerlo usando el archivo de configuración en XML.
La inyección por setter se realiza utilizando el elemento "<property>" del archivo de configuración. "<property>", al igual que "<constructor-arg>", permite recibir un valor, a través del atributo "value", o usar una referencia a otro bean declarado en el archivo, a través del atributo "ref". En este elemento debemos indicar cuál atributo, o propiedad, es el que queremos que sea inyectado, para esto usamos el atributo "name" del elemento "<property>". Como nosotros pasaremos un valor constante a la propiedad "repeticiones" del "servicioRemoto" usaremos el atributo "value", que estableceremos con un valor de "5" (aunque pueden poner el valor que quieran).
Colocaremos el elemento "<property>" dentro de las etiquetas "<bean>" que definen al bean "servicioRemoto", justo debajo del "<constructor-arg>" que colocamos hace un momento, de esta forma:
Este es el único cambio que necesitamos hacer para realizar la inyección de dependencias por setter. Recuerden que si quisieran inyectar un objeto, en vez de una constante, deben declarar el bean (o usar un bean interno), y el atributo "ref" de "<property>".
Si ejecutamos nuestra aplicación, obtenemos la siguiente salida en la consola:
Si modifican el valor de "repeticiones", en el archivo de configuración, deberán ver reflejado este cambio, de forma inmediata, en la aplicación.
Ahora veamos cómo hacer esta misma inyección de dependencias, pero usando anotaciones.
Si quisiéramos inyectar una referencia a un objeto lo haríamos como vimos en el caso de la DI basada en constructor (usando las anotaciones "@Autowired" y "@Qualifier"). Cuando vamos a inyectar un valor constante usamos la anotación "@Value" e indicamos en su atributo "value" el valor que inyectaremos.
En la DI basada en setter, podemos colocar estas anotaciones en dos lugares. El primero es directamente sobre el setter del atributo que vamos a establecer, en el caso de "repeticiones" sería de esta forma:
El segundo es directamente en el atributo que queremos establecer, de esta forma:
Yo usaré esta segunda forma, ya que me parece un poco más clara que la anterior. Al final, nuestra clase "ServicioRemoto" anotada queda de la siguiente forma:
Si ejecutamos este código veremos la siguiente salida en consola:
Como vemos, nuevamente ha sido muy fácil modificar nuestra aplicación para agregar este tipo de inyección de dependencias.
Spring nos permite elegir el método que más nos guste (o nos convenga), además de que nos permite mezclar ambas formas, como lo hemos hecho en el ejemplo. Sin embargo hay una “regla” o consejo que podemos seguir: usar DI basada en constructor para las dependencias obligatorias y la DI basada en setter para las dependencias opcionales.
El equipo desarrollador de Spring (y yo también) generalmente sugieren usar inyección por setter, porque tener constructores con un número grande de argumentos pueden ser engorrosos, especialmente si algunos de los argumentos son opcionales. Los métodos setter también hacen que los objetos de esas clases sean más fáciles de reconfigurar o re-inyectar después.
Lo mejor es usar el tipo de DI que tenga más sentido para la clase en la que estamos trabajando.
Dejaremos este tutorial aquí. Hemos aprendido a realizar inyección de dependencias de las dos formas que nos proporciona Spring: basada en constructor y basada en setter, y a inyectar referencias a otros objetos y valores constantes, tanto con archivos de configuración en XML como con anotaciones. Solo hemos visto un par de ejemplos muy simples, pero recuerden que usando esta misma lógica pueden extender estos ejemplos para armar aplicaciones grandes.
También es importante recordar que es posible combinar ambas formas de configuración, anotando unos beans, y declarando otros en el archivo de configuración XML.
Espero que el tutorial les sea de utilidad. En los siguientes tutoriales veremos cómo inyectar colecciones en nuestros beans, además del scope y ciclo de vida de los mismos para saber cómo sacar una mayor ventaja de estos.
No olviden dejar sus dudas, comentarios, y sugerencias. Todo es bien recibido ^_^.
Saludos.
Descarga los archivos de este tutorial desde aquí:
Entradas Relacionadas:
En este tutorial veremos como configurar Spring de 4 formas distintas para que realice este proceso de Inyección de Dependencias de forma automática
Spring coloca las clases básicas de su contenedor de IoC en dos paquetes:
- org.springframework.beans
- org.springframework.context
Estos paquetes contienen dos interfaces que son las que realizan la "magia" de la instanciación de objetos. La primera es "org.springframework.beans.factory.BeanFactory", que proporciona un mecanismo de configuración avanzada capaz de manejar cualquier tipo de objeto. Además proporciona una sub-interface de esta última, "org.springframework.context.ApplicationContext", la cual agrega una integración más fácil con algunas de las características más interesantes de Spring, como su módulo de Programación Orientada a Aspectos, manejo de recursos de mensajes (para la internacionalización), publicación de eventos, y contextos específicos para ciertas capas de aplicaciones (como aplicaciones web), entre otras.
"BeanFactory" proporciona el framework de configuración y la funcionalidad básica, mientras que "ApplicationContext" agrega más funcionalidad específica para ciertos productos empresariales. Ambas interfaces representan contenedores de beans, solo que de tipos distintos.
"BeanFactory" representa las fábricas de beans y son el tipo de contenedor más simple. Este proporciona el soporte básico para DI. "ApplicationContext" representa... el contexto de la aplicación, estos están construidos con las mismas nociones de las fábricas de beans, pero proporcionan a la aplicación servicios adicionales.
En Spring, los objetos que forman la columna vertebral de las aplicaciones, y que son administradas por el contenedor de IoC de Spring, son llamados "Beans". Un bean es un objeto que es instanciado, ensamblado (cuando sus dependencias son inyectadas), y en general, administrado por el contenedor de IoC. Dicho de otra forma: un bean es simplemente uno de muchos objetos de nuestra aplicación. Los Beans, y las dependencias entre ellos, las declaramos en los metadatos del contenedor de IoC (lo cual puede ser mediante anotaciones o archivos de mapeo).
Como dije antes: la interface "ApplicationContext" representa un tipo de contenedor de IoC de Spring, y es el responsable de instanciar, configurar, y ensamblar los antes mencionados beans. El contenedor obtiene la información de qué objetos debe crear leyendo los metadatos de la configuración, los cuales, como dije antes, pueden ser colocados en archivos XML, anotaciones, o dentro del código Java.
Spring proporciona muchas implementaciones de "ApplicationContext". En particular en las aplicaciones que NO son web, usamos "org.springframework.context.support.ClassPathXmlApplicationContext", que busca los archivos de configuración dentro del classpath, o "org.springframework.context.support.FileSystemXmlApplicationContext", que los busca en el sistema de archivos de la máquina donde se ejecute la aplicación.
Para ejemplificar todo esto, haremos una aplicación la cual tendrá una clase de prueba que invocará a un "ServicioRemoto". Este servicio no hará algo especial, solamente regresará un número aleatorio entre 0 y 10; lo interesante de esto es que el cliente obtendrá una instancia del ServicioRemoto sin tener que crearla de manera directa, sino usando la DI de Spring.
Lo primero que haremos es crear un nuevo proyecto en NetBeans. Para esto vamos al Menú "File->New Project...". En la ventana que se abre seleccionamos la categoría "Java" y en el tipo de proyecto "Java Application". Le damos una ubicación y un nombre al proyecto, en mi caso será "SpringIoC". Nos aseguramos que las opciones "Create Main Class" y "Set as Main Project" estén habilitadas. Presionamos el botón "Finish" y veremos aparecer en el editor nuestra clase "Main".
Nota: Yo usaré dos proyectos, uno para usar archivos de mapeo y otro para usar anotaciones y que el código de ambos no se mezcle.
Agregamos la biblioteca de "Spring 3" que creamos en el primer tutorial de la serie. Hacemos clic derecho sobre el nodo "Libraries" del proyecto, en el menú que aparece elegimos la opción "Add Library..."
En la ventana que se abre seleccionamos la biblioteca "Spring 3":
Y presionamos el botón "Add Library". Con esto los archivos de la biblioteca deben agregarse a nuestro proyecto:
Ahora crearemos un nuevo paquete que contendrá a nuestros beans, este paquete se llamará "beans". Hacemos clic derecho en el paquete en el que se encuentra nuestra clase "Main" (que en mi caso es "ejemplos.spring.ioc") y en el menú que se abre seleccionamos la opción "New -> Java package..." y al nombre del paquete le agregamos "beans". Presionamos el botón "Finish" y con esto aparecerá nuestro nuevo paquete:
Crearemos nuestra clase "ServicioRemoto", dentro del paquete "beans". Esta clase será muy sencilla, y solo tendrá un método llamado "consultaDato" el cual no recibirá ningún parámetro y regresará un "int" aleatorio entre 1 y 10. La clase "ServicioRemoto" queda de esta forma:
public class ServicioRemoto
{
public int consultaDato()
{
return (int)(Math.random()*10.0);
}
}
Como ven es una clase sumamente sencilla, así que no hará falta explicar mucho.
Normalmente para obtener una nueva instancia de "ServicioRemoto" tendríamos que colocar la siguiente línea en alguna parte de la clase de prueba:
servicioRemoto = new ServicioRemoto();
Como había dicho antes: el cliente no debería ser responsable de la instanciación de este ServicioRemoto, ya que es una tarea que no le corresponde. Gracias a la DI podremos evitarnos el escribir la línea anterior, y por lo tanto (como veremos un poco más adelante) muchos problemas potenciales que pueden surgir con ella. En Spring, los objetos no son responsables de encontrar o crear los otros objetos que necesitan para hacer su trabajo. En vez de eso, el contenedor les da las referencias de los objetos con los que colaborarán.
El acto de crear estas asociaciones entre objetos de la aplicación, usando DI, es llamado "wiring" (cableado en español). En el resto del tutorial veremos la configuración básica de este wiring con Spring, y veremos las posibilidades que este nos ofrece.
En las aplicaciones basadas en Spring, los objetos de la aplicación viven dentro del contenedor de beans. El contenedor se encarga de crear los objetos y "cablearlos", configurarlos, y administrar su ciclo de vida completo. Entender la forma en la que funciona este contenedor, nos permitirá comprender la forma en la que nuestros objetos serán manejados, y por lo tanto cómo podemos aprovecharlos mejor dentro de nuestra aplicación.
Configuración de Beans en Spring
Como mencione al principio: existen, básicamente, dos formas de configurar los beans y sus dependencias en Spring, la primera es mediante un archivo de configuración en XML, la segunda es mediante anotaciones proporcionadas por Spring. Como siempre, cada una tiene sus ventajas y sus desventajas. Nosotros veremos ambas formas de configuración. Iniciaremos viendo la configuración con archivos en XML y posteriormente pasaremos a verla con anotaciones.1. Configuración con archivos XML
La configuración de Spring consiste de, al menos, la definición de un bean que será administrado por el contenedor. Los metadatos de configuración basados en XML muestran estos beans configurados como elementos "<bean />" (sin s) dentro de un elemento "<beans />" (con s).Estas definiciones de beans corresponden con los objetos que componen nuestra aplicación. Cuando creamos una aplicación, típicamente definimos una capa de servicios, una capa de acceso a datos (o DAOs) una capa de presentación, y una capa de infraestructura (con objetos como el "SessionFactory" de Hibernate). En Spring normalmente declaramos objetos que definen estas 4 capas (y no objetos muy específicos del dominio), ya que son las capas anteriores quienes se encargan de crear y manejar los objetos del dominio.
Crearemos el archivo de configuración en nuestro proyecto, así que hacemos clic derecho sobre el nodo "Source Packages" de nuestro proyecto, y en el menú contextual que se abre seleccionamos "new -> Other...":
En la ventana que se abre, seleccionamos la categoría "Other" y como tipo de archivo "Spring XML Configuration File":
Le damos un nombre al archivo, en mi caso será "applicationContext". Hacemos clic en el botón "Next". En la siguiente pantalla debemos seleccionar los namespaces que queremos que tenga nuestro archivo. No seleccionaremos ninguno. Más adelante en este mismo tutorial explicaré qué es esto. Presionamos el botón "Finish" y con eso aparecerá en nuestro editor el archivo "applicationContext.xml" con el siguiente contenido:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
El archivo contiene únicamente el elemento raíz "<beans>" dentro del cual declararemos cada uno de nuestros beans usando un elemento "<bean>" para cada uno ^_^.
Lo siguiente que haremos es declarar nuestro bean "ServicioRemoto" para que el contenedor de Spring lo administre. Para esto declaramos un elemento "<bean>" en nuestro archivo de configuración:
<bean />
El elemento "<bean>" es la unidad de configuración más básica en Spring, le dice que cree y administre un objeto.
Debemos darle un identificador a cada uno de nuestros beans dentro de la aplicación; esto no es obligatorio pero si no lo hacemos no podremos distinguir a los beans con los que trabajamos (normalmente NO especificamos un identificador cuando los usamos como beans internos). Estos deben estar identificados de manera única dentro del contenedor que tiene al bean. Un bean por lo regular solo tiene un identificador (aunque hay formas de colocarle más en caso de ser necesario). En esta configuración basada en XML usamos el atributo "id" o el atributo "name" para especificar el identificador del bean. El atributo "id" nos permite especificar exactamente un identificador, esto es porque hace uso del elemento "id" real de la especificación XML, lo que nos permite algunas validaciones extra con parsers normales de XML, sin embargo nos limita al uso de caracteres que son legales en XML para definir este identificador. Si necesitamos usar algún carácter especial como identificador, o si queremos indicar algún alias para nuestro bean, podemos usar el atributo "name", separando los alias con una coma (,), un punto y coma (;), o un espacio en blanco.
Para este bean, usaremos el identificador "servicioRemoto":
<bean id="servicioRemoto" />
Ahora nuestro bean puede ser referenciado por el nombre de "servicioRemoto".
El último paso que debemos hacer en nuestro archivo de configuración es decirle a Spring de qué tipo es el bean, esto es, la clase de la que queremos que cree los objetos. Para hacer esto usamos el atributo "class". En este caso el bean será de tipo "ejemplos.spring.ioc.beans.ServicioRemoto". Por lo que la configuración de nuestro bean queda de la siguiente forma:
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto" />
Y el archivo de configuración queda así:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto" />
</beans>
Ahora que tenemos nuestro archivo de configuración listo ha llegado el momento tan esperado, ahora usaremos uno de los contenedores de Spring para crear nuestros beans. Como había dicho, hay dos tipos de contenedores los "BeanFactory" y los "ApplicationContext". Veremos cómo usar estos dos tipos de contenedores.
1.1 Archivos XML con BeanFactory
Como podemos imaginarnos por su nombre, un BeanFactory es una implementación del patrón de diseño factory. Esto es, una clase cuya responsabilidad es crear beans.BeanFactory hace más que solo crear objetos. Como un BeanFactory conoce a los objetos que componen nuestra aplicación (los declarados en el archivo de configuración), es capaz de crear las asociaciones entre estos objetos en el momento de ser instanciados. Además, como el BeanFactory tiene el control del ciclo de vida del bean, puede hacer llamadas a métodos personalizados de inicialización y destrucción (si estos métodos están definidos).
Hay muchas implementaciones de la interface "BeanFactory" (supuestamente, porque yo solo conozco una). La más usada (y la única que conozco) es "org.springframework.beans.factory.xml.XmlBeanFactory", el cual carga los beans basándose en la configuración del archivo XML que creamos hace un momento.
Para crear un "XmlBeanFactory" debemos indicar de dónde se leerá este XML, ya que podemos obtenerlo de un stream, de un archivo en nuestro classpath, hasta de un arreglo de bytes. Para esto usamos una instancia de "org.springframework.core.io.Resource".
Spring proporciona varias implementaciones de esta interface:
- org.springframework.core.io.ByteArrayResource Permite cargar contenido de un arreglo de bytes. Según la documentación es útil para crear adjuntos de mail de contenido local.
- org.springframework.core.io.ClassPathResource Carga la configuración desde un recurso que se encuentre en el classpath de la aplicación. Este es el que normalmente estaremos usando.
- org.springframework.core.io.FileSystemResource Carga la configuración desde un archivo situado en cualquier parte de la máquina.
- org.springframework.core.io.InputStreamResource Carga la configuración desde un InputStream
- org.springframework.web.portlet.context.PortletContextResource Carga la configuración desde un recurso que está disponible en el contexto de un portlet.
- org.springframework.web.context.support.ServletContextResource Carga la configuración desde un recurso que está disponible en el contexto de una aplicación web.
- org.springframework.core.io.UrlResource Carga la configuración desde un recurso que está disponible en una URL dada, usando la resolución de un protocolo, como "file:" o "classpath:"
- org.springframework.core.io.VfsResource Carga la configuración desde un recurso que se encuentra en un sistema de archivos virtual (Virtual file system).
Nosotros usaremos "ClassPathResource" para cargar nuestra configuración, ya que el archivo "applicationContext.xml" que creamos está dentro del classpath de nuestra aplicación (más específicamente en la raíz de la misma). A este "ClassPathResource" le pasamos una cadena que contiene la ruta (dentro del classpath) de nuestro archivo de configuración, de esta forma:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Ahora que tenemos esta "BeanFactory" ya podremos obtener nuestro bean.
Como una nota importante hay que decir que en el momento en el que creamos nuestro BeanFactory, este lee las definiciones de los beans del archivo indicado, sin embargo los beans NO son creados en ese momento. Los beans se crean hasta el momento que son necesitados (cuando los pedimos en nuestro código).
Ahora, con nuestro "BeanFactory" podemos obtener un bean haciendo uso del identificador que le dimos en el archivo XML, en este caso "servicioRemoto". Para esto usamos el método "getBean" del BeanFactory. A este método podemos (opcionalmente) indicarle la clase que esperamos que devuelva el método (y con esto ahorrarnos un cast ^_^).
ServicioRemoto servicio = beanFactory.getBean("servicioRemoto", ServicioRemoto.class);
En el momento en el que invocamos a "getBean", la fabrica instanciará el bean y establecerá los valores de sus propiedades usando DI. Es aquí cuando comienza el ciclo de vida de un bean en el contenedor de Spring. Veremos este ciclo de vida más adelante.
Ahora que ya tenemos una instancia de "ServicioRemoto" podemos invocar sus métodos como lo hacemos normalmente:
System.out.println("El valor es " + servicio.consultaDato());
Todo esto lo hacemos dentro de nuestro método "main", que queda de esta forma:
public static void main(String[] args)
{
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
ServicioRemoto servicio = beanFactory.getBean("servicioRemoto", ServicioRemoto.class);
System.out.println("El valor es " + servicio.consultaDato());
}
Si ejecutamos nuestra aplicación obtenemos la siguiente salida:
run:
XX-xxx-XXXX XX:XX:XX org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
El valor es 7
BUILD SUCCESSFUL (total time: 0 seconds)
Como podemos ver, obtenemos un mensaje que indica que las definiciones de beans del archivo "applicationContext.xml". Además obtenemos el valor que está regresando nuestro servicio remoto (en mi caso "7").
Todo esto lo logramos sin hacer ni un solo "new". Este ejemplo aunque es muy simple (por no decir tonto ^_^) nos muestra las bases de Spring. Un poco más adelante veremos, en este mismo tutorial, dónde está la verdadera ventaja de Spring (solo confíen en mi y continúen leyendo).
Ahora veremos cómo hacer esto mismo usando un "ApplicationContext".
1.2 - Archivos XML con ApplicationContext
Un "BeanFactory" está bien para aplicaciones simples, pero no toma ventaja de todo el poder que nos proporciona el framework de Spring, y como desarrolladores queremos cargar los beans de nuestra aplicación usando el contenedor más avanzado que nos proporciona Spring, el ApplicationContext.Usar "ApplicationContext" es similar a usar "BeanFactory". Ambos cargan definiciones de beans, cablean los beans, y los envían a quienes se los piden. Pero "ApplicationContext" ofrece más cosas:
- Proporciona un medio para resolver mensajes de texto (no, no son SMSs), incluyendo soporte para internacionalización (I18N) de estos mensajes.
- Proporciona una manera genérica de cargar archivos de recursos, como imágenes.
- Puede publicar eventos a beans que están registrados como listeners.
Debido a que "ApplicationContext" proporciona más funcionalidades, es preferible el uso de este sobre "BeanFactory" en casi todo tipo de aplicaciones, la única excepción es cuando estamos ejecutando una aplicación donde los recursos son escasos, como en un dispositivo móvil.
Aunque hay muchas implementaciones de "ApplicationContext" (de esta si =) ), hay 3 que son las más usadas comúnmente:
- org.springframework.context.support.ClassPathXmlApplicationContext: Carga la configuración desde un archivo XML ubicado en el classpath.
- org.springframework.context.support.FileSystemXmlApplicationContext: Carga la configuración desde un archivo XML ubicado en cualquier parte del sistema de archivos de la computadora.
- org.springframework.web.context.support.XmlWebApplicationContext: Carga la configuración desde un archivo XML ubicado en el contexto de una aplicación web
Hablaremos de "XmlWebApplicationContext" cuando hablemos de aplicaciones web en uno de los siguientes tutoriales.
En este caso usaremos "ClassPathXmlApplicationContext". En este caso el constructor recibe una cadena (o cadenas) indicando la ubicación de este archivo (o archivos):
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Con este ApplicationContext creado ahora podemos recuperar beans de la misma forma que lo hicimos cuando usamos el "BeanFactory", usando el método "getBean", ya que "ApplicationContext" es una interface que extiende de "BeanFactory":
ServicioRemoto servicio = applicationContext.getBean("servicioRemoto", ServicioRemoto.class);
Además de la funcionalidad extra, mencionada hace un momento, existe otra diferencia entre "ApplicationContext" y "BeanFactory", y es la manera en la que cargan los beans. Hay que decir que por default todos los beans manejados por Spring son Singletons, esto es, solo existe una instancia de ellos por aplicación. Como dije antes: "BeanFactory" crea los beans al momento que estos son pedidos (con la invocación de "getBean"). "ApplicationContext" trabaja de una forma un poco mejor, ya carga todos los singletons cuando se inicia el contexto, y de esta forma se asegura de que estarán listos en el momento en el que sean solicitados.
Ahora que tenemos nuestra instancia de "ServicioRemoto", proporcionada por Spring, podemos hacer uso de sus métodos de forma normal:
System.out.println("El valor es " + servicio.consultaDato());
Finalmente, nuestro método "main" queda de la siguiente forma:
public static void main(String[] args)
{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ServicioRemoto servicio = applicationContext.getBean("servicioRemoto", ServicioRemoto.class);
System.out.println("El valor es " + servicio.consultaDato());
}
Si ejecutamos nuestra aplicación, obtendremos una salida similar a esta:
run:
XX/xxx/XXXX XX:XX:XX PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
El valor es 5
BUILD SUCCESSFUL (total time: 0 seconds)
Como podemos ver, hacer el cambio de "BeanFactory" a "ApplicationContext" nos llevó solamente una línea. Y la configuración en el archivo XML también es bastante pequeña.
Cuando el contenido de nuestro archivo de configuración comience a crecer podemos separar nuestra configuración en varios archivos (por ejemplo, uno por cada capa de nuestra aplicación) y después hacer referencia a estos archivos en un archivo "maestro" usando el elemento "<import>", por ejemplo, en nuestro archivo "applicationContext.xml" podríamos tener:
<beans>
<import resource="servicios.xml" />
<import resource="persistencia.xml" />
<import resource="presentacion.xml" />
</beans>
Esto también puede ayudarnos cuando queramos organizar mejor los beans de nuestra aplicación para agruparlos de alguna manera, y seguir cargando solo un archivo XML de configuración.
Existe una alternativa para el uso de estos archivos XML, y es realizar la configuración a través de las anotaciones que nos proporciona Spring, que es lo que veremos a continuación.
2. Configuración con Anotaciones
El uso de anotaciones nos permite eliminar el tener la definición de nuestros beans en un archivo XML, y esto es "útil" cuando tenemos declarados muchos beans y nuestro archivo se vuelve engorroso (aunque siempre podremos combinar ambas formas de declaración).Spring proporciona una serie (relativamente grande) de anotaciones con las cuales podemos indicar exactamente cómo queremos que maneje la DI de cada uno de los componentes. Existen en realidad varias formas de manejar estas anotaciones, pero en este momento solo nos centraremos en anotar nuestros beans. Para esto existen tres anotaciones básicas, las cuales se conocen como "estereotipos":
- @Repository – Indica que las clases marcadas con esta anotación están relacionada de alguna forma con una capa de persistencia de datos.
- @Service – Indica que las clases marcadas con esta anotación está en una capa de servicios o de lógica de negocios.
- @Controller – Indica que las clases marcadas con esta anotación son el controlador de una aplicación web.
Las tres anotaciones anteriores extienden de "@Component", la cual indica que la clase es un componente, y por lo tanto son candidatas para ser auto-detectadas cuando usamos una configuración basada en anotaciones.
Veremos dos formas de configuración con anotaciones, en la primera (irónicamente) usaremos un archivo XML para indicar dónde están nuestras clases anotadas. En la segunda forma veremos cómo hacer esto mismo, pero sin necesidad de el archivo XML.
2.1. Anotaciones con Archivo XML
En esta configuración lo primero que haremos es eliminar el contenido del archivo "applicationContext.xml" que creamos hace un momento, dejándolo de esta forma:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
</beans>
Ahora agregaremos un namespace especial a este archivo de configuración que hace referencia a los elementos de configuración del contexto de Spring. En XML un namespace es una forma que se usa para proporcionar elementos con nombres únicos. Spring utiliza estos namespaces para separar conjuntos de etiquetas que están relacionadas y que ofrecen ciertas funcionalidades especiales. Además esto hace que leer y escribir los archivos de configuración sea más fácil. Algunas veces escucharán que se refieren a estos namespaces como "esquemas"
Cada uno de estos namespaces hace referencia a un esquema XML distinto. Así por ejemplo tenemos un namespace para programación orientada a aspectos ("aop"), otro para manejo de configuración de elementos jee ("jee"), otro para manejo de mensajes ("jme"), otro para transacciones ("tx"), etc.
El namespace "context" se encarga de la configuración del "ApplicationContext". Para indicar que haremos uso de este namespace, solo lo agregamos la siguiente línea dentro del elemento "beans" de archivo "applicationContext.xml":
xmlns:context="http://www.springframework.org/schema/context"
Dejándolo de la siguiente forma:
<?xml version="1.0" encoding="UTF-8"?>
<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">
</beans>
Ahora indicaremos, usando el namespace "context", que queremos activar la configuración de Spring para que detecte las anotaciones (propias de Spring) en las clases. Para eso usamos el elemento "annotation-config":
<context:annotation-config/>
Ahora debemos indicar en dónde (en qué paquetes) se encuentran las clases que hemos anotado. Si no indicamos este paquete Spring no buscará de forma automática las clases (no sé realmente por qué). Usamos el elemento "component-scan" para indicar, mediante su elemento "base-package", el paquete raíz en el que se encuentran nuestros beans anotados. Para este ejemplo el paquete raíz es "ejemplos.spring.ioc.beans". Por lo que el elemento queda de la siguiente forma:
<context:component-scan base-package="ejemplos.spring.ioc.beans" />
Y el archivo de configuración así:
<?xml version="1.0" encoding="UTF-8"?>
<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:annotation-config/>
<context:component-scan base-package="ejemplos.spring.ioc.beans" />
</beans>
Como vemos no hemos definido ni un solo bean en el archivo, así que ahora deberemos indicarlo directamente en las clases que se convertirán en nuestros beans (que en ese caso solo es "ServicioRemoto". Para indicar esto usaremos las anotaciones que mencioné antes ("@Repository", "@Service", "@Controller"). Como "ServicioRemoto" pertenece a la capa de servicios, lo mercaremos con la anotación "@Service", de esta forma:
@Service
public class ServicioRemoto
{
}
Si recuerdan, hacemos referencia a los beans mediante su identificador. Pero "ServicioRemoto" no tiene indicado ninguno. Spring asigna un nombre por default a sus beans, que es el nombre de la case pero con la primer letra en minúscula. Así que en este caso el nombre del bean será "servicioRemoto". Si quisiéramos darle a nuestro bean un nombre distinto podemos hacerlo mediante el atributo "value" de la anotación "@Service", de la siguiente forma:
@Service(value="servicioRemoto")
Por lo que nuestra clase "ServicioRemoto" queda de la siguiente forma:
@Service(value="servicioRemoto")
public class ServicioRemoto
{
public int consultaDato()
{
return (int) (Math.random() * 10.0);
}
}
No hay que hacerle ningún cambio al código de la clase "Main". Seguimos obteniendo una instancia de "ApplicationContext" (o de "BeanFactory") y le indicamos la ubicación del archivo que contiene la configuración de Spring, que en este caso es "applicationContext.xml". De la misma forma seguimos obteniendo el bean "servicioRemoto" invocando al método "getBean" del objeto "ApplicationContext". Finalmente llamamos al método "consultaDato" del bean "servicioRemoto". Nuestro Método "main" queda de la siguiente forma:
public static void main(String[] args)
{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ServicioRemoto servicioRemoto = applicationContext.getBean("servicioRemoto", ServicioRemoto.class);
System.out.println("El valor es: " + servicioRemoto.consultaDato());
}
Si ejecutamos nuestra aplicación veremos la siguiente salida en la consola:
run:
XX/xx/XXXX XX:XX:XX PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5: startup date [Xxx Xxx XX XX:XX:XX CST XXXX]; root of context hierarchy
XX/xx/XXXX XX:XX:XX PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
XX/xx/XXXX XX:XX:XX PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5329c5: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,servicioRemoto]; root of factory hierarchy
El valor es: 3
BUILD SUCCESSFUL (total time: 0 seconds)
Podemos ver que ahora hay más información en la consola pero que finalmente nuestro código se ejecuta y obtenemos la salida esperada. Por lo que nuestra aplicación ha quedado configurada de forma adecuada ^_^.
Como vemos, usando anotaciones, nuestro archivo de configuración ha quedado aún más pequeño. Sin embargo, aún seguimos dependiendo de un archivo XML para que nuestra aplicación funcione correctamente. Ahora veremos cómo configurar nuestra aplicación, pero sin depender de ningún XML.
2.2. Anotaciones sin Archivo XML
Como para esta última configuración no usaremos el archivo "applicationContext.xml" podemos eliminarlo de la aplicación. Nuevamente usaremos la clase "ServicioRemoto" con la anotación "@Service", de esta forma:
@Service(value="servicioRemoto")
public class ServicioRemoto
Para esta configuración, el único cambio necesario es en el código que crea el objeto "ApplicationContext" (en el método "main"). En este caso necesitamos un objeto especial ya que, como no existe un archivo XML, debemos indicar cuáles son las clases anotadas (o el paquete que las contiene).
El objeto "especial" que necesitamos crear es una implementación de "ApplicationContext" (y por lo tanto de "BeanFactory"), la clase "AnnotationConfigApplicationContext". Esta clase tiene dos constructores que nos interesan; el primero recibe una lista, separada por comas, de las clases que tenemos anotadas con @Component (o alguna de sus subclases), como es el caso de "ServicioRemoto". Por lo que crearíamos un nuevo objeto de esta clase de la siguiente manera:
new AnnotationConfigApplicationContext(ServicioRemoto.class);
Esto es muy cómodo si solo tenemos dos o tres clases anotadas, pero ¿qué pasa en el caso de que tengamos más? Bueno, en este caso podemos usar el segundo constructor de "AnnotationConfigApplicationContext" en el cual indicamos el paquete (o paquetes) raíz en el que se encuentran las clases anotadas, como en el caso del elemento "context:component-scan" del archivo XML, de la siguiente forma:
new AnnotationConfigApplicationContext("ejemplos.spring.ioc.beans");
Con este cambio nuestro método "main" queda de la siguiente forma:
public static void main(String[] args)
{
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("ejemplos.spring.ioc.beans");
ServicioRemoto servicio = applicationContext.getBean("servicioRemoto", ServicioRemoto.class);
System.out.println("El valor es " + servicio.consultaDato());
}
Si ejecutamos nuestra aplicación veremos la siguiente salida en la consola:
run:
XX-xxx-XXXX XX:XX:XX org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@d1e604: startup date [Xxx Xxx XX XX:XX:XX CST XXXX]; root of context hierarchy
XX-xxx-XXXX XX:XX:XX org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@186d4c1: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,servicioRemoto]; root of factory hierarchy
El valor es 4
BUILD SUCCESSFUL (total time: 0 seconds)
Que como podemos observar indica que todo quedó configurado correctamente ^_^.
De esta forma podemos declarar todas las dependencias y la configuración (casi toda) de nuestros beans directamente en las clases. Esta manera de configuración de beans, aunque es más cómoda y fácil para nosotros como desarrolladores al momento de estar escribiendo el código, es mucho menos clara y (para ser sinceros) no nos permite usar todo el poder que Spring nos proporciona. Es por esto que en estos tutoriales nos centraremos más en cómo hacer las cosas usando el archivo de configuración, haciendo las respectivas indicaciones para hacer lo mismo con anotaciones (en caso de que sea posible hacerlo).
Ahora que hemos visto 4 formas distintas de configurar el contenedor de beans de Spring para indicarle cuales son los beans que debe manejar, veremos algunos detalles un poco más interesantes de este framework con respecto a la inyección de dependencias.
Inyección de Dependencias
Una aplicación empresarial típica no consiste de un solo objeto o bean (como en el caso de nuestra prueba con "ServicioRemoto"). Aún la aplicación más simple tiene unos cuantos objetos que trabajan juntos para realizar sus procesos de una manera coherente. Como ya había dicho antes, Spring nos proporciona la forma de unir estos objetos sin tener que indicarlo de manera explícita en el código. Ahora veremos (por fin ^_^) como hacer esto de una manera rápida y sencilla.Cuando usamos el mecanismo de DI de Spring, el código es más limpio, y está desacoplado de una forma más efectiva. El objeto no busca sus dependencias, de hecho ni siquiera conoce la ubicación o clase de su dependencia. Por esta misma razón, las clases se hacen más fáciles de probar, en particular cuando las dependencias las definimos como interfaces o clases abstractas, ya que podemos usar objetos mock (que veremos en algún otro tutorial sobre pruebas) para realizar pruebas.
En Spring, la DI existe en dos formas principales:
- Inyección de dependencia basada en constructor
- Inyección de dependencia basada en setter
Lo primero que haremos es crear una nueva interface llamada "Proceso". Esta interface representará... bueno el proceso que se realizará dentro del servicio remoto. Tendrá un método llamado "ejecuta()", que será el que se encargará de realizar el proceso. Colocamos esta interface en el paquete de nuestros beans. "Proceso" queda de la siguiente forma:
public interface Proceso
{
Object ejecuta();
}
Como vemos, "ejecuta" regresa un "Object". Este irá cambiando de acuerdo a las implementaciones que realicemos de esta interface, de las cuales por cierto haremos 3. La primer implementación será la clase "Calculo". La cual solo regresará un número al azar entre 0 y 100 (que es lo que ya actualmente está haciendo la clase "ServicioRemoto"), quedando de la siguiente forma:
public class Calculo implements Proceso
{
public Object ejecuta()
{
return (int)(Math.random()*100.0);
}
}
La siguiente implementación de "Proceso" se llamará "Concatenacion" y simplemente concatenerá las cadenas "Hola " y "mundo" y nos regresará esta cadena. La clase queda de la siguiente forma:
public class Concatenacion implements Proceso
{
public Object ejecuta()
{
return new StringBuilder().append("Hola ").append(" mundo");
}
}
La tercer y última implementación de "Proceso" se llamará "Ordenamiento" y lo que hará es regresar una lista de enteros de forma ordenada. La clase "Ordenamiento" queda de la siguiente forma:
public class Ordenamiento implements Proceso
{
public Object ejecuta()
{
List< Integer> listaEnteros = new ArrayList<Integer>();
listaEnteros.add(9);
listaEnteros.add(3);
listaEnteros.add(1);
listaEnteros.add(6);
listaEnteros.add(5);
listaEnteros.add(10);
Collections.sort(listaEnteros);
return listaEnteros;
}
}
Ahora que tenemos nuestros tres "Procesos" modifiquemos un poco nuestra clase "ServicioRemoto" para que en vez de ser él quien realice la funcionalidad, delegue este trabajo a una de las implementaciones de "Proceso". "ServicioRemoto" podrá recibir el Proceso que ejecutará de dos formas, la primera es mediante su constructor, y la segunda es pasando este Proceso mediante un setter.
Como ahora "ServicioRemoto" delegará la ejecución de los Procesos a otras clases, su única función será invocar el método "ejecuta" del Proceso correspondiente, y como estos regresan Objects, cambiará su tipo de retorno.
Con las modificaciones hechas, la clase "ServicioRemoto" queda de la siguiente forma:
public class ServicioRemoto
{
private Proceso proceso;
public ServicioRemoto()
{
}
public ServicioRemoto(Proceso proceso)
{
this.proceso = proceso;
}
public Object consultaDato()
{
return proceso.ejecuta();
}
}
Ahora sí, veremos cómo hacer que Spring inyecte estas dependencias de forma automática, haciendo uso de sus dos formas de DI.
3. Inyección de Dependencia basada en Constructor
En este tipo de inyección de dependencia, el contenedor invoca a un constructor de la clase, que recibe cierto número de argumentos, cada uno de los cuales representa una dependencia.Spring invocará al constructor correspondiente dependiendo del número y tipo de dependencias que pasemos, ya sea a través de los archivos de configuración en XML (que será la primera forma que veremos) o haciendo uso de anotaciones.
3.1. Inyección de Dependencia basada en Constructor con Archivos XML
Lo primero que haremos es indicar, en el archivo de configuración "applicationContext.xml", que usaremos un bean llamado "proceso", que en este caso será de tipo "Ordenamiento":
<bean id="proceso" class="ejemplos.spring.ioc.beans.Ordenamiento" />
Ahora modificaremos la declaración actual de nuestro bean "servicioRemoto" que actualmente está de esta forma:
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto" />
Lo primero que haremos es que en vez de que el elemento se auto-cierre, agregar una etiqueta de cierre, esto es porque agregaremos un contenido en el cuerpo de la etiqueta:
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto">
</bean>
Para indicar que haremos inyección de dependencia basada en constructor, usamos el elemento "<constructor-arg>". Si no indicamos este elemento se usa el constructor por default de la clase (como en los ejemplos anteriores), pero si se usa, Spring buscará un constructor que reciba el tipo de objeto indicado en él. Tenemos dos maneras de usar este elemento, la primera es indicando el valor que pasaremos al constructor (útil para "constantes") usando el atributo "value". Por ejemplo, si "ServicioRemoto" tuviera un constructor que recibiera una cadena, podríamos usar "<constructor-arg>" de la siguiente forma:
<constructor-arg value="5" />
Pero como no es el caso, usaremos la otra forma de "<constructor-arg>" que recibe una referencia a otro bean declarado en el archivo de configuración, usando su elemento "ref", de esta forma:
<constructor-arg ref="proceso" />
En "ref" indicamos el identificador del bean al que haremos referencia, en este caso es el bean "proceso" que creamos antes. La declaración del bean "servicioRemoto" queda de la siguiente forma:
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto">
<constructor-arg ref="proceso" />
</bean>
¿Recuerdan que anteriormente dijimos que también podían declararse beans internos para usarse dentro de otras declaraciones? Pues aquí podemos hacerlo para ahorrarnos la declaración del bean "proceso", de esta forma:
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto" >
<constructor-arg>
<bean class="ejemplos.spring.ioc.beans.Ordenamiento" />
</constructor-arg>
</bean>
Pero para hacer las cosas más claras lo dejaremos declarado como lo teníamos ^_^. Nuestro archivo de configuración queda de la siguiente forma:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="proceso" class="ejemplos.spring.ioc.beans.Ordenamiento" />
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto">
<constructor-arg ref="proceso" />
</bean>
</beans>
Si nuestro constructor tuviera más de un argumento, bastaría con agregar otro elemento "<constructor-arg>".
Ahora ejecutaremos nuestra aplicación de forma normal. Con el siguiente contenido en el método "main":
public static void main(String[] args)
{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ServicioRemoto servicio = applicationContext.getBean("servicioRemoto", ServicioRemoto.class);
System.out.println("El valor es " + servicio.consultaDato());
}
Que es el mismo código que ya teníamos. Al correr nuestro código obtendremos la siguiente salida (le he quitado algo de la "basura" que genera Spring, para poder concentrarnos en la parte importante):
run:
El valor es [1, 3, 5, 6, 9, 10]
BUILD SUCCESSFUL (total time: 0 seconds)
Como vemos, hemos obtenido la salida esperada, sin tener que escribir ninguna línea de código para resolver las dependencias que usa la clase "ServicioRemoto", que en este caso es el proceso. Es más, "ServicioRemoto" ni siquiera sabe de qué tipo es el "Proceso" que está ejecutando, ni cómo encontrarlo, es el motor de IoC de Spring, que haciendo uso de DI se encarga de crear una instancia de la clase apropiada e inyectarla, en este caso mediante el constructor, a "ServicioRemoto".
Ahora veremos otro de los encantos de Spring. Sin tocar una sola línea de nuestro código (o más bien dicho, sin mover ninguna clase) haremos que "ServicioRemoto" obtenga una dependencia distinta. Para esto cambiaremos la línea:
<bean id="proceso" class="ejemplos.spring.ioc.beans.Ordenamiento" />
Por
<bean id="proceso" class="ejemplos.spring.ioc.beans.Concatenacion" />
Si volvemos a ejecutar nuestra aplicación ahora obtendremos la siguiente salida:
run:
El valor es Hola mundo
BUILD SUCCESSFUL (total time: 0 seconds)
Ahora se ha inyectado una instancia de "Concatenacion" y "ServicioRemoto" la usa para ejecutar su Proceso. ¿Por qué es útil esto? Pues imagínense el caso que tengamos nuestra aplicación ya compilada y en producción. Si quisiéramos hacer un cambio sería solo cuestión de modificar el archivo XML de configuración (que siempre está en texto plano) y reiniciar la aplicación para que haya un cambio. Esta es una de las ventajas que tiene la configuración en XML sobre las anotaciones.
Pero, para efectos académicos, veamos cómo hacer la inyección por constructor, usando anotaciones.
3.2. Inyección de Dependencia basada en Constructor con Anotaciones
En este caso debemos recordar colocar alguna de las anotaciones que extienden de "@Component" en las clases que implementan nuestra lógica de Procesos. Nosotros, nuevamente, usaremos la anotación "@Service". Las clases que implementan la interface "Proceso" se ven así (omitiendo los cuerpos de las mismas):
@Service
public class Calculo implements Proceso
{
}
@Service
public class Concatenacion implements Proceso
{
}
@Service
public class Ordenamiento implements Proceso
{
}
Ahora modificaremos nuestra clase "ServicioRemoto". Lo primero que haremos es indicarle que queremos que Spring realice el wiring de forma automática para el constructor de "ServicioRemoto" que recibe como parámetro la instancia de "Proceso". La anotación "@Autowired" nos permite hacer esto.
"@Autowired" nos permite marcar un constructor, campo, setter, o método de configuración para ser auto-cableado por el proceso de inyección de dependencia de Spring. Si colocamos esta anotación en un constructor, solo podemos anotar uno de los que tenga la clase. Así que será este constructor el que se invoque, aunque la clase tenga más.
Colocamos la anotación justo sobre el constructor, de esta forma:
@Autowired
public ServicioRemoto(Proceso proceso)
{
this.proceso = proceso;
}
Si ejecutamos nuestra aplicación en este momento veremos que en la consola obtenemos el siguiente mensaje de error:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'servicioRemoto' defined in file [ejemplos\spring\ioc\beans\ServicioRemoto.class]:
Unsatisfied dependency expressed through constructor argument with index 0 of type [ejemplos.spring.ioc.beans.Proceso]:
No unique bean of type [ejemplos.spring.ioc.beans.Proceso] is defined: expected single matching bean but found 3: [calculo, concatenacion, ordenamiento];
Lo que este error trata, tan sutilmente, de decirnos, es que Spring no sabe de cuál de las clases que implementa la interface "Proceso" debe inyectar ya que encuentra 3 clases candidatas: "calculo", "concatencacion", y "ordenamiento". Debemos indicar de cuál de estas tres clases queremos que sea inyectada una instancia. Para hacer esto Spring proporciona la anotación "@Qualifier" para poder referirnos a un bean a través de su nombre (que si recordamos, cuando usamos anotaciones, por default es el mismo nombre que la clase, pero iniciando con la primer letra en minúscula). Esta anotación la colocamos directamente dentro de los paréntesis que representan el parámetro al que haremos referencia (esta anotación también puede colocarse a nivel de campo) e indicamos el nombre del bean que queremos usar, que en este caso será "ordenamiento":
@Autowired
public ServicioRemoto(@Qualifier("ordenamiento")Proceso proceso)
{
this.proceso = proceso;
}
Finalmente, nuestra clase "ServicioRemoto" queda de la siguiente forma:
@Service(value="servicioRemoto")
public class ServicioRemoto
{
private Proceso proceso;
public ServicioRemoto()
{
}
@Autowired
public ServicioRemoto(@Qualifier("ordenamiento")Proceso proceso)
{
this.proceso = proceso;
}
public Object consultaDato()
{
return proceso.ejecuta();
}
}
Ahora sí, si ejecutamos nuestra aplicación veremos la siguiente salida en la consola:
run:
El valor es: [1, 3, 5, 6, 9, 10]
BUILD SUCCESSFUL (total time: 0 seconds)
Como vemos, también ha resultado bastante simple hacer la inyección basada en constructor mediante anotaciones. De la misma forma en la que, usando el archivo XML, pudimos hacer que nuestra aplicación usara una instancia de una clase o de otra moviendo solo una línea, aquí podemos hacer lo mismo; si cambiamos el valor de la anotación "@Qualifier" de "ordenamiento" a "calculo", de esta forma:
public ServicioRemoto(@Qualifier("calculo")Proceso proceso)
{
}
Obtendremos la siguiente salida:
run:
El valor es: 49
BUILD SUCCESSFUL (total time: 0 seconds)
Como vemos, todo ha salido perfectamente usando la inyección de dependencia por constructor. Ahora veamos la segunda forma de inyección de dependencia que nos proporciona Spring: inyección de dependencia basada en setter.
4. Inyección de Dependencia basada en Setter
Las propiedades de los JavaBeans, típicamente son declaradas como private y tienen un par de métodos de acceso, un setter y un getter. La inyección de dependencia basada en setter se realiza cuando el contenedor llama a los métodos setter de los beans, para inyectar sus dependencias requeridas.Para mostrar este ejemplo modificaremos nuestra clase "ServicioRemoto" para que ahora ejecute el Proceso correspondiente un cierto número de veces. Este número de veces será representado por un objeto de tipo Integer, que será insertado haciendo uso de la inyección de dependencia por setter.
Agregaremos esta variable, llamada "repeticiones", junto con su setter, de la siguiente forma:
private Integer repeticiones;
public void setRepeticiones(Integer repeticiones)
{
this.repeticiones = repeticiones;
}
Ahora modificaremos el método "consultaDato" para colocar un ciclo que ejecutará el método el número de veces indicado, a través de la variable "repeticiones":
public Object consultaDato()
{
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < repeticiones; i++)
{
stringBuilder.append(i + 1).append(" ").append(proceso.ejecuta()).append("\n");
}
return stringBuilder.toString();
}
Ahora haremos que Spring inyecte el valor de la propiedad "repeticiones" haciendo uso de inyección por setter. Primero veremos cómo hacerlo usando el archivo de configuración en XML.
4.1. Inyección de Dependencia Basado en Setter, mediante archivo XML
Para este ejemplo, modificaremos el archivo "applicationContext.xml".La inyección por setter se realiza utilizando el elemento "<property>" del archivo de configuración. "<property>", al igual que "<constructor-arg>", permite recibir un valor, a través del atributo "value", o usar una referencia a otro bean declarado en el archivo, a través del atributo "ref". En este elemento debemos indicar cuál atributo, o propiedad, es el que queremos que sea inyectado, para esto usamos el atributo "name" del elemento "<property>". Como nosotros pasaremos un valor constante a la propiedad "repeticiones" del "servicioRemoto" usaremos el atributo "value", que estableceremos con un valor de "5" (aunque pueden poner el valor que quieran).
Colocaremos el elemento "<property>" dentro de las etiquetas "<bean>" que definen al bean "servicioRemoto", justo debajo del "<constructor-arg>" que colocamos hace un momento, de esta forma:
<bean id="servicioRemoto" class="ejemplos.spring.ioc.beans.ServicioRemoto" >
<constructor-arg ref="proceso" />
<property name="repeticiones" value="5" />
</bean>
Este es el único cambio que necesitamos hacer para realizar la inyección de dependencias por setter. Recuerden que si quisieran inyectar un objeto, en vez de una constante, deben declarar el bean (o usar un bean interno), y el atributo "ref" de "<property>".
Si ejecutamos nuestra aplicación, obtenemos la siguiente salida en la consola:
run:
El valor es 1 - [1, 3, 5, 6, 9, 10]
2 - [1, 3, 5, 6, 9, 10]
3 - [1, 3, 5, 6, 9, 10]
4 - [1, 3, 5, 6, 9, 10]
5 - [1, 3, 5, 6, 9, 10]
Si modifican el valor de "repeticiones", en el archivo de configuración, deberán ver reflejado este cambio, de forma inmediata, en la aplicación.
Ahora veamos cómo hacer esta misma inyección de dependencias, pero usando anotaciones.
4.2. Inyección de Dependencia Basado en Setter, mediante Anotaciones
Cuando trabajamos con anotaciones, hacer inyección de dependencias se hace de forma distinta si vamos a inyectar una referencia a un objeto que si vamos a inyectar un valor constante.Si quisiéramos inyectar una referencia a un objeto lo haríamos como vimos en el caso de la DI basada en constructor (usando las anotaciones "@Autowired" y "@Qualifier"). Cuando vamos a inyectar un valor constante usamos la anotación "@Value" e indicamos en su atributo "value" el valor que inyectaremos.
En la DI basada en setter, podemos colocar estas anotaciones en dos lugares. El primero es directamente sobre el setter del atributo que vamos a establecer, en el caso de "repeticiones" sería de esta forma:
@Value(value="5")
public void setRepeticiones(Integer repeticiones)
{
this.repeticiones = repeticiones;
}
El segundo es directamente en el atributo que queremos establecer, de esta forma:
@Value(value="5")
private Integer repeticiones;
Yo usaré esta segunda forma, ya que me parece un poco más clara que la anterior. Al final, nuestra clase "ServicioRemoto" anotada queda de la siguiente forma:
@Service(value="servicioRemoto")
public class ServicioRemoto
{
private Proceso proceso;
@Value(value="5")
private Integer repeticiones;
public ServicioRemoto()
{
}
@Autowired
public ServicioRemoto(@Qualifier("calculo")Proceso proceso)
{
this.proceso = proceso;
}
public Object consultaDato()
{
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < repeticiones; i++)
{
stringBuilder.append(i + 1).append(" - ").append(proceso.ejecuta()).append("\n");
}
return stringBuilder.toString();
}
public void setRepeticiones(Integer repeticiones)
{
this.repeticiones = repeticiones;
}
}
Si ejecutamos este código veremos la siguiente salida en consola:
El valor es: 1 - 8
2 - 64
3 - 50
4 - 45
5 - 50
Como vemos, nuevamente ha sido muy fácil modificar nuestra aplicación para agregar este tipo de inyección de dependencias.
¿Cuál Forma de Inyección de Dependencias Elegir?
La elección entre cuál forma de inyección de dependencia, por setter o por constructor, elegir no es fácil, ya que ambas tienen sus ventajas y sus desventajas.Spring nos permite elegir el método que más nos guste (o nos convenga), además de que nos permite mezclar ambas formas, como lo hemos hecho en el ejemplo. Sin embargo hay una “regla” o consejo que podemos seguir: usar DI basada en constructor para las dependencias obligatorias y la DI basada en setter para las dependencias opcionales.
El equipo desarrollador de Spring (y yo también) generalmente sugieren usar inyección por setter, porque tener constructores con un número grande de argumentos pueden ser engorrosos, especialmente si algunos de los argumentos son opcionales. Los métodos setter también hacen que los objetos de esas clases sean más fáciles de reconfigurar o re-inyectar después.
Lo mejor es usar el tipo de DI que tenga más sentido para la clase en la que estamos trabajando.
Dejaremos este tutorial aquí. Hemos aprendido a realizar inyección de dependencias de las dos formas que nos proporciona Spring: basada en constructor y basada en setter, y a inyectar referencias a otros objetos y valores constantes, tanto con archivos de configuración en XML como con anotaciones. Solo hemos visto un par de ejemplos muy simples, pero recuerden que usando esta misma lógica pueden extender estos ejemplos para armar aplicaciones grandes.
También es importante recordar que es posible combinar ambas formas de configuración, anotando unos beans, y declarando otros en el archivo de configuración XML.
Espero que el tutorial les sea de utilidad. En los siguientes tutoriales veremos cómo inyectar colecciones en nuestros beans, además del scope y ciclo de vida de los mismos para saber cómo sacar una mayor ventaja de estos.
No olviden dejar sus dudas, comentarios, y sugerencias. Todo es bien recibido ^_^.
Saludos.
Descarga los archivos de este tutorial desde aquí:
- IoC con Archivos de Mapeo usando BeanFactory
- IoC con Archivos de Mapeo usando ApplicationContext
- IoC con Anotaciones y XML
- IoC con Anotaciones
Entradas Relacionadas: