Anotaciones de primavera: anotaciones de marco básico

Spring Framework es un marco muy robusto, lanzado en 2002. Sus características principales se pueden aplicar a aplicaciones simples de Java o extenderse a aplicaciones complejas y modernas...

Introducción

El marco de primavera es un framework muy robusto, lanzado en 2002. Sus funciones principales se pueden aplicar a aplicaciones Java sencillas o extenderse a aplicaciones web complejas y modernas.

Como se actualiza constantemente y sigue nuevos paradigmas de arquitectura y programación, ofrece soporte para muchos otros marcos que funcionan de la mano con él.

Con una gama tan amplia de funcionalidades, es normal que nos presente algunas anotaciones nuevas, que son una parte clave del desarrollo de aplicaciones Spring.

La configuración de Spring es totalmente personalizable, lo que originalmente se hizo a través de archivos de configuración XML. Sin embargo, este enfoque se ha vuelto obsoleto y la mayoría de las personas hoy en día recurren a la configuración de anotaciones.

Dicho esto, esta serie de artículos tiene como objetivo desentrañar las opciones que tiene como desarrollador para configurar y usar Spring Framework:

Nota: este artículo asume que está familiarizado con el principio de Inversión de control por resorte.

Anotaciones principales

Echemos un vistazo a las anotaciones principales que componen casi todas las aplicaciones de Spring:

@Frijol

Un @Bean es un objeto de columna vertebral en Spring Framework. Todo se reduce a JavaBeans: clases que encapsulan objetos en uno solo. Son un tipo de POJO (Plain Old Java Object).

Todos los JavaBeans deben ser Serializables, todos los campos deben ser privados, todos los campos deben tener setters y getters, debe haber un constructor sin argumentos, y los campos son accedidos exclusivamente por el constructor o los métodos getter/setter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Developer implements java.io.Serializable {
   private int id;
   private String name;

   public Developer() {}
   public void setId(int id) {this.id = id;}
   public int getId() {return id;}
   public void setName(String name) {this.name = name;}
   public String getName() {return name;}
}

En términos de Spring, los beans son instanciados y administrados por Spring IoC Container. Son simplemente instancias de objetos administradas por Spring.

Para que Spring sepa qué instancias de objetos debe administrar, simplemente marcamos los métodos en los que los instanciamos con la anotación @Bean.

Cuando se encuentre este método, se ejecutará y el valor devuelto se guardará dentro de BeanFactory:

1
2
3
4
5
6
7
@Configuration
public class ConfigurationClass {
    @Bean
    public Developer developer() {
        return new Developer();  
    }
}

Esto es lo mismo que usar el antiguo enfoque XML para registrar un bean:

1
2
3
<beans>
    <bean name="developer" class="com.wikihtp.Developer"/>
</beans>

Ahora, para inyectar este bean como una dependencia en otro bean, simplemente hacemos que otro bean llame al método del bean del desarrollador:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Configuration
public class ConfigurationClass() {
    
    @Bean
    public Manager manager() {
        return new Manager(developer());
    }

    @Bean
    public Developer developer() {
        return new Developer();  
    }
}

@Requerido

La anotación @Required se usa en métodos y constructores de establecimiento. Como sugiere el nombre, le dice a Spring que estos campos son requeridos para que el bean se inicialice correctamente.

Si los campos no se completan en el momento de la configuración, el bean no podrá inicializarse, lo que generará una excepción y la aplicación no podrá compilarse:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Developer implements java.io.Serializable {
    private int id;
    private String name;

    public Developer() {}
   
    @Required
    public void setId(int id) {
        this.id = id;
    }
   
    public int getId() {
        return id;
    }
   
    @Required
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
}

Para completar un campo en el momento de la configuración como este, asignamos los nombres de propiedad a través de XML:

1
2
3
<bean class="com.wikihtp.Develope>
    <property name="name" value="David"/>
</bean>

@Autocableado

La anotación @Autowired se usa para un mayor control sobre la inyección de dependencia. Se usa para conectar un bean a otro sin instanciar el primero.

Una vez más, en lugar de conectar las dependencias a través de XML, que era engorroso, simplemente marcamos nuestras dependencias como @Autowired. Basado en nuestra clase base, donde se encuentran todos nuestros componentes, Spring hace todo el cableado por nosotros.

Para declarar el paquete base de nuestros componentes, simplemente podemos agregar una etiqueta a nuestro archivo de contexto de aplicación:

1
<context:component-scan base-package="com.wikihtp.basePackage"/>

Todas las clases etiquetadas @Component (incluidos los derivados como @Service, @Controller y @Repository) se registrarán como beans aptos para el cableado automático.

@Autowired en Propiedades

En lugar de la instanciación explícita e imperativa:

1
2
3
4
5
6
7
public class ProductController {
    private ProductService productService = new ProductService();

    public void someMethod() {
        List<Product> productList = productService.getProductList();
    }
}

Utilizamos un enfoque declarativo:

1
2
3
4
5
6
7
8
9
public class ProductController {

    @Autowired
    private ProductService productService;

    public void someMethod() {
        List<Product> productList = productService.getProductList();
    }
}

En esta implementación, nunca instanciamos realmente la clase ProductService, separándola de ProductController si deseamos probarla.

Por supuesto, para autoconectar un campo, debe registrarse como un bean en el contenedor Spring IoC. En nuestro caso, es un bean anotado @Service, pero hablaremos de eso más adelante.

También hay otros casos de uso para la anotación @Autowired.

@Autowired en setters

Muy similar a la anotación @Required, también podemos usar @Autowired en setters:

1
2
3
4
5
6
7
8
9
public class ProductController {

    private ProductService productService;

    @Autowired
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }
}

Al autoconectar un setter como este, no hay necesidad de completarlo a través de XML.

Esta es la llamada inyección de dependencia basada en setter.

@Autowired en Constructores

La anotación @Autowired también se puede usar en constructores:

1
2
3
4
5
6
7
8
9
public class ProductService {

    private ProductDao productDao;
    
    @Autowired
    public ProductService(ProductDao productDao) {
        this.productDao = productDao;
    }
}

Esta es la denominada inyección de dependencia basada en constructores.

La bandera obligatoria

Al marcar un bean como @Autowired, Spring espera que esté disponible al construir las otras dependencias. Si no, seremos recibidos con una excepción y una compilación fallida.

Si no puede garantizar que el bean estará disponible, o si no siempre es necesario, puede usar el indicador requerido para marcarlo como opcional:

1
2
3
4
5
public class ProductController {

    @Autowired(required = false)
    private ProductService productService;
}

De esta manera, si el bean de servicio del producto no está disponible, todo funcionará sin problemas.

@Calificatorio

La anotación @Qualifier se utiliza para aclarar los casos en los que nos gustaría autoconectar más de un bean del mismo tipo.

Por ejemplo, en una empresa, lo más probable es que tengamos más de un empleado, y cada empleado tiene su puesto respectivo: desarrollador, desarrollador principal, gerente, CEO, etc.

1
2
3
4
5
@Component
public class Developer implements Employee {}

@Component
public class Manager implements Employee {}

Si tuviéramos que autoconectar a un empleado, sería ambiguo en cuanto a qué bean queremos autoconectar:

1
2
3
4
5
@Controller
public class CompanyController {
    @Autowired
    private Employee employee;
}

Recibiríamos un error:

1
2
3
org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No unique bean of type [com.wikihtp.employee] is defined: 
        expected single matching bean but found 2: [developer, manager]

Para evitar tal situación, agregamos calificadores:

1
2
3
4
5
6
7
@Component
@Qualifier("developer")
public class Developer implements Employee {}

@Component
@Qualifier("manager")
public class Manager implements Employee {}

Y al autocablear:

1
2
3
4
5
6
@Controller
public class CompanyController {
    @Autowired
    @Qualifier("developer")
    private Employee employee;
}

Esto aclara qué bean nos gustaría autoconectar y el código funciona bien.

@Escaneo de componentes

Una anotación crucial para Spring es la anotación @ComponentScan. Especifica qué paquetes contienen clases que están anotadas. De esa manera, Spring sabe qué clases necesita administrar y siempre se usa junto con la anotación @Configuration.

Por ejemplo, tenemos un paquete com.wikihtp.controller que contiene todos nuestros controladores donde cada clase se anota con @Controller. Para que Spring sepa que este paquete contiene componentes que necesitan administración, usamos la anotación @ComponentScan y agregamos el paquete.

De lo contrario, tendríamos que registrar cada bean individualmente, lo que sería engorroso e imposible de escalar.

En muchos casos, simplemente definimos un solo basePackage que contiene todos nuestros componentes, como com.wikihtp. Aunque en algunos casos nos gustaría incluir múltiples basePackages o basePackageClasses:

1
2
3
4
5
@Configuration
@ComponentScan(basePackage = "com.wikihtp")
public class SomeApplication {
    // some code
}

Si quisiéramos definir múltiples paquetes base:

1
2
3
4
5
@Configuration
@ComponentScan(basePackage = {"com.package1", "com.package2})
public class SomeApplication {
    // some code
}

Una alternativa de tipo seguro para basePackages es basePackageClasses:

1
2
3
4
5
@Configuration
@ComponentScan(basePackageClasses =  Developer.class) 
public class SomeApplication {
    // some code
}

Nota: Si no se define ningún paquete base, el paquete en el que se encuentra la clase se utilizará como paquete base.

@Perezoso

De forma predeterminada, los beans y los componentes se inicializan con entusiasmo. Si quisiéramos cambiar ese comportamiento, podemos hacerlo usando la anotación @Lazy.

Puede usarse en un nivel de clase que se anota como @Component o en un nivel de método que se anota como @Bean.

Si se anota, el componente/bean no se inicializará hasta que otro bean lo haga referencia explícitamente y sea necesario para que la aplicación funcione sin problemas:

1
2
3
@Lazy
@Bean
class SomeResource {}

También podríamos marcar una clase @Configuration como @Lazy:

1
2
3
4
5
@Lazy
@Configuration
public class AppConfig {
    // some code
}

En este caso, todos los beans definidos dentro de AppConfig también se inicializarán de forma diferida.

@Configuración

La anotación @Configuration está a nivel de clase y le dice a Spring que esta clase contiene uno o más métodos @Bean y puede ser procesada por el contenedor Spring para generar definiciones de beans.

Esta es una de las razones por las que los desarrolladores pudieron dejar de usar la configuración basada en XML y la simplicidad de la anotación hace que la configuración basada en Java sea preferible.

1
2
3
4
5
6
@Configuration
public class AppConfig {
     @Bean
     public SomeBean someBean() {
         // Instantiation, configuration, returning the bean
}

@Valor

La anotación @Value tiene bastantes casos de uso en Spring y justifica un artículo por sí mismo. Intentaré ser breve y cubrir los casos de uso más comunes y obvios en este caso.

Se puede utilizar para:

  • Asignación de valores predeterminados a los campos.
  • Lectura de variables de entorno.
  • Uso de expresiones Spring Expression Language (SpEL)
  • Valores predeterminados para parámetros si se usan dentro de un método/constructor

Dicho esto, repasemos todos estos casos de uso uno por uno.

Valores de campo predeterminados {#valores de campo predeterminados}

Si desea asignar un valor predeterminado a un campo, la anotación @Value es bastante sencilla:

1
2
@Value("Hello World!")
private String helloString;

Aunque no instanciamos esta Cadena ni le asignamos un valor explícitamente, lo hicimos a través de la anotación.

La anotación @Value está destinada a usarse con cadenas. Si intenta aplicarlo a otro tipo, funcionará solo si Spring puede convertir fácilmente entre los dos, como booleans e ints:

1
2
3
4
5
@Value("true")
private boolean accepted;

@Value("53")
private int userId;

Propiedades del entorno de lectura

Digamos que, entre otras propiedades, nuestro archivo application.properties contiene algunas variables de entorno:

1
sa.website_name = Stack Abuse

Por ejemplo, leamos esta propiedad y asignémosla a un String en nuestra clase de configuración. Para hacer esto, también necesitamos definir la fuente de la propiedad:

1
2
3
4
5
6
@PropertySource("classpath:application.properties")
@Configuration
public class AppConfig {
    @Value("${sa.website_name}")
    private String websiteName;
}

En términos generales, ${...} se usa como marcador de posición de propiedad en Spring. Probablemente ya estés familiarizado con esto si has incursionado en las tecnologías de Spring.

Si la propiedad no está disponible o definida, podríamos tener un problema. En este caso, podemos definir valores predeterminados para marcadores de posición en caso de que no estén bien definidos:

1
2
3
4
5
6
@PropertySource("classpath:application.properties")
@Configuration
public class AppConfig {
    @Value("${sa.website_name}:Backup Value")
    private String websiteName;
}

De esta forma, si sa.website_name no existe, el valor asignado a la cadena será Valor de copia de seguridad.

Uso de SpEL

Similar a la sintaxis del marcador de posición, Spring Expression Language (SpEL) usa la sintaxis #{...} para almacenar expresiones:

1
2
@Value("#{systemProperties['java.home']}")
private String someValue;

Si decidimos agregar algunas propiedades que podrían no estar disponibles, nuevamente estaríamos en un problema. Para evitar tales casos, también podemos definir valores predeterminados de "copia de seguridad" para SpEL:

1
2
@Value("#{systemProperties['unknownproperty'] ?: 'Backup Value'}")
private String someValue;

Valores de parámetros predeterminados {#valores de parámetros predeterminados}

Si se aplica a un método, la anotación @Value asignará el valor predeterminado a todos los parámetros del método:

1
2
3
4
@Value("Hello")
public String hello(String str1, String str2) {
    return str1 + str2;
}

Este método imprimiría:

1
HelloHello

Por otro lado, si aplicamos el método @Value tanto a un método como a un parámetro, al parámetro se le asignará el nuevo valor:

1
2
3
4
@Value("Hello")
public String hello(String str1, @Value("World") String str2) {
    return str1 + str2;
}

La salida en este caso sería:

1
HelloWorld

@Depende de

Si un bean depende de otros beans para una instanciación correcta, Spring puede garantizar que todos los beans de los que depende se crearán antes que él. Sin embargo, necesitamos especificar cuáles usando la anotación @DependsOn.

La anotación acepta una matriz de cadenas que corresponden a los nombres de los beans en cuestión. Esto significa que puede pasar cualquier nombre de bean válido como argumento, siempre que esté correctamente anotado con una anotación @Component o @Bean.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Configuration
public class AppConfig {
    @Bean("firstBean")
    @DependsOn(value = {"secondBean", "thirdBean"})
    public FirstBean firstBean() {
        return new FirstBean();
    }
    
    @Bean("secondBean")
    public SecondBean secondBean() {
        return new SecondBean();
    }
    
    @Bean("thirdBean")
    public ThirdBean thirdBean() {
        return new ThirdBean();
    }
}

Aunque FirstBean se encuentra antes del segundo y el tercero, hemos anotado que depende de la creación de SecondBean y ThirdBean para que funcione correctamente. Al hacer esto, Spring primero definirá esos dos y luego FirstBean.

@Primario

La anotación @Primary se usa a menudo junto con la anotación Qualifier. Se utiliza para definir el bean "predeterminado" para el cableado automático cuando no hay más información disponible.

Da preferencia al bean anotado, si hay más de un bean del mismo tipo, como su nombre lo indica:

1
2
3
4
5
6
7
8
@Component
@Qualifier("developer")
@Primary
public class Developer implements Employee {}

@Component
@Qualifier("manager")
public class Manager implements Employee {}

Este es el mismo problema que encontramos en la parte anterior del artículo donde definimos un calificador para permitir que la anotación @Autowired elija entre los beans calificados.

Sin embargo, esta vez, no necesitamos agregar la anotación @Qualifier a la anotación @Autowired ya que se ha declarado el bean principal/predeterminado:

1
2
3
4
5
@Controller
public class CompanyController {
    @Autowired
    private Employee employee;
}

Esto creará una instancia de un bean Developer.

@Alcance

La anotación @Scope se aplica a nivel de bean y define su visibilidad/ciclo de vida. Si se aplica junto con la anotación @Component, define el alcance de las instancias del tipo anotado. Si se usa en un método @Bean, el alcance se aplica a la instancia devuelta.

Hay dos ámbitos básicos, con otros cuatro para aplicaciones web:

  • único
  • prototipo
  • solicitud
  • sesión
  • solicitud
  • conector web

Ámbito único {#ámbito único}

Si no se utiliza otro nombre de ámbito, el valor predeterminado es único. Un alcance singleton garantiza solo una instancia de la instancia devuelta del método anotado. El objeto se guardará en el contenedor de Spring y se almacenará en caché, lo que permitirá su uso en cualquier lugar desde la aplicación:

1
2
3
4
5
@Bean
@Scope("singleton")
public CompanyCEO companyCEO() {
    return new CompanyCEO();
}

Prototipo

Lo opuesto al alcance singleton, aplicar el alcance prototipo garantiza una instancia nueva del bean anotado cada vez que lo solicitamos.

1
2
3
4
5
@Bean
@Scope("prototype")
public Developer developer() {
    return new Developer();  
}

Solicitud

El alcance de la solicitud garantiza la creación de instancias de un solo bean para cada solicitud HTTP:

1
2
3
4
5
6
// This method will be called on every HTTP request
@Bean
@Scope("request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public SetupClass someSetup() {
    // Run some setup on each http request
}

Una alternativa sería usar la anotación 4.3 @RequestScope que incluye el proxy por defecto.

Sesión

Muy similar al alcance de la solicitud, el alcance de la sesión instanciará el bean anotado con un ciclo de vida dependiente de la sesión HTTP.

1
2
3
4
5
6
// This method will be called on every HTTP session
@Bean
@Scope("session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public SetupClass someSetup() {
    // Run some setup on each http session
}

Solicitud

El alcance de aplicación funciona de manera similar al alcance de singleton. El ciclo de vida de un bean con ámbito de aplicación depende de la aplicación, o más bien, del ServletContext.

La principal diferencia entre estos dos es el hecho de que application tiene un alcance más amplio en el sentido de que puede expandirse a otras aplicaciones que se ejecutan en el mismo ServletContext.

1
2
3
@Scope("application")
@Component
public class Application {}

De nuevo, a partir de la versión 4.3, puede reemplazar esta anotación con @ApplicationScope.

WebSocket

Si usamos el ámbito websocket, vinculamos el ciclo de vida de nuestro bean con el ciclo de vida de la sesión WebSocket.

La primera vez que se llama, se crea una instancia del bean y se almacena para su uso posterior dentro de la misma sesión:

1
2
3
4
5
6
// This method will be called on every websocket session
@Bean
@Scope("websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public SetupClass someSetup() {
    // Run some setup on each websocket session
}

Conclusión

El framework Spring es un framework poderoso y robusto que realmente cambió el juego cuando se trata de desarrollar aplicaciones web. Entre su gran cantidad de proyectos, es una buena idea comenzar con el marco central y construir sobre eso.

El marco central nos presenta varias anotaciones que hacen que nuestras vidas sean más fáciles y productivas. El manejo de estas anotaciones es imprescindible para todos los desarrolladores de Java/Spring. ng.