Pular para o conteúdo principal

Capítulo 4: Escopos e Configuração

Introdução​

Você já sabe criar Beans, mas sabe quantas instâncias o Spring cria? Apenas uma para toda a aplicação? Uma nova a cada vez?

Este capítulo ensina como controlar o ciclo de vida dos Beans com os Escopos.

O que você vai aprender​

  1. Os escopos dos Beans (Singleton, Prototype, etc.)
  2. Quando usar cada escopo
  3. Configuração com @Configuration
  4. Perfis (@Profile) para dev/test/prod
  5. @ComponentScan

Duração: 45 minutos


Os Escopos dos Beans​

O que é um Escopo?​

Escopo = Tempo de vida e quantidade de instâncias de um Bean

Pergunta: Quando o Spring cria UserService, quantas instâncias ele cria?

  • Apenas uma para toda a aplicação? (Singleton)
  • Uma nova a cada injeção? (Prototype)
  • Uma por requisição HTTP? (Request)
  • Uma por sessão de usuário? (Session)

O escopo determina a resposta!


Os 5 Escopos Disponíveis​

EscopoDescriçãoInstânciasUso
@Singleton1 instância para toda a aplicação1Padrão - Services, Repositories
@PrototypeNova instância a cada injeçãoVáriasObjetos com estado
@RequestScope1 instância por requisição HTTP1/requisiçãoObjetos ligados à requisição
@SessionScope1 instância por sessão de usuário1/sessãoCarrinho, preferências do usuário
@ApplicationScope1 instância para a aplicação web1Semelhante ao Singleton para web

1. Escopo Singleton (Padrão)​

Princípio​

Spring cria UMA ÚNICA instância do Bean
Todos os componentes compartilham a MESMA instância

Código​

@Service  // Singleton por padrão
public class UserService {

private int counter = 0;

public void incrementCounter() {
counter++;
}

public int getCounter() {
return counter;
}
}

Teste​

@RestController
public class TestController {

private final UserService userService;

public TestController(UserService userService) {
this.userService = userService;
}

@GetMapping("/test")
public String test() {
userService.incrementCounter();
return "Counter: " + userService.getCounter();
}
}

Resultado:

Requisição 1: Counter: 1
Requisição 2: Counter: 2
Requisição 3: Counter: 3
...

Por quê? É a MESMA instância para todas as requisições!


Visualização​

Todos compartilham a mesma instância!


Quando Usar Singleton?​

✅ Services sem estado (stateless)

@Service
public class EmailService {
public void send(String to, String message) {
// Sem variáveis de instância mutáveis
// Apenas métodos que trabalham com os parâmetros
}
}

✅ Repositories

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Sem estado
}

✅ Controllers

@RestController
public class UserController {
// Sem estado (as dependências são final)
}

Regra: Se o Bean não tem estado mutável, use Singleton (padrão)!


2. Escopo Prototype​

Princípio​

Spring cria uma NOVA instância toda vez que o Bean é solicitado
Cada componente tem sua PRÓPRIA instância

Código​

import org.springframework.context.annotation.Scope;

@Service
@Scope("prototype") // ← Nova instância a cada vez
public class ShoppingCart {

private List<Product> items = new ArrayList<>();

public void addProduct(Product product) {
items.add(product);
}

public List<Product> getItems() {
return items;
}
}

Teste​

@RestController
public class CartController {

private final ApplicationContext context;

public CartController(ApplicationContext context) {
this.context = context;
}

@GetMapping("/test-prototype")
public String testPrototype() {
// Recuperar 2 instâncias
ShoppingCart cart1 = context.getBean(ShoppingCart.class);
ShoppingCart cart2 = context.getBean(ShoppingCart.class);

// Modificar cart1
cart1.addProduct(new Product("Laptop"));

// Verificar cart2
return "Cart1: " + cart1.getItems().size() +
", Cart2: " + cart2.getItems().size();
}
}

Resultado:

Cart1: 1, Cart2: 0

Por quê? São instâncias DIFERENTES!


Visualização​

Cada componente tem sua própria instância!


Quando Usar Prototype?​

✅ Objetos com estado

@Scope("prototype")
public class ReportGenerator {
private String title;
private List<Data> data;
// Estado específico de cada relatório
}

✅ Objetos temporários

@Scope("prototype")
public class FileProcessor {
private File currentFile;
// Processamento de um arquivo específico
}

⚠️ Atenção: Prototype com Singleton = problema!


Problema: Prototype em Singleton​

@Service  // Singleton
public class OrderService {

private final ShoppingCart cart; // Prototype

// ❌ PROBLEMA!
public OrderService(ShoppingCart cart) {
this.cart = cart;
// cart é injetado UMA ÚNICA VEZ (na inicialização)
// Mesmo que ShoppingCart seja Prototype!
}
}

Resultado: O Prototype se comporta como Singleton!

Solução: Use ObjectProvider ou ApplicationContext.getBean() para obter uma nova instância a cada vez.


3. Escopo Request (Web)​

Princípio​

1 nova instância POR requisição HTTP
A instância existe durante toda a requisição
Destruída após a resposta

Código​

import org.springframework.web.context.annotation.RequestScope;

@Component
@RequestScope // ← Nova instância por requisição
public class RequestLogger {

private String requestId;
private LocalDateTime startTime;

@PostConstruct
public void init() {
this.requestId = UUID.randomUUID().toString();
this.startTime = LocalDateTime.now();
System.out.println("Requisição " + requestId + " iniciada");
}

@PreDestroy
public void cleanup() {
Duration duration = Duration.between(startTime, LocalDateTime.now());
System.out.println("Requisição " + requestId + " finalizada em " +
duration.toMillis() + "ms");
}
}

Utilização​

@RestController
public class UserController {

private final RequestLogger logger;

public UserController(RequestLogger logger) {
this.logger = logger;
}

@GetMapping("/users")
public List<User> getUsers() {
logger.log("Recuperando usuários");
// ... lógica
}
}

Resultado:

Requisição 1:
Requisição abc-123 iniciada
Requisição abc-123 finalizada em 45ms

Requisição 2:
Requisição def-456 iniciada
Requisição def-456 finalizada em 32ms

Cada requisição tem sua própria instância!


4. Escopo Session (Web)​

Princípio​

1 instância por sessão de usuário
Compartilhada entre todas as requisições do mesmo usuário
Destruída quando a sessão expira

Código​

import org.springframework.web.context.annotation.SessionScope;

@Component
@SessionScope // ← Instância por sessão de usuário
public class UserPreferences {

private String theme = "light";
private String language = "fr";

public void setTheme(String theme) {
this.theme = theme;
}

public String getTheme() {
return theme;
}
}

Utilização: Carrinho de compras, preferências, autenticação


Comparação dos Escopos​

Tabela Resumo​

EscopoInstânciasTempo de VidaUso Principal
Singleton1 para o appInicialização → ParadaServices, Repos (padrão)
Prototype1 por obtençãoCriação → GCObjetos com estado
Request1 por requisição HTTPRequisição → RespostaLogs, contexto da requisição
Session1 por sessão do usuárioInício da sessão → fimCarrinho, preferências

Visualização Temporal​

Singleton:     [════════════════════════════════] (toda a aplicação)

Prototype: [===] [===] [===] [===] [===] (sob demanda)

Request: [req1] [req2] [req3] [req4] (por requisição)

Session: [═══ Sessão User1 ═══]
[═══════ Sessão User2 ═══════]

Configuração com @Configuration​

Classe de Configuração Completa​

@Configuration
public class AppConfig {

// Bean Singleton (padrão)
@Bean
public EmailService emailService() {
EmailService service = new EmailService();
service.setHost("smtp.gmail.com");
return service;
}

// Bean Prototype
@Bean
@Scope("prototype")
public ReportGenerator reportGenerator() {
return new ReportGenerator();
}

// Bean condicional (explicado abaixo)
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new H2DataSource();
}

@Bean
@Profile("prod")
public DataSource prodDataSource() {
return new PostgreSQLDataSource();
}
}

Perfis (@Profile)​

Problema​

Você tem diferentes configurações conforme o ambiente:

Desenvolvimento:
- Banco H2 em memória
- Logs detalhados
- Email em modo fake

Produção:
- Banco PostgreSQL
- Logs mínimos
- Email real

Como gerenciar isso de forma limpa?


Solução: Perfis Spring​

Criar Beans conforme o perfil ativo:

@Configuration
public class DatabaseConfig {

@Bean
@Profile("dev") // ← Ativo apenas em dev
public DataSource devDataSource() {
System.out.println("Usando H2 (dev)");
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}

@Bean
@Profile("prod") // ← Ativo apenas em prod
public DataSource prodDataSource() {
System.out.println("Usando PostgreSQL (prod)");
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("admin");
config.setPassword("secret");
return new HikariDataSource(config);
}

@Bean
@Profile("test") // ← Ativo apenas em test
public DataSource testDataSource() {
System.out.println("Usando H2 (test)");
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("test-schema.sql")
.build();
}
}

Ativar um Perfil​

Método 1: application.properties

spring.profiles.active=dev

Método 2: Variável de ambiente

export SPRING_PROFILES_ACTIVE=prod

Método 3: Argumento JVM

java -jar app.jar --spring.profiles.active=prod

Método 4: IDE (VS Code, IntelliJ)

"spring.profiles.active": "dev"

Perfis Múltiplos​

Ativar vários perfis:

spring.profiles.active=dev,debug,mock-email

Bean ativo para vários perfis:

@Bean
@Profile({"dev", "test"}) // ← Ativo em dev OU test
public DataSource devOrTestDataSource() {
return new H2DataSource();
}

Exemplo Completo: Configuração por Ambiente​

// Interface comum
public interface EmailService {
void send(String to, String message);
}

// Implementação DEV (fake)
@Service
@Profile("dev")
public class FakeEmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("FAKE EMAIL para " + to + ": " + message);
}
}

// Implementação PROD (real)
@Service
@Profile("prod")
public class RealEmailService implements EmailService {
@Override
public void send(String to, String message) {
// Lógica real de envio via SMTP
smtpClient.send(to, message);
}
}

Em dev: Nenhum email real enviado
Em prod: Emails reais enviados


Arquivos de Configuração por Perfil​

Estrutura​

src/main/resources/
├── application.properties (comum)
├── application-dev.properties (apenas dev)
├── application-test.properties (apenas test)
└── application-prod.properties (apenas prod)

Exemplo​

application.properties (comum):

app.name=My Application
server.port=8080

application-dev.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.show-sql=true
logging.level.root=DEBUG

application-prod.properties:

spring.datasource.url=jdbc:postgresql://prod-server:5432/mydb
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.show-sql=false
logging.level.root=WARN

Ativação:

spring.profiles.active=dev

O Spring carrega automaticamente o application-dev.properties!


@ComponentScan​

Problema​

Por padrão, o Spring escaneia apenas o package da classe principal e seus subpackages.

com.example.demo
├── DemoApplication.java ← Classe principal
├── service/
│ └── UserService.java ✅ Escaneado
└── controller/
└── UserController.java ✅ Escaneado

com.external.library
└── ExternalService.java ❌ NÃO escaneado

Solução: @ComponentScan​

@SpringBootApplication
@ComponentScan(basePackages = {
"com.example.demo", // Package principal
"com.external.library" // Package externo
})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

Agora ExternalService será escaneado!


Filtrar o Scan​

Excluir certas classes:

@ComponentScan(
basePackages = "com.example.demo",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {OldService.class, DeprecatedController.class}
)
)

Incluir apenas certas anotações:

@ComponentScan(
basePackages = "com.example.demo",
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Service.class // Apenas @Service
),
useDefaultFilters = false
)

Exemplo Completo: Aplicação Multi-Ambientes​

// Classe principal
@SpringBootApplication
public class ECommerceApplication {
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}

// Configuração
@Configuration
public class AppConfig {

// Email conforme ambiente
@Bean
@Profile("dev")
public EmailService devEmailService() {
return new FakeEmailService();
}

@Bean
@Profile("prod")
public EmailService prodEmailService() {
return new SmtpEmailService();
}

// Cache conforme ambiente
@Bean
@Profile("prod")
@Scope("singleton")
public CacheManager redisCacheManager() {
return new RedisCacheManager();
}

@Bean
@Profile("dev")
@Scope("singleton")
public CacheManager simpleCacheManager() {
return new SimpleCacheManager();
}
}

// Service com scope apropriado
@Service
@Scope("singleton") // Uma instância para o app
public class OrderService {

private final EmailService emailService;
private final CacheManager cacheManager;

public OrderService(EmailService emailService,
CacheManager cacheManager) {
this.emailService = emailService;
this.cacheManager = cacheManager;
}
}

// Bean com estado (Prototype)
@Component
@Scope("prototype") // Nova instância a cada vez
public class Invoice {
private Order order;
private BigDecimal total;

public void generate(Order order) {
this.order = order;
this.total = calculateTotal(order);
}
}

Resumo do Capítulo​

Escopos​

@Scope("singleton")   // 1 instância (padrão)
@Scope("prototype") // N instâncias
@RequestScope // 1 por requisição HTTP
@SessionScope // 1 por sessão user

Perfis​

@Profile("dev")       // Ativo em dev
@Profile("prod") // Ativo em prod
@Profile({"dev","test"}) // Ativo em dev OU test

Ativação:

spring.profiles.active=dev

Configuração​

@Configuration        // Classe de config
@Bean // Criar um Bean
@ComponentScan // Escanear packages

Pontos-Chave​

✅ Singleton por padrão (services sem estado)
✅ Prototype para objetos com estado
✅ Request/Session para web
✅ @Profile para gerenciar dev/test/prod
✅ application-{profile}.properties para configuração
✅ @ComponentScan para packages externos


Autoavaliação​

  1. Qual a diferença entre Singleton e Prototype?
  2. Quando usar RequestScope?
  3. Como ativar um perfil Spring?
  4. Qual a diferença entre application.properties e application-dev.properties?
  5. Para que serve o @ComponentScan?

Parabéns! 🎉​

Você concluiu toda a teoria do Módulo 5!

Agora você entende:

  • ✅ Inversão de Controle (IoC)
  • ✅ Injeção de Dependências (DI)
  • ✅ Beans Spring
  • ✅ Escopos e Configuração

Próxima etapa: Praticar com os TPs!


➡️ Começar TP1: Injeção Avançada

⬅️ Capítulo anterior: Beans

⬅️ Voltar ao Módulo 5