Profiles no Spring Boot: configuração dev, test, prod
1. Visão geral
Uma aplicação geralmente roda em múltiplos ambientes. Sua máquina usa banco de dados H2. O servidor de teste usa PostgreSQL separado. Production usa um cluster real com SMTP real. Se você for editar o arquivo de configuração manualmente cada vez que faz deploy, mais cedo ou mais tarde vai acabar mandando configuração de dev para prod.
Spring Boot resolve isso com profiles. Depois de ler este artigo, você saberá como separar arquivos de configuração por profile, ligar/desligar beans com @Profile, ativar profiles de várias formas, escrever testes com @ActiveProfiles e evitar os erros mais comuns.
2. O que é profile?
Profile é um rótulo com nome, por exemplo dev, test, prod. Spring usa esse rótulo para tomar duas decisões:
- Qual configuração é carregada:
application-dev.ymlsó é lido quando o profiledevestá ativo. - Qual bean é criado: um bean marcado com
@Profile("prod")só é registrado quando o profileprodestá ativo.
Se nenhum profile for ativado, Spring usa o profile padrão chamado default. Beans sem @Profile são sempre criados, não importa qual profile está ativo.
Você precisa apenas da dependência web básica para os exemplos deste artigo:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3. Arquivos de configuração por profile
3.1. Separar em múltiplos arquivos
A convenção é nomear como application-{profile}.yml (ou .properties). O arquivo application.yml contém configurações comuns, sempre carregadas:
# application.yml
spring:
application:
name: shop-service
profiles:
default: dev
app:
mail:
from: no-reply@shop.vn
host: localhost
Arquivos específicos de profile contêm apenas as diferenças:
# application-dev.yml
server:
port: 8080
logging:
level:
root: DEBUG
# application-test.yml
server:
port: 0
app:
mail:
host: fake-smtp
# application-prod.yml
server:
port: 80
logging:
level:
root: WARN
app:
mail:
host: ${MAIL_HOST}
Explicação:
spring.profiles.default: devsignifica que quando você não ativa nenhum profile, Spring usadevem vez dedefault. Sua máquina de desenvolvimento roda imediatamente sem precisar de parâmetros.- Quando o profile
prodestá ativo, Spring carregaapplication.ymlprimeiro e depoisapplication-prod.yml. Valores no arquivo de profile sobrescrevem valores comuns. Por issoapp.mail.hostse torna o valor da variável de ambienteMAIL_HOST, masapp.mail.frompermaneceno-reply@shop.vn. server.port: 0emtestfaz Spring escolher uma porta aleatória, evitando conflitos ao rodar em paralelo.
3.2. Consolidar em um único arquivo YAML
Se quiser menos arquivos, você pode usar YAML com múltiplos documentos, separados por ---:
# application.yml
app:
mail:
host: localhost
---
spring:
config:
activate:
on-profile: prod
app:
mail:
host: smtp.shop.vn
O segundo documento só é válido quando prod está ativo. Essa abordagem é compacta quando cada profile tem poucas diferenças. Para configurações longas, separar em arquivos ainda é mais fácil de ler e revisar.
Nota: Em arquivos
.properties, use a linha#---para separar documentos em vez de---.
4. Ativar profiles
Há várias formas. A ordem de prioridade segue a regra geral do Spring Boot: argumentos da linha de comando > variáveis de ambiente > arquivo de configuração.
Argumentos da linha de comando:
java -jar shop-service.jar --spring.profiles.active=prod
Propriedade do sistema JVM:
java -Dspring.profiles.active=prod -jar shop-service.jar
Variável de ambiente, o jeito mais comum em Docker e Kubernetes:
export SPRING_PROFILES_ACTIVE=prod
java -jar shop-service.jar
Ao rodar com o plugin Maven:
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
Via código, útil quando você sempre quer adicionar um profile específico:
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ShopApplication.class);
app.setAdditionalProfiles("metrics");
app.run(args);
}
}
setAdditionalProfiles adiciona profiles à lista, não substitui profiles já ativados de fora. Se rodar com --spring.profiles.active=prod, tanto prod quanto metrics estarão ativos.
Você pode ativar múltiplos profiles de uma vez com vírgulas: --spring.profiles.active=prod,eu. Se dois arquivos definem a mesma chave, o profile que vem depois vence. Nesse exemplo, application-eu.yml sobrescreve application-prod.yml.
Quando a aplicação inicia, o log diz qual profile está rodando:
INFO c.e.shop.ShopApplication : The following 1 profile is active: "prod"
Se nenhum profile estiver ativo e você também não definir spring.profiles.default, você verá:
INFO c.e.shop.ShopApplication : No active profile set, falling back to 1 default profile: "default"
Sempre dê uma olhada nessa linha de log após fazer deploy. É o jeito mais rápido de descobrir se o servidor está rodando com o profile errado.
5. Ligar/desligar beans com @Profile
Configuração diferente é apenas metade do problema. Às vezes você precisa de implementações completamente diferentes. Por exemplo: dev apenas imprime e-mail no console, test salva em memória para verificar, prod envia via SMTP.
Defina uma interface comum:
public interface NotificationService {
void send(String to, String message);
}
Implementação para dev:
@Service
@Profile("dev")
public class ConsoleNotificationService implements NotificationService {
@Override
public void send(String to, String message) {
System.out.println("[DEV] Enviando para " + to + ": " + message);
}
}
Implementação para test, que guarda mensagens para assertions:
@Service
@Profile("test")
public class InMemoryNotificationService implements NotificationService {
private final List<String> sentMessages = new CopyOnWriteArrayList<>();
@Override
public void send(String to, String message) {
sentMessages.add(to + "|" + message);
}
public List<String> getSentMessages() {
return List.copyOf(sentMessages);
}
}
Implementação para prod:
@Service
@Profile("prod")
public class SmtpNotificationService implements NotificationService {
private final String host;
public SmtpNotificationService(@Value("${app.mail.host}") String host) {
this.host = host;
}
@Override
public void send(String to, String message) {
// Na prática usaria JavaMailSender; aqui é só para ilustrar
System.out.println("[SMTP " + host + "] Enviando para " + to);
}
}
A classe que usa não precisa saber em qual ambiente está:
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void placeOrder(String customerEmail, long orderId) {
notificationService.send(customerEmail, "Pedido #" + orderId + " foi criado");
}
}
A qualquer momento, apenas um dos três beans NotificationService é criado. Spring injeta o bean correto em OrderService. Rodando com profile dev e chamando placeOrder("an@shop.vn", 42), você recebe:
[DEV] Enviando para an@shop.vn: Pedido #42 foi criado
5.1. Expressões de profile
@Profile suporta expressões lógicas:
@Configuration
public class SecurityConfig {
@Bean
@Profile("!prod")
public CommandLineRunner seedDemoData() {
return args -> System.out.println("Carregando dados de demo...");
}
@Bean
@Profile("dev | test")
public CommandLineRunner relaxedCors() {
return args -> System.out.println("CORS aberto para todos os origins");
}
}
!prod: cria o bean em qualquer profile excetoprod.dev | test: cria quandodevoutestestá ativo. Também tem¶ “e”, por exemploprod & eu.
@Profile pode ser colocado em classes @Component/@Configuration ou em métodos @Bean individuais. Quando colocado em @Configuration, todos os beans dentro compartilham a mesma condição.
5.2. Verificar profile no código
Às vezes você precisa criar lógica condicional. Use Environment:
@Component
public class StartupBanner implements CommandLineRunner {
private final Environment environment;
public StartupBanner(Environment environment) {
this.environment = environment;
}
@Override
public void run(String... args) {
if (environment.acceptsProfiles(Profiles.of("prod"))) {
System.out.println("Rodando em PRODUCTION");
}
}
}
Use essa abordagem com moderação. Se o código está cheio de if (prod), você deveria separar em beans distintos com @Profile.
6. Grupos de profiles
Conforme o sistema cresce, você pode querer dividir configurações em partes menores, por exemplo proddb para banco de dados e prodmq para message broker. Grupos de profiles permitem ativar um conjunto inteiro com um único nome:
# application.yml
spring:
profiles:
group:
prod: proddb, prodmq
dev: devdb
Agora rodar --spring.profiles.active=prod ativa prod, proddb e prodmq. Spring carrega em sequência application-prod.yml, application-proddb.yml, application-prodmq.yml. O operador só precisa lembrar um nome.
7. Escrever testes com @ActiveProfiles
Em testes, você seleciona o profile com @ActiveProfiles. O teste abaixo verifica duas coisas: o bean correto é injetado e a configuração de application-test.yml está em efeito.
@SpringBootTest
@ActiveProfiles("test")
class OrderServiceProfileTest {
@Autowired
private OrderService orderService;
@Autowired
private NotificationService notificationService;
@Value("${app.mail.host}")
private String mailHost;
@Test
void shouldUseInMemoryNotificationInTestProfile() {
assertThat(notificationService).isInstanceOf(InMemoryNotificationService.class);
}
@Test
void shouldRecordNotificationWhenOrderPlaced() {
orderService.placeOrder("binh@shop.vn", 7);
var inMemory = (InMemoryNotificationService) notificationService;
assertThat(inMemory.getSentMessages())
.contains("binh@shop.vn|Pedido #7 foi criado");
}
@Test
void shouldOverrideMailHostFromTestProfile() {
assertThat(mailHost).isEqualTo("fake-smtp");
}
}
Explicação:
@ActiveProfiles("test")ativa o profiletestpara o contexto dessa classe, não importa o quespring.profiles.defaultseja.- O primeiro teste confirma que Spring escolheu
InMemoryNotificationServiceem vez de enviar e-mail de verdade. - O segundo teste usa o bean em-memory para verificar o conteúdo da mensagem sem precisar fazer mock.
- O terceiro teste prova que
app.mail.hostdeapplication-test.ymlsobrescreveu o valorlocalhostemapplication.yml.
Nota: Cada combinação diferente de profiles cria um contexto Spring separado. Se cada classe de teste usa um conjunto diferente de profiles, o cache de contexto não reutiliza e a suite de testes fica lenta. Mantenha o número de combinações de profiles em testes no mínimo.
8. Erros comuns
Definir spring.profiles.active em arquivo de profile. A partir do Spring Boot 2.4, declarar spring.profiles.active ou spring.profiles.include em application-dev.yml (ou em um documento com on-profile) causa erro na inicialização:
InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from
location 'class path resource [application-dev.yml]' is invalid in a profile specific resource
Use grupos de profiles (seção 6) em vez disso.
Hard-code spring.profiles.active=dev em application.yml. Esse arquivo é empacotado no jar. Se alguém esquecer de passar a variável de ambiente no servidor, prod rodará com configuração de dev. Use spring.profiles.default para o ambiente local e sempre ative profiles explicitamente no servidor.
Colocar segredos em application-prod.yml. Senhas de banco de dados e chaves de API não devem estar no Git. Use placeholders como ${DB_PASSWORD} e forneça o valor via variável de ambiente ou gerenciador de segredos.
Nenhum bean correspondendo ao profile. Se você rodar com profile staging mas só tem implementações para dev, test, prod, a aplicação reportará erro:
Parameter 0 of constructor in OrderService required a bean of type
'NotificationService' that could not be found.
Considere um bean fallback com @Profile("!prod") ou adicione uma implementação para o novo profile.
Abusar de profiles para feature flags. Profiles são bons para descrever ambientes. Se você quer ligar/desligar um recurso independentemente do ambiente, @ConditionalOnProperty geralmente é mais claro:
@Bean
@ConditionalOnProperty(name = "app.feature.loyalty-points", havingValue = "true")
public LoyaltyService loyaltyService() {
return new LoyaltyService();
}
Quando você tem profiles como prod-with-loyalty ou dev-no-cache, é sinal de que deveria migrar para properties.
9. Conclusão
Profiles permitem separar configurações e comportamento por ambiente sem modificar código. Você viu como usar application-{profile}.yml para sobrescrever configurações, @Profile para escolher implementações, grupos de profiles para reunir múltiplos profiles, e @ActiveProfiles para testes.
Alguns princípios para lembrar: coloque configurações comuns em application.yml, escreva apenas as diferenças nos arquivos de profile. Ative profiles no servidor via variável de ambiente ou argumentos da linha de comando. Nunca coloque segredos no Git. E sempre verifique a linha de log The following profiles are active após cada deploy.