Segunda parte del curso Spring Boot

En el anterior post hablamos de los conceptos básicos, y este es uno de los principales, pero merece un espacio propio.

Tocamos temas sobre inyección de dependencias y beans, sin embargo no hablamos del ciclo de vida de estos componentes, por lo que vamos a empezar por ahí.

Teníamos nuestro archivo XML para inicializar los beans y dijimos que podemos inicializar varios; en muchos casos estos beans dependen de otros, es decir que debemos inyectar las dependencias y comportamiento. Esto lo logramos usando la interfaz de BeanPostProcessor que nos permite crear objetos y asignar comportamientos antes y después de crear los beans con los metodospostProcessBeforeInitialization y postProcessAfterInitialization respectivamente. Lo que hace Spring es justamente en los anteriores métodos por medio de Reflexion instanciar los objetos necesario y asignar el comportamiento deseado. A partir de Spring 3.5 se implemento la autodetección de beans, es decir que ya no es necesario usar un XML para inyectar cada uno de nuestros beans. Esto se logra a través de las anotaciones.

¿Qué es una anotación?

Una anotación es un metadato que se incrusta en el código, se caracterizan por iniciar con el carácter @, este no cambia el comportamiento del código pero si puede indicarle al compilador acciones especificas de lectura o ejecución, por ejemplo, Java por defecto tiene varias anotaciones, una de las mas conocidas es @Override que se utiliza para asegurar que un método heredado se sobrescribe. Así como ésta hay muchas mas y además se pueden crear anotaciones propias.

Spring se vale de anotaciones para indicarte la la interfaz BeanPostProcessor como instanciar los beans. En el siguiente ejemplo busca la anotación @MyAnnotation:

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    Method[] methods = bean.getClass().getDeclareMethods();
    for (Method get Bean: methods) {
        if (method.getAnnotation(MyAnnotation.class) != null) {
            Class[] args = method.getParameterTypes();
            try {
                Object obj = context.getBean(args[0]);
                method.invoke(bean, obj);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Spring provee unas anotaciones por defecto, vamos a ver las principales.

Anotaciones y componentes

@Component

Esta anotación le indica a Spring Boot que debe tratar la clase con el decorador como un bean, es decir que el propio motor se encargará de hacer la inyección.

import org.springframework.stereotype.Component;

@Component
public class MyClass {

@Repository, @Service y @Controller son especializaciones de @Component.

@Repository

Se utiliza para la capa de persistencia, en otras palabras, clases que interactúan con la base de datos. Tiene la particularidad de que tiene un control de errores mas especifico para interactuar con la sintaxis de base de datos, causando un mejor manejo de estos objetos.

@Service

Este se utiliza en la capa de servicio, es decir que se espera que sea lógica de negocio.

@Controller

Esta anotación nos enlaza con la capa de presentación, allí solemos colocar las urls que se exponen vía web.

@Controller
@RequestMapping("students")
public class StudentController {

    @GetMapping("/{id}", produces = "application/json")
    public @ResponseBody Student getStudent(@PathVariable int id) {
        return studentService.getStudentById(id);
    }
}

Esta anotación se suele usar mucho en los servicios web junto con la anotación @ResponseBody que permite mapear nuestra respuesta a un objeto JSON.

Ya que este tipo de comportamiento es el mas común, desde la versión 4.0 de Spring se creo una nueva anotación que los incluye a ambos:

@RestController

@RestController
@RequestMapping("students")
public class StudentController {

    @GetMapping("/{id}", produces = "application/json")
    public Student getStudent(@PathVariable int id) {
        return studentService.getStudentById(id);
    }
}

@Autowired

Esta anotación nos sirve para indicarle que vamos a inyectar un bean dentro de otro, es decir como una propiedad o también lo podemos usar en los métodos set; esto quiere decir que podemos darle el control al framework para que cree la instancia de los componentes, esto es importante ya que solo podemos utilizar esta anotación para inicializar componentes definidos en el contexto de Spring, es decir, que tengan el decorador @Component o alguna de sus especializaciones.

En el siguiente ejemplo se muestra la instanciación de un servicio como propiedad de otro bean(controlador):

@Service
public class StudentService {
@RestController
@RequestMapping("students")
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/{id}", produces = "application/json")
    public Student getStudent(@PathVariable int id) {
        return studentService.getStudentById(id);
    }
}

Con esta anotación nos surge un problema que vamos a ilustrar a continuación:

@Service
public class StudentService implements PersonService {
@Service
public class TeacherService implements PersonService {

La idea es que queremos inicializar una instancia de la interfaz PersonService, en este caso hay dos posibles orígenes y @Autowired no sabría cual implementar, para solucionar esto podemos usar la anotación @Qualifier de la siguiente manera:

@Service
@Qualifier("student")
public class StudentService implements PersonService {
@Service
@Qualifier("teacher")
public class TeacherService implements PersonService {
@RestController
@RequestMapping("registration")
public class RegistrationController {

    @Autowired
    @Qualifier("student")
    private PersonService personService;

    @GetMapping("/info/{id}", produces = "application/json")
    public Person getDataPerson(@PathVariable int id) {
        return personService.getBasicDataById(id);
    }
}

De esta manera podemos indicarle específicamente que tipo implementar. En el anterior ejemplo obtenemos la instancia del servicio StudentService.

@Value

Esta anotación nos sirve para inyectar valores en los campos de un constructor o método. En el siguiente ejemplo se obtiene un valor del archivo .properties y se le asigna a una variable. Podemos ver el @Value asignado a diferentes tipos de datos e incluso establecemos un valor por defecto.

@Service
@Qualifier("teacher")
public class TeacherService implements PersonService {

    @Value("${university.class.code:default}")
    private String code;

    @Value("${university.class.subjects}")
private String[] subjects;

@Scope

El Scope o ámbito indica el alcance de una instancia, en Spring tenemos por defecto la instancia de los beans como singleton (se crea una única instancia del beans, es decir que las siguientes instancias del mismo bean hacen referencia al único creado); como se mencionó por defecto funciona así, aunque si queremos especificarlo se hace de la siguiente manera:

@Bean
@Scope("singleton")
public University university() {
    return new University();
}

En el anterior post también mencionamos que se pueden establecer los beans por medio de xml, para ese caso también podemos definir el scope.

<?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.xsd">

    <bean id="personService" class="com.jhontona.service.PersonServiceImpl" scope="singleton" />
    <!--También podemos usar singleton="true" -->
</beans>

También podemos definir el alcance como prototype, el cual crea una instancia nueva en cada llamado.

@Bean
@Scope("prototype")
public Teacher teacher() {
    return new Teacher();
}

Los anteriores scopes se pueden usar en cualquier aplicación de Spring, pero además tenemos scopes específicos para el ámbito web(Web Aware).

El scope de Request se utiliza para crear una instancia nueva de bean por cada petición http, es decir que su uso mas común es con un @RestController aunque también lo podemos definir para un @Bean.

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public ExampleBean exampleBean() {
    return new ExampleBean();
}
@RestController
@RequestScope
public class ExampleController {

    @GetMapping("/test")
    public String sayHello() {
            return "Hello";
    }

}

El scope de Session se utiliza para crear una instancia nueva de bean por cada sesión http

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION)
public ExampleBean exampleBean() {
    return new ExampleBean();
}
@RestController
@SessionScope
public class ExampleController {

    @GetMapping("/test")
    public String sayHello() {
            return "Hello";
    }

}

El scope de Global Session se utiliza para crear una instancia nueva de bean por cada sesión http, pero este va orientado a Portlets, es decir aplicaciones que trabajan con módulos reutilizables de este tipo; las características principales de este tipo de aplicación es que dependen de un contenedor especializado en controlar estos módulos, además de que cada pagina puede estar constituida por múltiples portlets y cada una de estos tiene un request y response independiente.

@Bean
@Scope(value = WebApplicationContext.SCOPE_GLOBAL_SESSION)
public ExampleBean exampleBean() {
    return new ExampleBean();
}

El scope de Application se utiliza para crear una instancia nueva de bean por cada ServletContext, que por lo general en una aplicación es único(aunque podemos crear mas), comportándose como un singleton.

@Bean
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public ExampleBean exampleBean() {
    return new ExampleBean();
}
@RestController
@ApplicationScope
public class ExampleController {

    @GetMapping("/test")
    public String sayHello() {
            return "Hello";
    }

}

El scope de WebSocket se utiliza para crear una instancia nueva de bean por cada websocket.

@Bean
@Scope(scopeName = "websocket")
public ExampleBean exampleBean() {
    return new ExampleBean();
}

Tenemos un montón de anotaciones, pero las que hemos visto son las de uso mas frecuente o nos ayudan a entender los comportamientos por defecto que toma una aplicación en Spring (si se quiere profundizar mas en estas anotaciones le recomiendo este recurso).

Anotaciones Javax

Con las anotaciones propias de Spring tenemos para adaptarnos a una gran variedad de situaciones, sin embargo podemos trabajar con anotaciones propias del lenguaje o provistas por paquetes externos, por ejemplo, Javax Annotation API tiene unas anotaciones interesantes que se integran con Spring.

@PostConstruct and @PreDestroy nos sirve para llamar un método después de la instanciación del bean o antes de su destrucción, es decir que funciona como un callback.

import javax.annotation.*;

public class ExampleController {

    public String sayHello() {
            return "Hello";
    }

    @PostConstruct
    public void init(){
        System.out.println("El Bean se acaba de inicializar.");
    }
   
    @PreDestroy
    public void destroy(){
        System.out.println("El Bean se va a destruir.");
    }

}

Contamos con otras etiquetas como @Resource e @Inject que se comporta de manera muy similar al @Autowired por lo que teniendo alternativas en Spring es mas practico. Simplemente debemos saber que podemos crear o insertar anotaciones desde otros paquetes.

Hasta aquí llega este post, espero tus preguntas, comentarios y que compartas este contenido.