Spring es un framework de código abierto para el desarrollo de aplicaciones usando el lenguaje de programación Java de una forma modular y escalable. Se compone de varios módulos. Cada uno de ellos se encarga de una tarea específica como:

  • Creación de servicios web.
  • Acceso a datos.
  • Seguridad.
  • Integración con otras tecnologías.

Es uno de los frameworks basados en Java más populares y utilizados a día de hoy debido a su adaptabilidad y flexibilidad a los requisitos empresariales, a ka vez que a su gran cantidad de características. Por ejemplo, facilita mucho del desarrollo de microservicios, como hemos visto en Qué es una arquitectura de Microservicios.

1. Características principales

Entre las características principales se encuentran las siguientes:

Inversión de control (IoC)

Con la Inversión de Control (IoC) la responsabilidad de creación y posterior gestión de un objeto ahora recae en el contenedor de Spring en lugar de en el desarrollador. Sin IoC es el propio objeto quien crea y gestiona los objetos que dependen de él.

Para implementar la IoC Spring se apoya en el patrón de Inyección de Dependencias (DI). El contenedor de Spring se encarga de inyectar los objetos mediante configuración XML o anotaciones. Particularmente prefiero la segunda en base al paradigma de diseño Convention over configuration.

A continuación podéis ver un ejemplo (de Samuráis, por supuesto) de IoC con Spring:

Arma.java
// Definimos la interfaz Arma
public interface Arma {
    void atacar();
}
Espada.java
// Definimos una implementación de Arma, por ejemplo, una espada
@Component
public class Espada implements Arma {

    @Override
    public void atacar() {
        System.out.println("Atacando con una espada!");
    }
}
Samurai.java
// Definimos la clase Samurai, que tiene una dependencia de Arma
@Component
public class Samurai {

    private Arma arma;

    @Autowired
    public Samurai(Arma arma) {
        this.arma = arma;
    }

    public void atacar() {
        arma.atacar();
    }
}

Observa que la clase Samurai tiene un constructor que toma un parámetro de tipo Arma, y la anotación @Autowired se utiliza para indicar que Spring debe inyectar una instancia de Arma en la clase Samurai. La clase Samurai no crea una instancia de Arma directamente.

Como hemos visto, mediante la IoC reducimos la dependencia entre objetos, la complejidad del código, mejorando la legibilidad y claridad. También nos permite cambiar de forma sencilla un objeto inyectado por otro.

Inyección de dependencias (DI)

Esta característica está ligada a la anterior, ya que suelen usarse de manera conjunta. Se podría decir que la IoC se implementa utilizando el patrón de Inyección de Dependencias (DI).

El contenedor de Spring es un contenedor de beans. Es decir, tenemos beans preparados para ser inyectados en cualquier parte de nuestro código que necesitemos. Spring se encarga de crear, configura y administrar dichos beans.

A continuación seguimos con el ejemplo mostrado en el apartado de IoC para mostrar como podemos hacer uso de la DI:

AppConfig.java
// Mediante la etiqueta @Configuration indicamos que está clase contiene
// configuración de Spring
@Configuration
public class AppConfig {

     // Se crea una instancia de Arma, en este caso una Espada
    @Bean
    public Espada espada() {
        return new Espada();
    }

    // Se crea una instancia Samurai
    // Mediante el parámetro arma se inyecta el bean Espada anterior
    @Bean
    public Samurai samurai(Arma arma) {
        return new Samurai(arma);
    }
}

La anotación @Bean indica a Spring que ese objeto debe incluirse al contenedor de beans y serán gestionados por el mismo. Con esta configuración establecida podemos obtener una instancia de Samurai:

public static void main(String[] args) {

    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    Samurai samurai = context.getBean(Samurai.class);
    samurai.atacar();
}

Este ejemplo demuestra cómo podemos utilizar la IoC y DI en una aplicación Java de Samurai utilizando la configuración por anotaciones en Spring.

Programación orientada a aspectos (AOP)

La AOP es un paradigma de programación para modularizar aplicaciones mediante la separación de responsabilidades. Se utiiza para implementar funcionalidades transversales al sistema (por ejemplo seguridad y logging).

Los conceptos más importantes de la AOP son:

  • Aspect: el aspecto es la funcionalidad transversal en si. Es implementada de forma modular y puede ser usado desde cualquier parte de la aplicación.
  • Pointcut: el punto de corte es el punto de código donde se invoca al aspecto. Suele especificarse mediante expresiones regulares o patrones de nombres.
  • Advice: el aviso es la implementación del aspecto. Se aplican en los pointcuts. Se indica cuando se lanza en cada pointcut (before, after).

Vemos un ejemplo para que quede un poco más claro mediante un aspecto de logging:

LogginngAspect.java
// Implementación del aspecto
@Component
@Aspect
public class LoggingAspect {

    // Método que se ejecuta antes de invocar al método atacar
    @Before("execution(* com.example.Samurai.atacar())")
    public void logAntesDeAtacar() {
        System.out.println("Preparando para atacar...");
    }

    // Método que se ejecuta después de invocar al método atacar
    @After("execution(* com.example.Samurai.atacar())")
    public void logDespuesDeAtacar() {
        System.out.println("Ataque realizado!");
    }
}

La anotación `@Component` se utiliza para que Spring pueda administrar y configurar esta clase como un componente de la aplicación

Para que el aspecto aplique a la clase Samurai simplemente anotamos la clase de configuración con la anotación @EnableAspectJAutoProxy:

AppConfig.java
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.ejemplo")
public class AppConfig {
}

La anotación @ComponentScan se utiliza para que Spring sepa donde se encuentran las clases que tiene que administrar y configurar, pudiendo indicar el paquete base.

Modularidad

Para que una aplicación sea más sencilla y más mantenible una de las principales características que podemos usar es la modularidad. De esta forma podemos separar en capas y funcionalidades las diferentes partes o módulos del sistema. Por ejemplo, el patrón de MVC separa el modelo, la vista y el controlador.

Cada módulo puede estar diseñado y desarrollado independientemente y se puede reutilizar, modificar o eliminar si ya no fuera necesario, sin afectar a los demás módulos. En Spring existen anotaciones específicas para determinar de que tipo es una clase. Las más usadas son @Controller, @Service y @Repository y @Component. Se utilizan para marcar los componentes como «inyectables» y poder usarse en otros componentes o clases.

Veámoslo en código para mayor claridad. Por ejemplo, y siguiendo con el ejemplos de samuráis, definamos dos módulos, uno para la gestión de samuráis (paquete samurai) y otro para la gestión de armas (paquete armas). La estructura quedaría de la siguiente forma:

  • es.javasamurai.samurai (package)
    • SamuraiService.java
  • es.javasamurai.arma (package)
    • ArmaService.java

A continuación tenemos el código de los dos módulos (que se traducen en clases Java):

SamuraiService.java
@Service
public class SamuraiService {

    private SamuraiRepository samuraiRepository;

    @Autowired
    public SamuraiService(SamuraiRepository samuraiRepository) {
        this.samuraiRepository = samuraiRepository;
    }

    public List<Samurai> findAll() {
        return samuraiRepository.findAll();
    }

    public Samurai findById(Long id) {
        return samuraiRepository.findById(id).orElse(null);
    }

    public Samurai save(Samurai samurai) {
        return samuraiRepository.save(samurai);
    }

    public void delete(Samurai samurai) {
        samuraiRepository.delete(samurai);
    }
}
ArmaService.java
@Service
public class ArmaService {

    private ArmaRepository armaRepository;

    @Autowired
    public ArmaService(ArmaRepository armaRepository) {
        this.armaRepository = armaRepository;
    }

    public List<Arma> findAll() {
        return armaRepository.findAll();
    }

    public Arma findById(Long id) {
        return armaRepository.findById(id).orElse(null);
    }

    public Arma save(Arma arma) {
        return armaRepository.save(arma);
    }

    public void delete(Arma arma) {
        armaRepository.delete(arma);
    }
}

Además, podríamos utilizar la inyección de dependencias de Spring para inyectar los componentes de cada módulo en otras partes de la aplicación. Por ejemplo, si queremos mostrar la lista de samuráis y sus armas en una página web, podríamos inyectar tanto el componente SamuraiService como el componente ArmaService en un controlador de Spring encargado de manejar la petición correspondiente.

SamuraiController.java
// Controlador que utiliza los servicios de samurais y armas
@Controller
public class SamuraiController {

    private SamuraiService samuraiService;
    private ArmaService armaService;

    @Autowired
    public SamuraiController(SamuraiService samuraiService, ArmaService armaService) {
        this.samuraiService = samuraiService;
        this.armaService = armaService;
    }

    @GetMapping("/samurais")
    public String listarSamurais(Model model) {

        List<Samurai> samurais = samuraiService.findAll();
        model.addAttribute("samurais", samurais);

        return "samuraisView";
    }

    @GetMapping("/armas")
    public String listarArmas(Model model) {

        List<Arma> armas= armaService.findAll();
        model.addAttribute("armas", armas);

        return "armasView";

    }
}

En resumen, la modularidad en Spring se logra a través del desarrollo de componentes independientes y la utilización de la inyección de dependencias para integrar estos componentes en la aplicación principal. Esto permite una mayor reutilización del código, una mejor organización y mantenimiento del código, y una mayor flexibilidad para activar o desactivar características de la aplicación según las necesidades del usuario.

Orientado a microservicios

Como vimos en el artículo previo Qué es una arquitectura de Microservicios, Spring, en concreto el framework de Spring boot, nos facilita mucho la creación de microservicios mediante la creación de configuración de manera automática con starters.

Por ejemplo, y siguiendo el ejemplo de los Samuráis, podríamos hacer uso del starter spring-boot-starter-data-jpa para implementar un repositorio que gestione las entidades de Samurai. Primero crearemos el siguiente fichero pom.xml de Maven:

<dependencies>
    <!-- Samurai -->
    <dependency>
        <groupId>es.javasamurai</groupId>
        <artifactId>samurai-core</artifactId>
        <version>1.1.0</version>
    </dependency>

    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>2.6.3</version>
    </dependency>

    <!-- Driver de base de datos (en este ejemplo, H2) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
    </dependency>
</dependencies>

Se ha añadido la dependencia de Spring Data JPA para el acceso a una base de datos H2, también indicada en el fichero. A continuación se crea la clase con el método main, que sería el punto de arranque de la aplicación:

MiSamuraisApp.java
package es.javasamurai;

@SpringBootApplication
public class MiSamuraisApp {

    public static void main(String[] args) {
        SpringApplication.run(MiSamuraisApp .class, args);
    }
}

Se incluye la anotación @SpringBootApplication que contiene las siguientes tres anotaciones:

  • @Configuration: indica que la en clase se pueden definir beans que se incluyen en el contexto.
  • @EnableAutoconfiguration: indica que se deben añadir los beans que se encuentren en el classpath, otros beans y la configuración de propiedades.
  • @ComponentScan: indica que busque otros componentes, configuraciones y servicios en el paquete por defecto. En el ejemplo, escanearía desde el paquete es.javasamurai, que es donde se encuentra la clase MiSamuraisApp, y recursivamente hacía dentro.

Ahora creamos la entidad que es la clase POJO usada en el repositorio. Se marca con la anotación @Entity:

Samurai.java
@Entity
@Data
public class Samurai {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String nombre;
    private int edad;
}

Y por último se crea la clase anotada con @Repository. Se encarga de la gestión de las entidades Samurai. Es sólo una interface que extiende de la clase JpaRepository e indica la entidad, Samurai, y el tipo de la clave, Long. En tiempo de compilación Spring boot genera la implementación de todos los métodos necesarios:

SamuraiRepository.java
@Repository
public interface SamuraiRepository extends JpaRepository<Samurai, Long> {
}

Al no indicar las propiedades de la base de datos H2, Spring boot por defecto determina que estará en memoria, con el usuario sa y sin contraseña establecida.

También es importante mencionar Spring cloud que nos proporciona herramientas para construir rápidamente los patrones más comunmente usados en un sistema de microservicios. Se puede llevar a cabo las siguientes funcionalidades, entre otras:

  • Configuración distribuida.
  • Servicio de registro y descubrimiento.
  • Enrutamiento.
  • Comunicación entre microservicios.
  • Balanceo de carga.
  • Patrón circuit breakers.
  • Mensajería distribuida.

2. Ventajas e inconvenientes

Cuando se habla de las ventajas de Spring siempre se suele comparar con Java EE (Enterprise Edition), ya que es el oficial y donde se definen las diferentes especificaciones que después son implementadas por los diferentes frameworks.

Spring nace para suplir las desventajas que tenemos en Java EE como:

  • Entorno de desarrollo complejo.
  • Alta curva de aprendizaje debido a las muchas especificaciones.
  • Desarrollo de servicios webs más complejos.
  • Inversión requerida más alta.

Con Spring conseguimos suplir estas desventajas, y añadimos las siguientes ventajas:

  • Es más ligero, utilizando POJOs (Plain Old Java Object).
  • Abstrae las especificaciones de Java EE.
  • Configuración mediante XML o anotaciones.
  • Test más sencillos.

En cuanto a los inconvenientes tenemos las siguientes:

  • Complejo y difícil de aprender para nuevos desarrolladores.
  • La abstracción también se puede considerar una desventaja, ya que no te permite ver claramente lo que se está haciendo «por debajo».

Podemos observar que las ventajas son mucho mayores que los inconvenientes. Desde aquí recomendamos el uso de Spring como framework de desarrollo en Java, !para ser un auténtico Samurái!

3. Conclusión

Como hemos visto, Spring es un framework muy flexible que nos facilita el desarrollo de software haciéndolo más sencillo. Podemos llevar a cabo tareas tales como servicios web, acceso a datos, gestión de la seguridad e integraciones, entre otros, de una manera simple.

Sus características principales son la Inversión de Control (IoC), la Inyección de Dependencias (DI), la Programación Orientada a Aspectos (AOP) y la modularidad. En el último punto hemos podido comprobar que aporta más ventajas que inconvenientes, por lo que es muy recomendable su uso.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *