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
- Os escopos dos Beans (Singleton, Prototype, etc.)
- Quando usar cada escopo
- Configuração com @Configuration
- Perfis (@Profile) para dev/test/prod
- @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
| Escopo | Descrição | Instâncias | Uso |
|---|---|---|---|
@Singleton | 1 instância para toda a aplicação | 1 | Padrão - Services, Repositories |
@Prototype | Nova instância a cada injeção | Várias | Objetos com estado |
@RequestScope | 1 instância por requisição HTTP | 1/requisição | Objetos ligados à requisição |
@SessionScope | 1 instância por sessão de usuário | 1/sessão | Carrinho, preferências do usuário |
@ApplicationScope | 1 instância para a aplicação web | 1 | Semelhante 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
| Escopo | Instâncias | Tempo de Vida | Uso Principal |
|---|---|---|---|
| Singleton | 1 para o app | Inicialização → Parada | Services, Repos (padrão) |
| Prototype | 1 por obtenção | Criação → GC | Objetos com estado |
| Request | 1 por requisição HTTP | Requisição → Resposta | Logs, contexto da requisição |
| Session | 1 por sessão do usuário | Início da sessão → fim | Carrinho, 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
- Qual a diferença entre Singleton e Prototype?
- Quando usar RequestScope?
- Como ativar um perfil Spring?
- Qual a diferença entre application.properties e application-dev.properties?
- 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!