Articles

Guía rápida de los Scopes de los Beans de Spring

Posted on

Resumen

En este tutorial rápido, aprenderemos los diferentes tipos de scopes de los beans en el framework de Spring.

El scope de un bean define el ciclo de vida y la visibilidad de ese bean en los contextos que lo usamos.

La última versión del framework de Spring define 6 tipos de ámbitos:

  • singleton
  • prototipo
  • solicitud
  • sesión
  • aplicación
  • websocket

Los últimos cuatro scopes mencionados, solicitud, sesión, aplicación y websocket, sólo están disponibles en una aplicación web-aware.

Lectura adicional:

¿Qué es un Spring Bean?

Una explicación rápida y práctica de lo que es un Spring Bean.
Leer más →

Anotaciones de Spring Bean

Aprende cómo y cuándo utilizar las anotaciones estándar de Spring Bean – @Component, @Repository, @Service y @Controller.
Lee más →

Alcance de Singleton

Cuando definimos un bean con el alcance de singleton, el contenedor crea una única instancia de ese bean; todas las peticiones para ese nombre de bean devolverán el mismo objeto, que se almacena en caché. Cualquier modificación del objeto se reflejará en todas las referencias al bean. Este ámbito es el valor por defecto si no se especifica ningún otro ámbito.

Creemos una entidad Persona para ejemplificar el concepto de ámbitos:

public class Person { private String name; // standard constructor, getters and setters}

Después, definimos el bean con el ámbito singleton utilizando la anotación @Scope:

@Bean@Scope("singleton")public Person personSingleton() { return new Person();}

También podemos utilizar una constante en lugar del valor String de la siguiente manera:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

Ahora podemos proceder a escribir un test que demuestre que dos objetos que hacen referencia al mismo bean tendrán los mismos valores, aunque sólo uno de ellos cambie su estado, ya que ambos están haciendo referencia a la misma instancia de bean:

private static final String NAME = "John Smith";@Testpublic void givenSingletonScope_whenSetName_thenEqualNames() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personSingletonA = (Person) applicationContext.getBean("personSingleton"); Person personSingletonB = (Person) applicationContext.getBean("personSingleton"); personSingletonA.setName(NAME); Assert.assertEquals(NAME, personSingletonB.getName()); ((AbstractApplicationContext) applicationContext).close();}

El fichero scopes.xml de este ejemplo debe contener las definiciones xml de los beans utilizados:

<?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 class="org.baeldung.scopes.Person" scope="singleton"/> </beans>

Alcance del prototipo

Un bean con el alcance del prototipo devolverá una instancia diferente cada vez que se solicite al contenedor. Se define estableciendo el valor prototype a la anotación @Scope en la definición del bean:

@Bean@Scope("prototype")public Person personPrototype() { return new Person();}

También podemos utilizar una constante como hicimos para el ámbito singleton:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Ahora escribiremos un test similar al anterior que muestre dos objetos solicitando el mismo nombre de bean con el ámbito prototype. Tendrán estados diferentes pues ya no se refieren a la misma instancia de bean:

private static final String NAME = "John Smith";private static final String NAME_OTHER = "Anna Jones";@Testpublic void givenPrototypeScope_whenSetNames_thenDifferentNames() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personPrototypeA = (Person) applicationContext.getBean("personPrototype"); Person personPrototypeB = (Person) applicationContext.getBean("personPrototype"); personPrototypeA.setName(NAME); personPrototypeB.setName(NAME_OTHER); Assert.assertEquals(NAME, personPrototypeA.getName()); Assert.assertEquals(NAME_OTHER, personPrototypeB.getName()); ((AbstractApplicationContext) applicationContext).close();}

El fichero scopes.xml es similar al presentado en la sección anterior mientras que se añade la definición xml para el bean con el ámbito del prototipo:

<bean class="org.baeldung.scopes.Person" scope="prototype"/>

Ámbitos conscientes de la web

Como se mencionó anteriormente, hay cuatro ámbitos adicionales que sólo están disponibles en un contexto de aplicación consciente de la web. Los utilizamos con menos frecuencia en la práctica.

El ámbito de solicitud crea una instancia de bean para una única solicitud HTTP, mientras que el ámbito de sesión crea una instancia de bean para una Sesión HTTP.

El ámbito de aplicación crea la instancia del bean para el ciclo de vida de un ServletContext, y el ámbito de websocket lo crea para una sesión WebSocket concreta.

Creemos una clase para usarla en la instanciación de los beans:

public class HelloMessageGenerator { private String message; // standard getter and setter}

4.1. Alcance de la petición

Podemos definir el bean con el alcance de la petición utilizando la anotación @Scope:

@Bean@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator();}

El atributo proxyMode es necesario porque en el momento de la instanciación del contexto de la aplicación web, no hay ninguna petición activa. Spring crea un proxy para inyectarlo como dependencia, e instanciar el bean de destino cuando se necesite en una petición.

También podemos utilizar una anotación compuesta @RequestScope que actúa como un atajo para la definición anterior:

@Bean@RequestScopepublic HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator();}

A continuación podemos definir un controlador que tenga una referencia inyectada al requestScopedBean. Necesitamos acceder a la misma petición dos veces para probar los ámbitos específicos de la web.

Si mostramos el mensaje cada vez que se ejecuta la petición, podemos ver que el valor se restablece a null, aunque luego se cambie en el método. Esto es debido a que se devuelve una instancia de bean diferente para cada petición.

@Controllerpublic class ScopesController { @Resource(name = "requestScopedBean") HelloMessageGenerator requestScopedBean; @RequestMapping("/scopes/request") public String getRequestScopeMessage(final Model model) { model.addAttribute("previousMessage", requestScopedBean.getMessage()); requestScopedBean.setMessage("Good morning!"); model.addAttribute("currentMessage", requestScopedBean.getMessage()); return "scopesExample"; }}

4.2. Session Scope

Podemos definir el bean con el ámbito de la sesión de una manera similar:

@Bean@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator();}

También hay una anotación compuesta dedicada que podemos utilizar para simplificar la definición del bean:

@Bean@SessionScopepublic HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator();}

A continuación definimos un controlador con una referencia al sessionScopedBean. De nuevo, necesitamos ejecutar dos peticiones para demostrar que el valor del campo mensaje es el mismo para la sesión.

En este caso, cuando se realiza la petición por primera vez, el valor mensaje es nulo. Sin embargo, una vez que se modifica, ese valor se mantiene para las siguientes peticiones ya que se devuelve la misma instancia del bean para toda la sesión.

@Controllerpublic class ScopesController { @Resource(name = "sessionScopedBean") HelloMessageGenerator sessionScopedBean; @RequestMapping("/scopes/session") public String getSessionScopeMessage(final Model model) { model.addAttribute("previousMessage", sessionScopedBean.getMessage()); sessionScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", sessionScopedBean.getMessage()); return "scopesExample"; }}

4.3. Ámbito de aplicación

El ámbito de aplicación crea la instancia del bean para el ciclo de vida de un ServletContext.

Es similar al ámbito singleton, pero hay una diferencia muy importante con respecto al ámbito del bean.

Cuando los beans tienen ámbito de aplicación, la misma instancia del bean se comparte entre múltiples aplicaciones basadas en servlets que se ejecutan en el mismo ServletContext, mientras que los beans con ámbito singleton tienen ámbito sólo para un único contexto de aplicación.

Creemos el bean con el ámbito de aplicación:

@Bean@Scope( value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator();}

De forma análoga a los ámbitos de petición y sesión, podemos utilizar una versión más corta:

@Bean@ApplicationScopepublic HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator();}

Ahora vamos a crear un controlador que haga referencia a este bean:

@Controllerpublic class ScopesController { @Resource(name = "applicationScopedBean") HelloMessageGenerator applicationScopedBean; @RequestMapping("/scopes/application") public String getApplicationScopeMessage(final Model model) { model.addAttribute("previousMessage", applicationScopedBean.getMessage()); applicationScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", applicationScopedBean.getMessage()); return "scopesExample"; }}

En este caso, una vez establecido en el applicationScopedBean, el valor del mensaje se conservará para todas las peticiones posteriores, sesiones e incluso para las diferentes aplicaciones servlet que accedan a este bean, siempre que se ejecute en el mismo ServletContext.

4.4. Ámbito WebSocket

Por último, vamos a crear el bean con el ámbito websocket:

@Bean@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator websocketScopedBean() { return new HelloMessageGenerator();}

Cuando se accede por primera vez, los beans con ámbito WebSocket se almacenan en los atributos de la sesión WebSocket. A continuación, se devuelve la misma instancia del bean cada vez que se accede a ese bean durante toda la sesión de WebSocket.

También podemos decir que exhibe un comportamiento singleton, pero limitado únicamente a una sesión de WebSocket.

Conclusión

En este artículo, hemos hablado de los diferentes ámbitos de bean que proporciona Spring y cuáles son sus usos previstos.

La implementación de este artículo se puede encontrar en el proyecto GitHub.

Iniciarse en Spring 5 y Spring Boot 2, a través del curso Learn Spring: >> EL CURSO

Deja una respuesta

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