Spring Boot e Spring Cloud

Spring Boot explicado: starters, autoconfiguração e sua primeira aplicação

Iniciante10 min de leituraAtualizado

Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês

Ao final deste guia, você vai entender o que o Spring Boot acrescenta ao Spring Framework, como um starter e uma classe de autoconfiguração trabalham juntos, como ler o relatório de avaliação de condições quando um bean está faltando e como escrever sua primeira aplicação Boot 3 sem tratar o framework como mágica.

O que você vai aprender

  • Explicar o que o Spring Boot acrescenta ao Spring Framework e o que ele não muda
  • Descrever como starters, classes de autoconfiguração e condições produzem beans na inicialização
  • Criar e executar uma aplicação Spring Boot 3 com Maven e entender seu ponto de entrada
  • Ler o relatório de avaliação de condições para descobrir por que uma autoconfiguração foi ou não aplicada
  • Sobrescrever ou excluir uma autoconfiguração com segurança declarando seu próprio bean

O Spring Boot é hoje a forma como a maioria dos serviços Java é construída, e o motivo não é um novo modelo de programação. O Boot mantém o Spring Framework exatamente como ele é e elimina aquilo que antes consumia a primeira semana de um projeto: escolher versões compatíveis de bibliotecas, ligar os beans de infraestrutura, configurar um contêiner de servlets e escrever o descritor de implantação. Você adiciona uma dependência, o Boot percebe e uma configuração sensata aparece. Esta página explica a engrenagem por trás dessa experiência para que você consiga prever o que o Boot vai fazer, descobrir por que ele fez outra coisa e sobrescrevê-lo quando a opinião dele não for a sua. Tudo aqui se refere ao Spring Boot 3 com Java 17 ou superior.

O que o Boot acrescenta e o que ele deixa como está

O Spring Framework oferece um contêiner de inversão de controle, programação orientada a aspectos, gerenciamento de transações, Spring MVC e uma longa lista de integrações. O que ele propositalmente não oferece é uma opinião sobre como montar tudo isso. Por isso, uma aplicação Spring clássica declarava à mão, em XML ou em configuração Java, um DataSource, um EntityManagerFactory, um gerenciador de transações, um DispatcherServlet, conversores de mensagens e um view resolver, e empacotava tudo como um WAR para um servidor de aplicação externo.

O Spring Boot contribui com quatro coisas sobre essa base.

Gerenciamento de dependências. O BOM (bill of materials) spring-boot-dependencies fixa a versão de várias centenas de bibliotecas que sabidamente funcionam juntas. Você declara spring-boot-starter-web sem versão e herda um conjunto coerente.

Starters. Um starter é um artefato Maven ou Gradle vazio cuja única função é trazer um grupo selecionado de dependências para uma capacidade: web, dados, segurança, validação, testes.

Autoconfiguração. Um conjunto de classes @Configuration que registram beans de infraestrutura apenas quando as condições certas são atendidas: a biblioteca está no classpath, não existe bean do usuário daquele tipo, uma propriedade está definida.

Recursos de produção. Um servidor servlet ou reativo embutido para que a aplicação rode com java -jar, configuração externalizada e o módulo Actuator para endpoints de health, métricas e gerenciamento.

Nada nessa lista muda a forma de escrever um service, um repository ou um controller. Se você consegue ler uma aplicação Spring comum, consegue ler uma aplicação Boot; a diferença está em quanta configuração de infraestrutura aparece no código-fonte.

Uma primeira aplicação

Gere um projeto no Spring Initializr (o site, a CLI spring ou o assistente embutido no IntelliJ IDEA, no Eclipse e no VS Code) com a dependência Web. Quem usa Maven recebe um pom.xml cujo parent é o spring-boot-starter-parent:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.5.5</version>
</parent>

<properties>
  <java.version>21</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-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

O POM parent fornece o BOM, configurações sensatas de compilador e de filtragem de recursos, e o plugin que reempacota o build em um jar executável. Se a sua organização já tem um parent corporativo, importe spring-boot-dependencies em dependencyManagement com <scope>import</scope>; você perde os padrões do plugin, mas mantém o alinhamento de versões.

O ponto de entrada é uma única classe:

package com.example.shop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class ShopApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShopApplication.class, args);
    }
}

@RestController
class HelloController {

    @GetMapping("/hello")
    String hello() {
        return "Hello from Spring Boot";
    }
}

Rode ./mvnw spring-boot:run (ou java -jar target/shop-0.0.1-SNAPSHOT.jar depois de ./mvnw package) e o Tomcat responde na porta 8080. Você não declarou o contêiner de servlets, nem o DispatcherServlet, nem o conversor JSON do Jackson, nem a página de erro: a autoconfiguração fez isso, porque o spring-boot-starter-web colocou as classes necessárias no classpath.

@SpringBootApplication é uma composição de três anotações:

AnotaçãoEfeito
@SpringBootConfigurationMarca a classe como @Configuration e permite que os testes a encontrem automaticamente
@EnableAutoConfigurationLiga o mecanismo de autoconfiguração descrito abaixo
@ComponentScanVarre o pacote desta classe e seus subpacotes em busca de @Component, @Service, @Repository, @Controller e @Configuration

O detalhe do component scan tem uma consequência prática: coloque a classe da aplicação no pacote raiz (com.example.shop), e não no pacote padrão nem em um subpacote como com.example.shop.config. Classes fora da árvore varrida são ignoradas silenciosamente, e essa é uma das surpresas mais comuns do tipo “meu bean não foi encontrado”.

Starters: pacotes de dependências com uma convenção de nomes

Abra o spring-boot-starter-web na visão de dependências da sua IDE e você não vai encontrar código, apenas dependências transitivas: spring-boot-starter (o núcleo, logging e suporte a YAML), spring-boot-starter-json (Jackson), spring-boot-starter-tomcat e os módulos do framework spring-web/spring-webmvc. Os starters existem para que “quero construir uma aplicação web” seja uma linha em vez de oito, e para que trocar de tecnologia seja questão de exclusões: exclua spring-boot-starter-tomcat, adicione spring-boot-starter-jetty e o servidor embutido muda sem nenhuma alteração de código.

Os starters oficiais se chamam spring-boot-starter-*. A documentação de referência pede que terceiros usem o padrão inverso, acme-spring-boot-starter, para que o prefixo spring-boot-starter- fique reservado ao time do Spring. Essa convenção mostra de relance se um starter é mantido pelo projeto ou por uma comunidade.

Alguns starters que vale a pena conhecer logo:

StarterO que ele traz
spring-boot-starter-webSpring MVC, Jackson, Tomcat embutido
spring-boot-starter-webfluxStack web reativa sobre Reactor Netty
spring-boot-starter-data-jpaSpring Data JPA, Hibernate, pool de conexões HikariCP
spring-boot-starter-validationJakarta Bean Validation com Hibernate Validator
spring-boot-starter-securitySpring Security com uma configuração segura por padrão
spring-boot-starter-actuatorEndpoints de health, métricas e gerenciamento
spring-boot-starter-testJUnit 5, AssertJ, Mockito, Spring Test, asserções de JSON

Repare que o spring-boot-starter-validation é separado do -web. Essa divisão aconteceu no Boot 2.3; se @Valid parece não fazer nada em um projeto novo, geralmente a causa é a falta desse starter.

Como a autoconfiguração funciona

Autoconfiguração é configuração Java comum do Spring com dois acréscimos: um arquivo de registro que lista as classes candidatas e condições que decidem se cada uma se aplica.

Registro

Desde o Boot 2.7, e exclusivamente desde o 3.0, as classes de autoconfiguração são listadas, um nome totalmente qualificado por linha, em um recurso chamado META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports dentro do jar que as fornece. A antiga chave de autoconfiguração do spring.factories foi removida no 3.0; uma biblioteca que ainda dependa dela não será detectada. O @EnableAutoConfiguration lê todos esses arquivos do classpath e importa as classes que encontra. Só o jar spring-boot-autoconfigure traz bem mais de cem.

Condições

Cada classe, e muitas vezes cada método @Bean dentro dela, carrega condições do pacote org.springframework.boot.autoconfigure.condition:

CondiçãoAplica-se quando
@ConditionalOnClass / @ConditionalOnMissingClassUma classe está (ou não está) no classpath
@ConditionalOnBean / @ConditionalOnMissingBeanUm bean de determinado tipo ou nome já está (ou não está) definido
@ConditionalOnPropertyUma propriedade tem um valor específico, ou está ausente com matchIfMissing
@ConditionalOnWebApplication / @ConditionalOnNotWebApplicationA aplicação é (ou não é) uma aplicação web servlet ou reativa
@ConditionalOnResourceUm recurso, como um arquivo, existe no classpath
@ConditionalOnExpressionUma expressão SpEL é avaliada como true

É o @ConditionalOnMissingBean que torna o Boot educado. As classes de autoconfiguração são processadas depois das suas próprias classes @Configuration, então, sempre que você declara um bean de um tipo que o Boot criaria, a versão do Boot recua. É por isso que o conselho padrão para personalizar o Boot é “defina o bean você mesmo”, e não “desligue o bean do Boot”.

Aqui está uma versão simplificada da autoconfiguração do data source, escrita do jeito que você escreveria a sua:

@AutoConfiguration
@ConditionalOnClass(DataSource.class)          // only if JDBC support is present
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean                  // back off if the app declared one
    @ConditionalOnProperty(prefix = "spring.datasource", name = "url")
    DataSource dataSource(DataSourceProperties properties) {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(properties.getUrl());
        ds.setUsername(properties.getUsername());
        ds.setPassword(properties.getPassword());
        return ds;
    }
}

@AutoConfiguration (introduzida no 2.7) substitui @Configuration nessas classes. Ela desativa o proxy dos métodos @Bean, de que a autoconfiguração não precisa, e aceita os atributos before/after para ordenação em relação a outras autoconfigurações. A ordem importa porque uma condição como @ConditionalOnBean só faz sentido se o bean que ela procura já teve a chance de ser registrado.

Escrevendo a sua

Times que mantêm vários serviços costumam acabar com uma biblioteca compartilhada que configura os aspectos comuns do mesmo jeito em todo lugar: um ObjectMapper ajustado, um cliente HTTP com headers de tracing, um tratador de erros padrão. Empacotar isso como autoconfiguração, e não como classe base, deixa os serviços consumidores livres para sobrescrever qualquer peça:

acme-http-spring-boot-starter/
└── src/main/resources/META-INF/spring/
    └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
        (contains: com.acme.http.AcmeRestClientAutoConfiguration)
@AutoConfiguration
@ConditionalOnClass(RestClient.class)
public class AcmeRestClientAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    RestClient.Builder acmeRestClientBuilder() {
        return RestClient.builder()
                .defaultHeader("X-Acme-Caller", "shop");
    }
}

Mantenha as classes de autoconfiguração fora do alcance do @ComponentScan das aplicações que as usam; se um scan as captura como classes @Configuration comuns, a garantia de ordem se perde e o @ConditionalOnMissingBean pode se comportar de forma inesperada. Basta colocar a biblioteca em uma hierarquia de pacotes própria (com.acme.http, e não com.example.shop.*).

Descobrindo o que aconteceu

A autoconfiguração é determinística, mas nem sempre óbvia, e o Boot oferece duas formas de ver as decisões dela.

Inicie a aplicação com --debug (ou debug=true no application.properties) e leia o relatório de avaliação de condições no log. Ele tem três partes: correspondências positivas (autoconfigurações aplicadas, com a condição que as deixou passar), correspondências negativas (as que não foram, com a condição que falhou) e exclusões. Uma entrada negativa típica fica assim:

DataSourceAutoConfiguration:
   Did not match:
      - @ConditionalOnClass did not find required class 'javax.sql.DataSource'

Essa linha diz exatamente qual dependência adicionar. Quando um bean está inesperadamente ausente, esse relatório responde à pergunta mais rápido do que ler o código-fonte.

Se o starter do Actuator estiver presente, a mesma informação fica disponível em tempo de execução pelo endpoint conditions depois que você o expõe, o que é útil em ambientes onde não dá para reiniciar com uma flag.

Sobrescrevendo e excluindo

Existem três níveis de controle, do mais suave ao mais drástico.

  1. Defina uma propriedade. A maioria das autoconfigurações é guiada por classes @ConfigurationProperties, então server.port, spring.datasource.url ou spring.jackson.default-property-inclusion mudam o comportamento sem mexer no código. A lista completa está no apêndice “Common Application Properties” da documentação de referência.
  2. Declare o bean você mesmo. Graças ao @ConditionalOnMissingBean, o seu bean substitui o do Boot. Se você precisa do bean do Boot mais um ajuste, procure um ponto de extensão do tipo customizer: WebMvcConfigurer, Jackson2ObjectMapperBuilderCustomizer, RestClientCustomizer, TomcatConnectorCustomizer e muitos outros existem justamente para ajustar sem substituir.
  3. Exclua a autoconfiguração. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) ou a propriedade spring.autoconfigure.exclude a removem por completo. Use isso quando a biblioteca estiver no classpath por um motivo que não tem relação com o recurso que o Boot deduz, por exemplo um driver JDBC usado apenas por uma ferramenta em tempo de build.

Recorra ao nível 3 raramente. Uma autoconfiguração excluída costuma ter dependentes (DataSourceAutoConfiguration alimenta JPA, templates JDBC, Flyway e health indicators), e excluir uma pode desencadear uma cadeia de beans ausentes mais difícil de depurar do que o problema original.

Erros comuns

Classe da aplicação no pacote errado. Componentes em um pacote irmão ou pai nunca são varridos. Mova a classe para o pacote raiz ou adicione scanBasePackages ao @SpringBootApplication; a primeira opção é mais limpa.

Brigar com o POM parent. Sobrescrever uma versão gerenciada (por exemplo, forçar um Hibernate mais novo) é legítimo, mas deve ser feito pela propriedade que o BOM expõe (<hibernate.version>), e não declarando uma versão na dependência, para que o alinhamento das transitivas continue valendo. Leia antes as release notes da biblioteca: a versão do BOM é aquela com a qual a autoconfiguração do Boot foi testada.

Achar que um starter equivale a um recurso. Adicionar spring-boot-starter-data-jpa sem um driver de banco de dados e sem spring.datasource.url faz a inicialização falhar com uma mensagem sobre banco de dados embutido. O Boot só consegue configurar o que o classpath e as propriedades descrevem.

Misturar javax.* e jakarta.*. Uma aplicação Boot 3 com uma dependência que ainda usa javax.servlet ou javax.persistence compila, mas falha em tempo de execução com NoClassDefFoundError ou entidades não mapeadas. Verifique se cada biblioteca de terceiros tem uma versão compatível com Jakarta antes de atualizar.

Tratar --debug como nível de log. A flag ativa o relatório de condições e o logging DEBUG de alguns loggers do núcleo, não dos seus próprios pacotes; para isso, use logging.level.com.example=DEBUG.

Principais pontos

  • O Spring Boot é o Spring Framework mais gerenciamento de dependências, starters, autoconfiguração e recursos de produção; o modelo de programação não muda.
  • Um starter é um pacote de dependências; uma autoconfiguração é uma classe @Configuration condicional registrada em META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
  • Condições como @ConditionalOnClass e @ConditionalOnMissingBean fazem os beans do Boot recuarem sempre que você declara os seus, que é a forma prevista de personalizá-lo.
  • @SpringBootApplication combina configuração, autoconfiguração e component scan a partir do pacote da classe para baixo, por isso a localização dela importa.
  • Quando um bean está faltando ou aparece de forma inesperada, o relatório de avaliação de condições (--debug, ou o endpoint conditions do Actuator) mostra qual condição decidiu.
  • Prefira propriedades e beans customizer a excluir autoconfigurações; exclusões têm efeito cascata.

Fontes e leituras complementares

Perguntas frequentes

O Spring Boot é um framework diferente do Spring?
Não. O Spring Boot é uma camada sobre o Spring Framework. Todo bean, anotação e abstração que você usa em uma aplicação Boot (injeção de dependências, @Transactional, Spring MVC, Spring Data) vem do framework e dos projetos do seu ecossistema. O Boot acrescenta um gerenciamento de dependências opinativo, autoconfiguração, um servidor embutido e recursos de produção como o Actuator.
Por que o Spring Boot 3 exige Java 17 e os pacotes jakarta.*?
O Spring Boot 3 é construído sobre o Spring Framework 6, que definiu o Java 17 como versão mínima e migrou do Java EE para o Jakarta EE 9+. O Jakarta EE 9 renomeou todos os pacotes de API javax.* para jakarta.*, então mudam os imports de servlet, persistência, validação e outras APIs. Bibliotecas que ainda distribuem classes javax.* não vão funcionar com o Boot 3 até serem atualizadas.
Como saber quais autoconfigurações foram aplicadas à minha aplicação?
Inicie a aplicação com a propriedade debug ativada (por exemplo, --debug na linha de comando ou debug=true no application.properties). O Boot registra no log um relatório de avaliação de condições com as correspondências positivas, as negativas e a condição exata que decidiu cada uma. Com o Actuator, você consulta os mesmos dados em tempo de execução pelo endpoint conditions.
Se eu definir meu próprio bean DataSource, o Boot ainda vai criar um?
Não. O DataSourceAutoConfiguration é protegido por @ConditionalOnMissingBean, e a configuração do usuário é processada antes da autoconfiguração. O seu bean prevalece e o autoconfigurado é ignorado. Esse recuo automático é a forma padrão de personalizar o Boot: defina o bean você mesmo em vez de brigar com os padrões.
A autoconfiguração deixa a inicialização mais lenta?
Cada classe de autoconfiguração é avaliada na inicialização, mas a maioria é descartada quase imediatamente por uma verificação @ConditionalOnClass barata, que falha antes mesmo de a classe ser carregada. O que domina o tempo de inicialização é o trabalho dos beans que de fato são criados (abrir pools de conexão, montar os metamodelos do Hibernate, subir o Tomcat), e não a avaliação das condições em si.