Los alcances de Spring Bean nos permiten tener un control más granular de la creación de instancias de beans. A veces queremos crear una instancia de bean como singleton, pero en otros casos podemos querer que se cree en cada solicitud o una vez en una sesión.
Los alcances de Spring Bean
Hay cinco tipos de alcances de beans de spring:
- singleton – se creará solo una instancia del bean de spring para el contenedor de spring. Este es el alcance de bean de spring por defecto. Al utilizar este alcance, asegúrese de que el bean no tenga variables de instancia compartidas, de lo contrario podría generar problemas de inconsistencia de datos.
- prototype – se creará una nueva instancia cada vez que se solicite el bean desde el contenedor de spring.
- request – esto es igual que el alcance de prototipo, sin embargo, está destinado a ser utilizado en aplicaciones web. Se creará una nueva instancia del bean para cada solicitud HTTP.
- session – el contenedor creará un nuevo bean para cada sesión HTTP.
- global-session – esto se utiliza para crear beans de sesión global para aplicaciones de portlet.
Alcance Singleton y Prototipo de los Beans de Spring
Los alcances singleton y prototipo de los beans de Spring se pueden utilizar en aplicaciones independientes de Spring. Veamos cómo podemos configurar fácilmente estos alcances usando la anotación @Scope
. Digamos que tenemos una clase de bean en Java.
package com.journaldev.spring;
public class MyBean {
public MyBean() {
System.out.println("MyBean instance created");
}
}
Definamos la clase de configuración de Spring donde definiremos el método para obtener una instancia de MyBean del contenedor de Spring.
package com.journaldev.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class MyConfiguration {
@Bean
@Scope(value="singleton")
public MyBean myBean() {
return new MyBean();
}
}
Tenga en cuenta que singleton
es el alcance predeterminado, por lo que podemos eliminar @Scope(value="singleton")
de la definición de bean anterior. Ahora creemos un método principal y probemos el alcance singleton.
package com.journaldev.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MySpringApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MyConfiguration.class);
ctx.refresh();
MyBean mb1 = ctx.getBean(MyBean.class);
System.out.println(mb1.hashCode());
MyBean mb2 = ctx.getBean(MyBean.class);
System.out.println(mb2.hashCode());
ctx.close();
}
}
Cuando se ejecuta el programa anterior, obtendremos una salida como la siguiente.
MyBean instance created
867988177
867988177
Observe que ambas instancias de MyBean tienen el mismo hashcode y el constructor se llama solo una vez, lo que significa que el contenedor de Spring siempre devuelve la misma instancia de MyBean. Ahora cambiemos el alcance a prototipo
.
@Bean
@Scope(value="prototype")
public MyBean myBean() {
return new MyBean();
}
Esta vez obtendremos la siguiente salida cuando se ejecute el método principal.
MyBean instance created
867988177
MyBean instance created
443934570
Es claro que la instancia de MyBean se crea cada vez que se solicita desde el contenedor de Spring. Ahora cambiemos el alcance a request
.
@Bean
@Scope(value="request")
public MyBean myBean() {
return new MyBean();
}
En este caso, obtendremos la siguiente excepción.
Exception in thread "main" java.lang.IllegalStateException: No Scope registered for scope name 'request'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:347)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:224)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1015)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
at com.journaldev.spring.MySpringApp.main(MySpringApp.java:12)
Es porque los ámbitos request
, session
y global-session
no están disponibles para aplicaciones independientes.
Alcance de solicitud y sesión del Bean Spring
Para el ejemplo de alcance de solicitud y sesión del bean Spring, crearemos una aplicación web de Spring Boot. Cree un proyecto de inicio de Spring Boot y elija “web” para que podamos ejecutarlo como una aplicación web. Nuestro proyecto final se verá como en la siguiente imagen.
ServletInitializer
y SpringBootMvcApplication
son clases de Spring Boot generadas automáticamente. No necesitamos hacer ningún cambio allí. Aquí está mi archivo pom.xml, eche un vistazo a las dependencias para nuestra aplicación. Su archivo pom.xml podría ser ligeramente diferente según la versión de Eclipse que esté utilizando.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>Spring-Boot-MVC</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Spring-Boot-MVC</name>
<description>Spring Beans Scope MVC</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>10</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</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>
Creemos algunos componentes de Spring y configúrelos como beans de Spring en el contenedor de Spring con un ámbito como request
y session
.
Alcance de solicitud del Bean Spring
package com.journaldev.spring;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataRequestScope {
private String name = "Request Scope";
public DataRequestScope() {
System.out.println("DataRequestScope Constructor Called");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Ámbito de sesión de Spring Bean
package com.journaldev.spring;
import org.springframework.context.annotation.Scope;
import java.time.LocalDateTime;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataSessionScope {
private String name = "Session Scope";
public DataSessionScope() {
System.out.println("DataSessionScope Constructor Called at "+LocalDateTime.now());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Componente de Spring
Ahora creemos un componente de Spring y usemos Spring para configurar automáticamente los beans anteriores.
package com.journaldev.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Customer {
@Autowired
private DataRequestScope dataRequestScope;
@Autowired
private DataSessionScope dataSessionScope;
public DataRequestScope getDataRequestScope() {
return dataRequestScope;
}
public void setDataRequestScope(DataRequestScope dataRequestScope) {
this.dataRequestScope = dataRequestScope;
}
public DataSessionScope getDataSessionScope() {
return dataSessionScope;
}
public void setDataSessionScope(DataSessionScope dataSessionScope) {
this.dataSessionScope = dataSessionScope;
}
}
Controlador de Rest de Spring
Finalmente, creemos una clase RestController y configuremos algunos puntos finales de API para nuestros propósitos de prueba.
package com.journaldev.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloData {
@Autowired
private Customer customer;
@RequestMapping("/nameRS")
public String helloRS() {
return customer.getDataRequestScope().getName();
}
@RequestMapping("/nameSSUpdated")
public String helloSSUpdated() {
customer.getDataSessionScope().setName("Session Scope Updated");
return customer.getDataSessionScope().getName();
}
@RequestMapping("/nameSS")
public String helloSS() {
return customer.getDataSessionScope().getName();
}
}
Configuración de tiempo de espera de sesión de Spring Boot
Finalmente, tenemos que configurar variables de tiempo de espera de sesión de Spring Boot, agregue las siguientes propiedades en src/main/resources/application.properties
.
server.session.cookie.max-age= 1
server.session.timeout= 1
Ahora nuestros beans de Spring con ámbito de sesión se invalidarán en un minuto. Simplemente ejecute la clase SpringBootMvcApplication
como aplicación de arranque de Spring. Debería ver la siguiente salida para nuestros puntos finales configurados.
2018-05-23 17:02:25.830 INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameRS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloRS()
2018-05-23 17:02:25.831 INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSSUpdated]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSSUpdated()
2018-05-23 17:02:25.832 INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSS()
Prueba de ámbito de solicitud de Spring Bean
Abre cualquier navegador y ve a la URL https://localhost:8080/nameRS
y verifica la salida en la consola. Deberías ver DataRequestScope Constructor Called
impreso en cada solicitud.
Prueba de ámbito de sesión de Spring Bean
Ve a https://localhost:8080/nameSS
y obtendrás la siguiente salida. Ahora ve a
https://localhost:8080/nameSSUpdated
para que el valor del nombre de DataSessionScope
se actualice a Session Scope Updated
. Ahora ve de nuevo a
https://localhost:8080/nameSS
y deberías ver el valor actualizado. Para este momento, deberías ver
DataSessionScope Constructor Called at XXX
solo una vez en la salida de la consola. Ahora espera 1 minuto para que nuestro bean de alcance de sesión se invalide. Luego ve de nuevo a https://localhost:8080/nameSS
y deberías ver la salida original. Además, debes revisar el mensaje de la consola para la creación de DataSessionScope nuevamente por el contenedor. Eso es todo para el tutorial de alcance de beans de Spring.
Puedes descargar el proyecto Spring Beans Scope Spring Boot desde nuestro Repositorio de GitHub.
Source:
https://www.digitalocean.com/community/tutorials/spring-bean-scopes