2. Introducción al marco Spring
Spring surgió en 2004, inicialmente como un contenedor de objetos. Desde entonces, ha evolucionado en múltiples ramificaciones: Spring MVC, Spring Data, Spring Batch, ... [http://spring.io]. En este capítulo solo presentaremos el contenedor de objetos. A continuación, algunos puntos de referencia:
- una aplicación tiene múltiples clases y algunas de ellas comparten objetos que deben ser únicos (singletons). Spring crea y administra estos singletons;
- Spring coloca estos singletons en una estructura llamada contexto;
- las clases tienen acceso a los singletons de la aplicación solicitándolos a Spring mediante su nombre, su tipo o ambos;
- Spring crea los singletons y administra sus posibles dependencias: de hecho, un singleton puede tener referencias a uno o más singletons. Cuando Spring crea un singleton, también crea sus dependencias;
- cuando se inicia una aplicación basada en Spring, esta puede solicitarle a Spring que cree todos los singletons de la aplicación. Estos quedarán disponibles posteriormente en el contexto de Spring;
- Spring facilita el uso de arquitecturas en capas y la programación por interfaces. En los casos sencillos, cada capa es implementada por un singleton y implementa una interfaz. Si la aplicación trabaja con las interfaces de las capas y no con sus clases de implementación, se obtiene una arquitectura escalable que permite cambiar la implementación de una capa sin modificar las demás gracias a las dos características siguientes:
- la aplicación obtiene una referencia a la capa a través de su nombre. Spring le proporciona una referencia a la clase que implementa la capa;
- la aplicación utiliza esta referencia como si fuera la de la interfaz de la capa y no como la de una clase;
La declaración de los singletons puede realizarse de tres formas que pueden combinarse:
- dentro de un archivo XML,
- en una clase especial de configuración;
- con cualquier clase mediante anotaciones;
A continuación presentamos tres ejemplos de configuración:
- [exemple-01]: configuración centralizada en un único archivo XML;
- [exemple-02]: configuración centralizada en una única clase Java;
- [exemple-03]: configuración distribuida en varias clases Java;
El último ejemplo, [exemple-04], se centra en la configuración de Spring para una arquitectura en capas. Este es el ejemplo más importante. Se utilizará constantemente para configurar las arquitecturas del documento.
Estos cuatro ejemplos sientan las bases de lo que sigue:
- configuración de Spring e inyección de dependencias;
- uso de Maven para gestionar las dependencias de un proyecto;
- uso de JUnit para probar los proyectos;
2.1. Configuración del entorno de trabajo
Debes tener:
- tener instalado el JDK (Java Development Kit) (párrafo 23.1);
- tener instalado el gestor de dependencias Maven (párrafo 23.2);
- tener instalado el IDE Spring Tool Suite (STS) (párrafo 23.3);
- descargue los códigos del documento [http://tahe.developpez.com/java/spring-database];
Importa a STS las configuraciones de ejecución de la carpeta [eclipse config] de los ejemplos. Estas configuraciones son particularmente importantes. Algunos proyectos requieren, para ejecutarse, pasar argumentos a JVM, y este tipo de configuración suele ser un dolor de cabeza. Además, este documento utiliza proyectos Maven. Cuando te encuentres con la advertencia:
Nota: ejecute [Alt-F5] para regenerar todos los proyectos de Maven.
Se recomienda encarecidamente seguir esta indicación. Sin esta precaución, los proyectos pueden presentar errores incomprensibles simplemente porque las dependencias de Maven entre proyectos son incorrectas.
![]() |
- en [1], haz clic con el botón derecho en [Package Explorer];
![]() |
- en [4a-4b-4c], selecciona la carpeta [eclipse config / launch configurations] [4b] de ejemplos;
- en [5], las configuraciones disponibles. Seleccione todas;
- en [6], finalice el asistente;
- en [7-8], visualice las configuraciones de ejecución importadas;
![]() |
- en [8-9], desmarca [9] para mostrar las configuraciones de proyectos no cargados en STS. Este es el caso actualmente;
![]() |
- en [10], las aplicaciones Java, y en [11], las configuraciones de ejecución de los tres primeros ejemplos que vamos a estudiar;
- en [12], las pruebas JUnit y en [13] la configuración de ejecución del cuarto ejemplo de este párrafo;
Ahora, crea una variable de Eclipse llamada [M2_REPO] que indicará la carpeta del repositorio local de Maven (véase el párrafo 23.2). Esta variable se utiliza en varias configuraciones de ejecución:
![]() |
A la variable [M2_REPO] se le ha asignado el valor que se muestra en [6] a continuación:
![]() |
Ahora, importe los cuatro ejemplos de la carpeta [spring-core]:
![]() |
- en [1], haz clic con el botón derecho en [Package Explorer];
![]() |
- a [4a-4b], selecciona la carpeta [spring-core] de los ejemplos;
- en [5], selecciona todos los proyectos de la carpeta y haz [Finish];
- en [6], los cuatro proyectos en [Package Explorer];
2.2. Exemple-01
2.2.1. El proyecto Eclipse
![]() |
2.2.2. La clase [Personne]
![]() |
package istia.st.spring.core;
public class Personne {
// campos
private String nom;
private String prenom;
private int age;
// constructores
public Personne() {
}
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prenom = prénom;
this.age = âge;
}
// toString
public String toString() {
return String.format("Personne[%s, %s,%d]", prenom, nom, age);
}
// getters y setters
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Nota: los métodos getter y setter se pueden generar automáticamente de la siguiente manera [1-2]:
![]() |
2.2.3. La clase [Appartement]
![]() |
package istia.st.spring.core;
public class Appartement {
// campos
private Personne proprietaire;
private int surface;
// getters y setters
public Personne getProprietaire() {
return proprietaire;
}
public void setProprietaire(Personne proprietaire) {
this.proprietaire = proprietaire;
}
public int getSurface() {
return surface;
}
public void setSurface(int surface) {
this.surface = surface;
}
// toString
public String toString() {
return String.format("Appartement[%s, %s]", proprietaire, surface);
}
}
Nota: esta clase no tiene un constructor explícito. En este caso, siempre existe por defecto el constructor sin parámetros que no hace nada. Cuando se crean constructores, este constructor por defecto ya no existe implícitamente. Por lo tanto, hay que definirlo explícitamente:
2.2.4. El archivo de configuración de Spring
![]() |
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- Persona 01 -->
<bean id="personne_01" class="istia.st.spring.core.Personne">
<constructor-arg index="0" value="dubois" />
<constructor-arg index="1" value="paul" />
<constructor-arg index="2" value="34" />
</bean>
<!-- Persona 02 -->
<bean id="personne_02" class="istia.st.spring.core.Personne">
<property name="nom" value="martin" />
<property name="prenom" value="micheline" />
<property name="age" value="18" />
</bean>
<!-- una lista de personas -->
<util:list id="club">
<ref bean="personne_01" />
<ref bean="personne_02" />
</util:list>
<!-- un departamento -->
<bean id="appartement" class="istia.st.spring.core.Appartement">
<property name="surface" value="100" />
<property name="proprietaire" ref="personne_01" />
</bean>
</beans>
- líneas 2 y 27: los singletons se definen dentro de una etiqueta <beans>;
- líneas 6-10: cada singleton se define mediante una etiqueta <bean>;
- línea 6: [id] es el identificador del singleton. [class] es el nombre completo de la clase que se va a instanciar;
- líneas 7-9: los tres valores que se deben pasar al constructor de la clase [Personne];
- líneas 12-16: primero se crea la clase [Personne] con su constructor por defecto [new Personne()]. Luego, para cada etiqueta [property], se utiliza un método setter de la clase. Por ejemplo, para la línea 13, se ejecutará el método [setNom("martin")]. Por lo tanto, es necesario que el método [setNom] exista. Es importante recordar esto;
- líneas 18-21: la etiqueta <util:list> permite definir un singleton que es una lista;
- línea 19: hace referencia al singleton [personne_01] definido en la línea 6. Aquí tenemos lo que se conoce como inyección de dependencias. Se pueden utilizar dos atributos para inicializar el campo de un singleton:
- [value]: para asignar al campo un valor primitivo (cadena, número, fecha, etc.),
- [ref]: para asignar al campo la referencia de un objeto de Spring;
Nota: el archivo de configuración de Spring se puede generar de la siguiente manera [1-4]:
![]() |
2.2.5. La clase ejecutable
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo01 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// recuperación del contexto de Spring
ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
// se recuperan los beans
Personne p01 = ctx.getBean("personne_01", Personne.class);
Personne p02 = ctx.getBean("personne_02", Personne.class);
List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
Appartement appart01 = ctx.getBean(Appartement.class);
// se muestran
System.out.println("personnes--------");
System.out.println(p01);
System.out.println(p02);
System.out.println("club--------");
for (Personne p : club) {
System.out.println(p);
}
System.out.println("appartement--------");
System.out.println(appart01);
// los beans recuperados son singletons
// se pueden solicitar varias veces; siempre se recupera el mismo bean
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- línea 14: crea el contexto de Spring. A continuación, se instancian todos los singletons definidos en el archivo [config-01.xml];
- línea 16: solicita una referencia sobre el singleton identificado por [personne_01], de tipo [Personne]. Este segundo parámetro es opcional, pero en ese caso se recibe una referencia de tipo [Object], referencia que luego hay que convertir al tipo [Personne];
- línea 19: no se utiliza el nombre del bean, sino únicamente su tipo, ya que solo hay un singleton de tipo [Appartement];
- línea 18: se utilizaron tanto el identificador como el tipo del singleton deseado. El identificador es innecesario, ya que solo hay un singleton de tipo [new ArrayList<Personne>().getClass()];
- líneas 32-33: muestran que si se solicita varias veces el mismo singleton, siempre se obtiene la misma referencia, lo que demuestra que efectivamente se trata de un singleton. Es importante comprender este punto;
Nota: se puede generar una clase ejecutable de la siguiente manera [1-6]:
![]() |
![]() |
- Al marcar [6], la clase generada contendrá un método estático [main] que la hará ejecutable;
2.2.6. Las dependencias del proyecto
![]() |
- Dependencias de Spring: [spring-core, spring-beans, spring-context, spring-expression, commons-logging];
Las dependencias se agregan al proyecto de la siguiente manera:
![]() |
- en [1]: clic derecho en el proyecto / [Build Path] / [Configure Build Path];
![]() |
- a [2]: [Add JARs] si los archivos JARs que se van a agregar se encuentran en una carpeta del proyecto. De lo contrario, [Add External JARs];
![]() |
- en [3], seleccione los JARs que se deben agregar al ClassPath del proyecto (en este caso, se encuentran en la carpeta [lib] dentro del proyecto);
Definición: el [ClassPath] de un proyecto es el conjunto de carpetas exploradas por el JVM (Máquina Virtual Java) que ejecuta el proyecto, para encontrar una clase a la que este hace referencia. Para un proyecto de Eclipse, el [ClassPath] está formado por los siguientes elementos:
- la carpeta [bin] del proyecto;
- los elementos del [Build Path] del proyecto;
La carpeta [bin] es la carpeta generada al compilar la carpeta [src]. Por lo tanto, todo lo que se coloque en la carpeta [src] forma parte automáticamente de la carpeta [ClassPath] (aunque no sea un archivo .java). Por lo tanto, en el proyecto anterior, el archivo de configuración de Spring [config-01.xml], que se encuentra en la carpeta [src], formará parte de [Classpath] del proyecto en el momento de la ejecución.
2.2.7. Los resultados
2.3. Exemple-02
2.3.1. El proyecto Eclipse
![]() |
2.3.2. La clase de configuración de Spring
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public Personne personne_01() {
return new Personne("Paul", "Dubois", 34);
}
@Bean
public Personne personne_02() {
return new Personne("Martin", "Micheline", 18);
}
@Bean
public List<Personne> club(Personne personne_01, Personne personne_02) {
List<Personne> personnes = new ArrayList<Personne>();
personnes.add(personne_01);
personnes.add(personne_02);
return personnes;
}
@Bean
public Appartement appartement(Personne personne_01) {
Appartement appartement = new Appartement();
appartement.setSurface(200);
appartement.setPropriétaire(personne_01);
return appartement;
}
}
- línea 9: la anotación [@Configuration] es una anotación de Spring. Indica que la clase anotada define singletons. Estos se definen mediante la anotación [@Bean]. Spring ejecutará todos los métodos anotados con [@Bean]. Estos crean los singletons de la aplicación;
- líneas 12-15: definen un singleton identificado por [personne_01], es decir, el nombre del método.
- línea 23: los parámetros [personne_01, personne_02] llevan los nombres de los singletons. Spring los inicializará automáticamente con las referencias de dichos singletons. A esto se le llama inyección de parámetros;
Esta forma de configurar los singletons es más explícita que la que utiliza el archivo XML. De hecho, estamos reproduciendo nosotros mismos lo que Spring hacía implícitamente a partir del archivo XML.
2.3.3. La clase ejecutable
![]() |
package istia.st.spring.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo02 {
@SuppressWarnings({ "unchecked", "resource" })
public static void main(String[] args) {
// se recupera el contexto de Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
// se recuperan los beans
Personne p01 = ctx.getBean("personne_01", Personne.class);
Personne p02 = ctx.getBean("personne_02", Personne.class);
List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
Appartement appart01 = ctx.getBean(Appartement.class);
// se muestran
System.out.println("personnes--------");
System.out.println(p01);
System.out.println(p02);
System.out.println("club--------");
for (Personne p : club) {
System.out.println(p);
}
System.out.println("appartement--------");
System.out.println(appart01);
// los beans recuperados son singletons
// se pueden solicitar varias veces; siempre se recupera el mismo bean
Personne p01b = ctx.getBean("personne_01", Personne.class);
System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
}
}
- la línea 13 provoca la instanciación de todos los beans definidos en la clase [Config];
- el resto del código no cambia;
2.3.4. Las dependencias del proyecto
![]() | ![]() |
Las dependencias se definen en el siguiente archivo [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-02</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
La gestión manual de las dependencias de un proyecto se convierte en un dolor de cabeza cuando se utilizan bibliotecas de Java cuyas dependencias se desconocen. Por ejemplo, el marco [Hibernate], que administra el acceso a las bases de datos, tiene decenas de dependencias. El proyecto [Maven] resuelve este problema. Se indica el nombre de la dependencia que se necesita. Esta se busca automáticamente en los repositorios de Maven disponibles en Internet. Si la dependencia solicitada tiene a su vez otras dependencias, estas también se descargan automáticamente. Estas dependencias descargadas se almacenan en un repositorio local en la computadora. Si más adelante otra aplicación necesita la misma dependencia, esta no se descargará, sino que se buscará en el repositorio local. Una dependencia se caracteriza por los siguientes elementos:
- línea 17: una etiqueta <dependency>;
- línea 18: un atributo [groupId] que, por lo general, identifica a la empresa que creó la dependencia;
- línea 19: un atributo [artifactId] que identifica la dependencia;
- línea 20: un atributo [version] que identifica la versión deseada;
La generación del proyecto producirá a su vez un componente Maven definido por las líneas 4 a 8:
- líneas 4 a 6: los atributos [ groupId, artifactId,version] que acabamos de describir;
- líneas 7-8: son atributos opcionales;
Más adelante volveremos a abordar la función de las líneas 24 a 40. Para convertir un proyecto Eclipse común en un proyecto Maven, hay que hacer dos cosas:
- crear el archivo [pom.xml] anterior;
- declarar que el proyecto es ahora un proyecto Maven [1-4]:
![]() |
El ícono de un proyecto Maven tiene una M [4]. La S indica que el proyecto contiene elementos de Spring. No se recomienda convertir (como acabamos de hacerlo) un proyecto de Eclipse en un proyecto Maven, ya que, al hacerlo, el proyecto no tiene la estructura esperada para un proyecto Maven, lo que a veces puede generar problemas inesperados.
2.3.5. Generación del artefacto Maven del proyecto
Denominamos «artefacto Maven del proyecto» al elemento definido en las líneas 4 a 6 del archivo [pom.xml]:
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
Para generar este artefacto, deben estar presentes las siguientes líneas 3 a 7 en el archivo [pom.xml]:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
Estas definen el complemento de Maven capaz de generar el artefacto del proyecto. A continuación, se procede de la siguiente manera:
![]() |
El artefacto así generado se guarda en el repositorio local de Maven. La ubicación de este se puede encontrar en la configuración de Eclipse:
![]() |
A continuación, es posible verificar que el artefacto de Maven se haya instalado correctamente:
![]() |
A partir de ahora, otro proyecto local de Maven podrá utilizar este archivo.
2.4. Exemple-03
2.4.1. El proyecto Eclipse
En esta ocasión, creamos un proyecto Maven [1-8]:
![]() |
![]() |
- en [3b]: seleccione una carpeta vacía donde se generará el proyecto;
![]() |
- en [4]: el identificador del grupo de Maven al que pertenecerá el proyecto;
- en [5]: el nombre del artefacto Maven generado;
- en [6]: su versión;
- en [7]: su modo de empaquetado (también existen war, ear, apk, etc.);
- en [8]: el proyecto así creado;
Un proyecto de Maven tiene, por defecto, una estructura de directorios específica:
- [src / main / java]: el código fuente del proyecto. Los productos compilados a partir de este código fuente se guardarán en la carpeta [target/classes] del proyecto;
- [src / main / resources]: los recursos que deben estar en el Classpath del proyecto sin ser necesariamente código fuente Java. Se copiarán tal cual en la carpeta [target/classes] del proyecto;
- [src / test / java]: los códigos fuente de las pruebas del proyecto. Los productos compilados a partir de estos códigos fuente se colocarán en la carpeta [target/test-classes] del proyecto. Estos elementos no se incluyen en el archivo Maven del proyecto;
- [src / test / resources]: los recursos que deben estar en el Classpath del proyecto para las pruebas, sin ser necesariamente código fuente de Java. Se copiarán tal cual en la carpeta [target/test-classes] del proyecto;
Completamos el proyecto de la siguiente manera:
![]() |
2.4.2. La configuración de Maven
Se genera un archivo [pom.xml] de forma predeterminada. Lo modificamos de la siguiente manera:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-core-03</name>
<description>Introduction à Spring</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<!-- proyecto padre de Maven -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Contexto de Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- registros -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<!-- para generar el archivo del proyecto con sus dependencias -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- para la instalación del artefacto del proyecto en el repositorio local de Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- línea 11: el proyecto está codificado en UTF-8;
- línea 12: se utiliza un JDK 1.8 para compilar el proyecto;
- líneas 16-20: para los proyectos que utilizan las bibliotecas de Spring, resulta práctico usar un proyecto padre de Maven llamado [spring-boot-starter-parent]. Este define las versiones de las diferentes bibliotecas de Spring, así como las de sus dependencias. Esto permite no tener que definirlas en la definición de dependencias. Por lo tanto, en las líneas 24-27 no se especifica la versión deseada de [spring-context]. Será la definida por el proyecto padre [spring-boot-starter-parent]. Esta técnica permite no preocuparse por posibles incompatibilidades de versiones entre dependencias. Las definidas por el proyecto padre son compatibles entre sí;
- líneas 29-32: Spring genera una gran cantidad de información en la consola a través de una biblioteca de registros. Esta se importa aquí;
- líneas 40-47: un complemento de Maven sobre el que volveremos más adelante;
- líneas 50-52: el complemento para generar el artefacto Maven del proyecto;
2.4.3. La clase de configuración de Spring
![]() |
La clase [Config] es la siguiente:
package istia.st.spring.core.config;
import istia.st.spring.core.entities.Personne;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {
@Bean
public Personne personne_01() {
return new Personne("Paul", "Dubois", 34);
}
@Bean
public Personne personne_02() {
return new Personne("Martin", "Micheline", 18);
}
@Bean
public List<Personne> club(Personne personne_01, Personne personne_02) {
List<Personne> personnes = new ArrayList<Personne>();
personnes.add(personne_01);
personnes.add(personne_02);
return personnes;
}
@Bean
public int mySurface() {
return 200;
}
}
- Aquí encontramos un código ya comentado con dos novedades:
- línea 13: indica que hay otros beans que deben instanciarse en el paquete [spring.core.entities],
- líneas 34-37: un bean [mySurface];
2.4.4. La clase [Appartement]
![]() |
package spring.core.entities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Appartement {
// campos inyectados por Spring
@Autowired
@Qualifier("personne_01")
private Personne propriétaire;
@Autowired
@Qualifier("mySurface")
private int surface;
// getters y setters
public Personne getPropriétaire() {
return propriétaire;
}
public void setPropriétaire(Personne propriétaire) {
this.propriétaire = propriétaire;
}
public int getSurface() {
return surface;
}
public void setSurface(int surface) {
this.surface = surface;
}
// toString
public String toString() {
return String.format("Appartement[%s, %s]", propriétaire, surface);
}
}
- línea 7: la anotación [@Component] le indica a Spring que la clase es un singleton que el marco debe instanciar y administrar. Es porque en la clase [Config] escribimos [@ComponentScan({ "istia.st.spring.core.entities" })] que se encontrará este singleton;
- línea 11: le pide a Spring que inyecte en el campo la referencia de uno de los singletons. Esta puede definirse de dos maneras:
- por su identificador (líneas 12, 16),
- por su tipo, si solo hay un singleton de ese tipo;
2.4.5. Ejecución del proyecto
La ejecución del proyecto arroja el siguiente resultado en la consola:
- líneas 1-3: Spring genera una gran cantidad de registros, varias decenas de líneas. Estos registros pueden ser muy útiles para depurar un proyecto que no funciona. Cuando funciona, se pueden reducir los registros de la siguiente manera:
![]() |
En la carpeta [src / main / resources] se crea el siguiente archivo [logback.xml]:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- a los codificadores se les asigna por defecto el tipo
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- control del nivel de los registros -->
<root level="info"> <!-- información, depuración, advertencia -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
- En la línea 12, se establece el nivel de los registros. [debug] es un nivel muy detallado, mientras que [info] lo es mucho menos;
Estos son los resultados con [level=info]:
Ya solo hay una línea de registros.
2.4.6. Generación del archivo del proyecto con sus dependencias
El archivo creado en el proyecto anterior también puede ser utilizado por un proyecto de Eclipse que no sea de Maven. Algunos proyectos utilizan numerosas bibliotecas y puede ser complicado no olvidarse de alguna. Ahí es donde Maven hace maravillas, ya que basta con nombrar la dependencia de nivel más alto para que las demás de nivel inferior se agreguen automáticamente a la ruta de clases del proyecto. Cuando un proyecto de Eclipse que no utiliza Maven necesita usar los archivos de un proyecto de Maven, es posible generar el artefacto de este último con todas sus dependencias (lo cual no era el caso en el proyecto anterior). Para esta generación, es necesario que las siguientes líneas 3 a 10 estén presentes en el archivo [pom.xml]:
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
Estas definen el complemento de Maven capaz de generar el artefacto del proyecto con sus dependencias. A continuación, se procede de la siguiente manera [1-6]:
![]() |
![]() |
- [4-6] representan una configuración de ejecución de Maven;
- en [4], pon cualquier nombre;
- en [5], indica la carpeta del proyecto;
- en [6], ingresa los objetivos de Maven (goals):
- [clean]: se elimina la carpeta [target] del proyecto;
- [compile]: se compila el proyecto. Los resultados de la compilación se colocan en una carpeta [target] regenerada;
- [assembly:single]: las clases del proyecto y sus dependencias se colocan en un único archivo jar dentro de la carpeta [target];
Tras la ejecución, se obtiene el siguiente resultado:
![]() |
Un archivo JAR es un archivo comprimido que, por lo tanto, se puede abrir con un descompresor. Una vez descomprimido el archivo anterior, se obtiene la siguiente estructura de directorios:
- en [8], las clases de las dependencias del proyecto;
- en [9], las clases del proyecto en sí;
2.5. Exemple-04
2.5.1. Objetivo
Este ejemplo retoma uno de los presentados en el documento [Introduction à Spring IoC], en el que se muestra la contribución de Spring a la configuración de arquitecturas de múltiples capas. En el documento original, el ejemplo se aborda con una configuración de Spring realizada mediante un archivo XML. Aquí tratamos el ejemplo con una configuración mediante clases de Java y anotaciones.
El objetivo es configurar un proyecto de Spring para la siguiente arquitectura:
![]() |
Cada capa presenta una interfaz implementada con dos clases. Queremos demostrar que, gracias a Spring, se puede cambiar la implementación de una capa sin que ello afecte en absoluto al código de las demás capas.
2.5.2. El proyecto de Eclipse
2.5.2.1. Génération
Creamos un nuevo tipo de proyecto:
![]() |
![]() |
- en [4], ingresa el nombre del proyecto de Eclipse;
- en [5], elige un proyecto Maven;
- en [6], elige una versión de Java >=1.7;
- en [7], elige la versión de Spring Boot que se propone;
- la información de [8-11] corresponde a Maven;
- en [12], se puede elegir una o varias de las dependencias propuestas. Esto tendrá como efecto integrar en el archivo [pom.xml] de Maven una serie de dependencias;
![]() |
- en [13], se debe seleccionar una carpeta existente y vacía para alojar el proyecto;
- en [14], el proyecto generado. Analizaremos sus elementos;
El proyecto es un proyecto de Maven configurado por el siguiente archivo [pom.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-core-04</name>
<description>Programmation par interfaces</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- búsqueda del padre en el repositorio -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SpringCore04Application</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- líneas 6-12: recogen la información ingresada en el asistente de creación del proyecto;
- líneas 14-19: el proyecto Maven principal que define varias bibliotecas con sus versiones. Si alguna de ellas es una dependencia del proyecto, se menciona en el archivo [pom.xml] sin indicar su versión;
- línea 23: esta línea solo sirve si se tiene la intención de generar un archivo ejecutable del proyecto. De lo contrario, no se utiliza;
- líneas 28-31: la dependencia mínima de un proyecto Spring Boot. Recordemos que no hemos seleccionado ninguna dependencia en la lista de opciones;
- líneas 33-37: la dependencia necesaria para gestionar pruebas unitarias JUnit y [http://junit.org/] integradas con Spring. La línea 36 indica que la dependencia solo es necesaria para las pruebas. Por lo tanto, no se incluirá en el archivo del proyecto;
- líneas 42-45: el complemento que permite generar el artefacto Maven del proyecto;
La lista de dependencias que incluye este archivo es la siguiente: [1]:
![]() |
Veremos que son suficientes para lo que queremos hacer aquí.
2.5.2.2. La clase ejecutable
![]() |
La clase ejecutable [SpringCore04Application] [[2] es la siguiente:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
SpringApplication.run(SpringCore04Application.class, args);
}
}
- En la línea 6, la anotación [@SpringBootApplication] es un atajo para las tres anotaciones [@Configuration, @EnableAutoConfiguration, @ComponentScan], lo que significa:
- que la clase [SpringCore04Application] es una clase de configuración de Spring;
- que se le pide a Spring Boot que realice configuraciones a partir de las clases que encuentre en el Classpath del proyecto, es decir, en este caso, en las dependencias de Maven;
- que revise la carpeta actual (la de la clase [SpringCore04Application]) para encontrar otros posibles componentes de Spring;
- línea 10: se ejecuta el método estático [SpringApplication.run]. Su primer parámetro es una clase de configuración de Spring, en este caso la clase [SpringCore04Application]. Su segundo parámetro es, en este caso, la lista de argumentos pasados al método [main] (línea 9). El método estático [SpringApplication.run] tiene la función de crear el contexto de Spring, es decir, crear los distintos beans que se encuentran ya sea en las clases de configuración o en las carpetas exploradas por la anotación [@ComponentScan]. El método [main] aquí no hace nada más. Para darle un poco más de sustancia, lo transformaremos de la siguiente manera:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringCore04Application {
public static void main(String[] args) {
// instanciación del contexto de Spring
ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
// visualización del contexto
System.out.println("---------------- Liste des beans Spring");
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName);
}
// cierre del contexto
context.close();
}
}
- línea 12: el método estático [SpringApplication.run] devuelve el contexto de Spring que ha construido;
- líneas 15-17: se muestra el nombre de todos los beans de este contexto;
Podemos ejecutar la aplicación de la siguiente manera: [1-3]. El método habitual [Run As Java Application] también es válido.
![]() |
Se obtiene el siguiente resultado:
- líneas 14-28: los beans del contexto de Spring. No sabemos cuál es su función. Encontramos el bean [springCore04Application] en la línea 18, que, debido a su anotación [@SpringBootApplication], se convierte automáticamente en un bean de Spring;
- las demás líneas son registros de Spring de nivel [INFO]. Como ya hemos visto, estos registros pueden controlarse mediante el archivo [logback.xml] ubicado en el Classpath del proyecto:
![]() |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- A los codificadores se les asigna por defecto el tipo
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- control del nivel de los registros -->
<root level="warn"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Si en la línea 12 anterior cambiamos el nivel [warn] por [info], obtenemos el siguiente resultado:
Los registros han desaparecido. Solo aparecen los mensajes de nivel [warn] y aquí no hubo ninguno.
2.5.3. Implementación de las diferentes capas de la arquitectura
![]() |
Ahora implementaremos las tres capas de la arquitectura anterior:
![]() |
La capa [DAO] se implementa mediante el paquete [spring.core.dao]. Presenta la siguiente interfaz [IDao]:
package spring.core.dao;
public interface IDao {
public int doSomethingInDaoLayer(int a, int b);
}
Esta interfaz tiene dos implementaciones: [Dao1] y [Dao2]:
package spring.core.dao;
public class Dao1 implements IDao {
public int doSomethingInDaoLayer(int a, int b) {
return a+b;
}
}
package spring.core.dao;
public class Dao2 implements IDao {
public int doSomethingInDaoLayer(int a, int b) {
return a-b;
}
}
La capa [métier] está implementada por el paquete [spring.core.metier]. Presenta la siguiente interfaz [IMetier]:
package spring.core.metier;
public interface IMetier {
public int doSomethingInMetierLayer(int a, int b);
}
Esta interfaz tiene dos implementaciones: [Metier1] y [Metier2]:
package spring.core.metier;
import spring.core.dao.IDao;
public class Metier1 implements IMetier {
private IDao dao;
public int doSomethingInMetierLayer(int a, int b) {
a++;
b++;
return dao.doSomethingInDaoLayer(a, b);
}
public void setDao(IDao dao) {
this.dao = dao;
}
}
package spring.core.metier;
import spring.core.dao.IDao;
public class Metier2 implements IMetier {
private IDao dao;
public int doSomethingInMetierLayer(int a, int b) {
a--;
b++;
return dao.doSomethingInDaoLayer(a, b);
}
public void setDao(IDao dao) {
this.dao = dao;
}
}
La capa [UI] está implementada por el paquete [spring.core.ui]. Presenta la siguiente interfaz [IUi]:
package spring.core.ui;
public interface IUi {
public int doSomethingInUiLayer(int a, int b);
}
Esta interfaz tiene dos implementaciones: [Ui1] y [Ui2]:
package spring.core.ui;
import spring.core.metier.IMetier;
public class Ui1 implements IUi {
private IMetier metier;
public int doSomethingInUiLayer(int a, int b) {
a++;
b++;
return metier.doSomethingInMetierLayer(a, b);
}
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
package spring.core.ui;
import spring.core.metier.IMetier;
public class Ui2 implements IUi {
private IMetier metier;
public int doSomethingInUiLayer(int a, int b) {
a--;
b++;
return metier.doSomethingInMetierLayer(a, b);
}
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
2.5.4. Configuración del proyecto Spring
![]() |
La clase de configuración [Config] es la siguiente:
package spring.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;
@Configuration
public class Config {
// -------------- implementación [Ui1, Metier1, Dao1]
@Bean
public IDao dao1() {
return new Dao1();
}
@Bean
public IMetier metier1(IDao dao1) {
Metier1 metier = new Metier1();
metier.setDao(dao1);
return metier;
}
@Bean
public IUi ui1(IMetier metier1) {
Ui1 ui = new Ui1();
ui.setMetier(metier1);
return ui;
}
// -------------- implementación [Ui2, Metier2, Dao2]
@Bean
public IDao dao2() {
return new Dao2();
}
@Bean
public IMetier metier2(IDao dao2) {
Metier2 metier = new Metier2();
metier.setDao(dao2);
return metier;
}
@Bean
public IUi ui2(IMetier metier2) {
Ui2 ui = new Ui2();
ui.setMetier(metier2);
return ui;
}
}
- líneas 20-23: el bean denominado [dao1] (nombre del método) es una instancia de la clase [Dao1] (línea 22), considerada como una implementación de la interfaz [IDao] (línea 21). Por lo tanto, el bean [dao1] se considera una instancia de interfaz (la terminología es incorrecta, pero se puede entender) y no una instancia de clase. Es importante comprender este punto. Todos los demás beans también serán instancias de interfaces;
- líneas 25-30: una instancia de la interfaz [IMetier] implementada por la clase [Metier1];
- líneas 32-37: una instancia de la interfaz [IUi] implementada por la clase [Ui1];
- líneas 20-37: implementan las capas [UI, Metier, DAO] con instancias de [Ui1, Metier1, Dao1];
- líneas 40-57: implementan las capas [UI, Metier, DAO] con instancias de [Ui2, Metier2, Dao2];
2.5.5. Prueba unitaria [JUnitTest]
![]() |
La clase [JUnitTest] se coloca en la rama [src / test / java] del proyecto Maven. Los elementos de esta rama no se incluyen en el archivo final del proyecto. Su código es el siguiente:
package spring.core.tests;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;
@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
- línea 16: la anotación [@SpringApplicationConfiguration] es una anotación del proyecto Spring Boot Test (línea 8). Se incluye mediante la siguiente dependencia del archivo [pom.xml]:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
Esta anotación tiene como parámetro la lista de clases de configuración que se utilizarán para construir el contexto de Spring necesario para la prueba. Aquí utilizamos la clase de configuración [Config] ya presentada;
- línea 17: la anotación [@RunWith] es una anotación JUnit (línea 5). Su parámetro es la clase encargada de ejecutar las pruebas en lugar de la clase predeterminada del marco JUnit. Esta clase es una clase de Spring (línea 9). Utilizará las anotaciones de Spring presentes en la clase de prueba;
La clase completa es la siguiente
...
@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
// capa [UI]
@Autowired
@Qualifier("ui1")
private IUi ui1;
@Autowired
@Qualifier("ui2")
private IUi ui2;
// capa [métier]
@Autowired
@Qualifier("metier1")
private IMetier metier1;
@Autowired
@Qualifier("metier2")
private IMetier metier2;
// capa [dao]
@Autowired
@Qualifier("dao1")
private IDao dao1;
@Autowired
@Qualifier("dao2")
private IDao dao2;
@Test
public void testDao() {
Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
}
@Test
public void testMetier() {
Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
}
@Test
public void testUI() {
Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
}
}
- líneas 8-10: se inyecta (línea 8) el bean denominado (línea 9) [ui1]. Cabe destacar en la línea 10 que se inyecta una instancia de interfaz y no una instancia de clase;
- líneas 21-32: se inyectan de la misma manera los demás beans definidos en la clase [Config];
- línea 34: la anotación [@Test] designa un método que se debe ejecutar durante las pruebas. Las otras anotaciones posibles son las siguientes:
- [@BeforeClass]: método que se debe ejecutar antes de comenzar las pruebas;
- [@AfterClass]: método que se debe ejecutar una vez que se hayan realizado todas las pruebas;
- [@Before]: método que se debe ejecutar antes de cada prueba;
- [@After]: método que se debe ejecutar después de cada prueba;
- línea 36: se verifica que la llamada a [dao1.doSomethingInDaoLayer(10, 20)] devuelva efectivamente 30. Por convención, el primer parámetro es el valor esperado y el segundo, el valor real;
- línea 36: prueba la instancia [dao1] de la interfaz [IDao];
- línea 37: prueba la instancia [dao2] de la interfaz [IDao];
- línea 42: prueba la instancia [metier1] de la interfaz [IMetier];
- línea 43: prueba la instancia [metier2] de la interfaz [IMetier];
- línea 48: prueba la instancia [ui1] de la interfaz [IUi];
- línea 36: prueba la instancia [ui2] de la interfaz [IUi];
Las aserciones que se pueden utilizar en un método de prueba son las siguientes:
- assertEquals(expresión1, expresión2): verifica que los valores de ambas expresiones sean iguales. Se aceptan numerosos tipos de expresiones (int, String, float, double, boolean, char, short). Si ambas expresiones no son iguales, se lanza una excepción de tipo [AssertionFailedError ],
- assertEquals(real1, real2, delta): verifica que dos valores reales sean iguales con una diferencia de hasta delta, es decir, c.a.d abs(real1 - real2) <= delta. Por ejemplo, se puede escribir assertEquals(real1, real2, 1E-6) para verificar que dos valores sean iguales con una precisión de 10⁻⁶,
- assertEquals(mensaje, expresión1, expresión2) y assertEquals(mensaje, real1, real2, delta) son variantes que permiten especificar el mensaje de error que se asociará a la excepción de tipo [AssertionFailedError] lanzada cuando falla el método [assertEquals],
- assertNotNull(Object) y assertNotNull(message, Object): verifica que la referencia Object no sea nula,
- assertNull(Object) y assertNull(message, Object): verifica que la referencia Object sea igual a null,
- assertSame(Object1, Object2) y assertSame(mensaje, Object1, Object2): verifica que las referencias Object1 y Object2 apunten al mismo objeto,
- assertNotSame(Object1, Object2) y assertNotSame(mensaje, Object1, Object2): comprueba que las referencias Object1 y Object2 no apunten al mismo objeto;
Para ejecutar la prueba, se puede proceder de la siguiente manera:
![]() |
Se obtiene el siguiente resultado:
![]() |
Aquí todas las pruebas han sido exitosas. ¿Qué demuestra este ejemplo? Demuestra la flexibilidad que ofrece el marco Spring en la configuración de una arquitectura en capas. Se puede decidir utilizar la implementación [Ui1, Metier1, Dao1] o [Ui2, Metier2, Dao2] simplemente mediante la configuración. Así, en la prueba anterior JUnit, si solo se mantiene la inyección de los beans [ui1, metier1, dao1], se trabaja con la primera arquitectura. Para cambiar de arquitectura, basta con cambiar los beans inyectados. Esto se hace sin modificar el código de las capas que implementan las interfaces. A este tipo de programación se le denomina «programación por interfaces», ya que no se utilizan las instancias de las clases que implementan las capas, sino las instancias de sus interfaces.
2.6. Conclusion
- Spring maneja objetos que son singletons (una sola instancia). Spring también maneja objetos que se instancian cada vez que se le solicita una instancia a Spring. Este caso también se presentará en este documento;
- estos objetos pueden declararse de diversas formas que pueden combinarse:
- en un archivo XML,
- en una clase Java anotada con [@Configuration],
- con cualquier clase Java anotada con [@Component, @Service, ...];
- un objeto Spring puede inyectarse en otro objeto Spring con la anotación [@Autowired]. A esto se le conoce como inyección de dependencias (DI: Dependency Injection);
- Spring resulta muy práctico para configurar arquitecturas en capas junto con el paradigma de la programación por interfaces;




















































