Spring Beans e Scopes: O guia definitivo sobre Singleton, Prototype, Request e Session
Você já enfrentou um bug fantasma onde dados se misturam entre requisições de usuários diferentes? O culpado quase sempre é a má configuração do escopo de um Spring Bean. Neste post, vamos desmistificar os escopos do Spring (Singleton, Prototype, Request e Session), entender quando usar cada um e evitar vazamentos de memória e falhas de segurança nas suas aplicações.
O Bug no Carrinho de Compras
Imagine o cenário: você constrói um e-commerce incrível. Na sua máquina local, rodando sozinho, tudo funciona perfeitamente. Porém, assim que a aplicação vai para produção com dezenas de usuários simultâneos, o caos acontece. O João adiciona um tênis no carrinho, mas quem acaba pagando por ele é a Maria.
O que aconteceu? Você acabou de ser vítima do Estado Compartilhado (Shared State) causado por um entendimento errado sobre como o Spring gerencia os seus objetos (Beans).
O que é um Spring Bean?
De forma simples e direta: um Bean é qualquer objeto instanciado, montado e gerenciado pelo container do Spring (o famoso ApplicationContext). Quando você coloca um @Component, @Service, @Repository ou @Controller em uma classe, você está dizendo: "Spring, cuida da criação e do ciclo de vida desse objeto pra mim".
Mas quanto tempo esse objeto vive? E quem tem acesso a ele? É aí que entram os 4 escopos principais.
1. Singleton
Este é o escopo padrão do Spring. Se você não especificar nada, o seu Bean será um Singleton. Isso significa que o Spring cria apenas uma única instância dessa classe e a reutiliza em toda a aplicação. Toda vez que alguém pedir esse Bean (via @Autowired ou injeção no construtor), o Spring entregará exatamente o mesmo objeto.
Quando usar? Para serviços Stateless (sem estado). Ou seja, classes que apenas executam regras de negócio, mas não guardam informações específicas de um usuário nos atributos da classe.
O Perigo: Se você criar variáveis globais dentro de um Service padrão, todos os usuários da aplicação vão compartilhar e sobrescrever essa mesma variável (o bug do carrinho de compras!).
@Service // Por padrão, é Singleton
public class RelatorioService {
public void gerar() {
System.out.println("Gerando relatório...");
}
}
2. Prototype (Um novo para cada chamada)
O escopo Prototype é o exato oposto do Singleton. Toda vez que o Spring precisar injetar esse Bean em algum lugar, ele usará o operador new por baixo dos panos e criará uma nova instância completamente independente.
Quando usar? Para objetos Stateful (com estado), onde você precisa guardar dados específicos dentro dos atributos da classe e não quer que outras partes do sistema ou outros usuários leiam essa mesma informação.
@Component
@Scope("prototype") // Agora, cada injeção gera um novo objeto
public class CarrinhoCompras {
private List<String> itens = new ArrayList<>();
// métodos de adicionar e remover
}
Aviso: Cuidado com o uso excessivo de Prototypes. Criar muitos objetos o tempo todo aumenta o trabalho do Garbage Collector do Java e pode gerar problemas de performance.
Os Escopos Exclusivos para Web
Se você está construindo uma API REST ou uma aplicação Web (usando spring-boot-starter-web), você ganha acesso a dois escopos extremamente úteis que resolvem a vida de quem trabalha com requisições HTTP.
3. Request (Uma vida curta)
Com o escopo de Request, o Spring cria uma nova instância do Bean para cada requisição HTTP que chega no servidor. Assim que a resposta (Response) for enviada de volta ao cliente, aquele Bean é destruído.
Quando usar? Útil para rastrear informações específicas de uma única chamada, como guardar os dados do usuário autenticado apenas enquanto processa aquela transação, ou guardar logs específicos de um endpoint.
@Component
@RequestScope // Atalho para @Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class RequestTracker {
private String traceId = UUID.randomUUID().toString();
// Essa ID será a mesma durante toda a rota HTTP atual, mas diferente na próxima
}
4. Session (Fidelidade com o usuário)
No escopo de Session, o Bean vive durante toda a Sessão HTTP do usuário. Diferente do Request (que morre assim que o endpoint responde), o Session mantém o objeto vivo enquanto o usuário estiver navegando pelo sistema.
Quando usar? Geralmente usado em aplicações monolíticas com renderização de telas no servidor (Thymeleaf, JSF), para guardar as preferências do usuário, histórico de navegação ou o próprio carrinho de compras atrelado ao navegador dele.
@Component
@SessionScope // Atalho para @Scope(value = WebApplicationContext.SCOPE_SESSION)
public class PreferenciasUsuario {
private String tema = "DARK_MODE";
}
Resumo prático para o seu dia a dia
Para não esquecer mais, salve essa regra de ouro:
Se o objeto só executa lógica e cálculos (Stateless): Deixe no padrão Singleton.
Se o objeto guarda dados temporários não-web (Stateful): Use Prototype.
Se o objeto guarda dados atrelados a uma chamada de API: Use Request.
Se o objeto guarda dados atrelados ao tempo que o usuário passa no site: Use Session.