Escopos do Spring Bean

Os escopos de Beans do Spring nos permitem ter um controle mais granular da criação das instâncias dos beans. Às vezes, queremos criar uma única instância do bean como singleton, mas em outros casos, podemos querer que ele seja criado a cada solicitação ou apenas uma vez em uma sessão.

Escopos de Beans do Spring

Existem cinco tipos de escopos de beans do Spring:

  1. singleton – apenas uma instância do bean do Spring será criada para o container do Spring. Este é o escopo padrão do bean do Spring. Ao usar esse escopo, certifique-se de que o bean não tenha variáveis de instância compartilhadas, caso contrário, poderá levar a problemas de inconsistência de dados.
  2. prototype – uma nova instância será criada sempre que o bean for solicitado ao container do Spring.
  3. request – é o mesmo que o escopo de protótipo, no entanto, é destinado a ser usado em aplicativos da web. Uma nova instância do bean será criada para cada solicitação HTTP.
  4. session – um novo bean será criado para cada sessão HTTP pelo container.
  5. global-session – isso é usado para criar beans de sessão global para aplicativos de portlet.

Escopo Singleton e Protótipo do Spring Bean

Os escopos singleton e protótipo do Spring podem ser usados em aplicativos Spring autônomos. Vamos ver como podemos configurar facilmente esses escopos usando a anotação @Scope. Digamos que temos uma classe de bean Java.

package com.journaldev.spring;

public class MyBean {

	public MyBean() {
		System.out.println("MyBean instance created");
	}

}

Vamos definir a classe de configuração do Spring onde iremos definir o método para obter uma instância de MyBean do contêiner 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();
	}
	
}

Observe que singleton é o escopo padrão, então podemos remover @Scope(value="singleton") da definição de bean acima. Agora, vamos criar um método principal e testar o escopo 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();
	}

}

Quando o programa acima é executado, obteremos uma saída como abaixo.

MyBean instance created
867988177
867988177

Observe que ambas as instâncias de MyBean têm o mesmo hashcode e o construtor é chamado apenas uma vez, o que significa que o contêiner Spring está sempre retornando a mesma instância de MyBean. Agora, vamos alterar o escopo para protótipo.

@Bean
@Scope(value="prototype")
public MyBean myBean() {
	return new MyBean();
}

Desta vez, obteremos a seguinte saída quando o método principal é executado.

MyBean instance created
867988177
MyBean instance created
443934570

Está claro que a instância de MyBean é criada toda vez que é solicitada ao contêiner Spring. Agora, vamos alterar o escopo para requisição.

@Bean
@Scope(value="request")
public MyBean myBean() {
	return new MyBean();
}

Neste caso, obteremos a seguinte exceção.

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)

É porque os escopos request, session e global-session não estão disponíveis para aplicações independentes.

Escopo de Solicitação e Sessão do Bean Spring

Para o exemplo de escopo de bean de solicitação e sessão do Spring, criaremos uma aplicação web Spring Boot. Crie um projeto inicializador do Spring Boot e escolha “web” para que possamos executá-lo como uma aplicação web. Nosso projeto final ficará como na imagem abaixo. ServletInitializer e SpringBootMvcApplication são classes Spring Boot geradas automaticamente. Não precisamos fazer nenhuma alteração lá. Aqui está o meu arquivo pom.xml, dê uma olhada nas dependências para nossa aplicação. Seu arquivo pom.xml pode ser ligeiramente diferente dependendo da versão do Eclipse que você está usando.

<?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>

Vamos criar alguns componentes Spring e configurá-los como beans Spring no contêiner Spring com escopo request e session.

Escopo de Bean de Solicitação do 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;
	}
}

Escopo de Sessão 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 Spring

Agora vamos criar um componente Spring e usar o Spring para configurar automaticamente os beans acima.

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 Rest do Spring

Finalmente, vamos criar uma classe RestController e configurar alguns pontos finais da API para nossos propósitos de teste.

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();
	}
}

Configuração de Tempo Limite da Sessão Spring Boot

Finalmente, temos que configurar as variáveis de tempo limite da sessão do Spring Boot, adicionar as propriedades abaixo em src/main/resources/application.properties.

server.session.cookie.max-age= 1
server.session.timeout= 1

Agora nossos beans Spring com escopo de sessão serão invalidados em um minuto. Apenas execute a classe SpringBootMvcApplication como uma aplicação Spring Boot. Você deverá ver a saída abaixo para nossos endpoints sendo 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()

Teste de Escopo de Solicitação de Spring Bean

Abra qualquer navegador e vá para o URL https://localhost:8080/nameRS e verifique a saída do console. Você deverá ver Construtor de DataRequestScope Chamado sendo impresso em cada solicitação.

Teste de Escopo de Sessão de Spring Bean

Vá para https://localhost:8080/nameSS e você obterá a seguinte saída. Agora vá para https://localhost:8080/nameSSUpdated para que o valor do nome de DataSessionScope seja atualizado para Session Scope Updated. Agora, vá novamente para https://localhost:8080/nameSS e você deverá ver o valor atualizado. Neste momento, você deverá ver DataSessionScope Constructor Called at XXX apenas uma vez na saída do console. Agora, aguarde 1 minuto para que nosso bean de escopo de sessão seja invalidado. Em seguida, vá novamente para https://localhost:8080/nameSS e você deverá ver a saída original. Além disso, verifique a mensagem do console para a criação do DataSessionScope novamente pelo contêiner. Isso é tudo para o tutorial de escopo de beans do Spring.

Você pode baixar o projeto Spring Beans Scope Spring Boot do nosso Repositório no GitHub.

Source:
https://www.digitalocean.com/community/tutorials/spring-bean-scopes