Spring Boot

Andre Luis Gomes
Opensanca
Published in
7 min readSep 13, 2021

Conversando com diversos desenvolvedores, percebi serem poucos os que sabem como realmente o Spring Boot funciona. A verdade é que esse conhecimento não é essencial no cotidiano, e, convenhamos, a ideia do framework é abstrair o trabalho de certa forma, visando dar produtividade e sendo a realidade da maioria dos projetos e produtos que trabalhamos. Mas, sempre haverá um conhecimento conceitual necessário para resolvermos problemas mais complexos.

Isso me motivou a escrever esse texto, pois hoje eu encaro esse conhecimento “não essencial” com um diferencial no mercado, ainda mais um mercado extremamente aquecido que estamos vivendo, com dezenas de bootcamps formando profissionais com conhecimento para demandas de alta produtividade.

Acredito que já mencionei outras vezes sobre a relação de Pareto com 80%-20%, onde 20% do conhecimento é suficiente para resolvermos 80% dos problemas. Mas os problemas mais complexos vivem nos 20%, e, nessa altura deveríamos buscar os outros 80% de conhecimento.

Não vou entrar em muitos detalhes de como o Spring Framework funciona, apesar que, terei que tocar em alguns pontos para dar contexto, mas farei isso de forma mais sutil. Pretendo, um dia, escrever melhor como o Core funciona, sobre o IoC, o ApplicationContext, a CGLIB, Stereotypes, Beans. Com isso irei supor que você já conhece minimamente o que o Spring Framework faz e quais problemas ele resolve. Vamos nessa…

O que é o Spring Boot

Aplicações Web em Java sempre tiveram o esteriótipo de serem cercadas de boilerplate e setup para funcionarem. Configurações essas que demandavam conhecimentos de Servidores de Aplicação, Servelet, Especificações e outras coisas. O Spring resolveu muitos problemas na época do mundo Java Enterprise, mas vou deixar essa história para outro post.

Outras linguagens e frameworks da época traziam conceitos mais modernos de startup de projetos, com muito mais convenções a serem seguidas que configurações a serem feitas.

Vale mencionar também, que o advento de sistemas mais distribuídos ou microsserviços, demandaram novas formas de construir software, visando maior velocidade e produtividade.

Nesse contexto, surge o Spring Boot, em 2014

E a ideia era trazer todas as novas experiências de desenvolvimento de software e construções de produtos para o mundo Java + Spring. Reduzindo a necessidade de configurações iniciais e com poucos passos seguindo convenções, deveríamos ter uma aplicação rodando totalmente funcional.

O lema, que esta até hoje na documentação oficial é: Just Run.

Sem nenhuma geração de código e nenhuma configuração de XMLs.

Outro ponto importante é que, todo o boilerplate das configurações, estão escondidos no que o Spring Boot chama Starters. Que são dependências que adicionamos aos projetos para resolver determinados casos de uso, por exemplo: spring-boot-starter-web, que já traz todo o setup web para construções de APIs ou Sites, usando Containers Web.

Apesar dos esforços, não existe mágica, uma pequena configuração é necessária para o projeto funcionar. Esse setup inicial do projeto pode ser feito pelo site https://start.spring.io/ ou através de um plugin no IntelliJ.

Como ele funciona?

Agora começa a diversão.

O Core do Spring Boot possui todas as funcionalidades que nos permite rodar a aplicação e extrair do Spring Framework o máximo que pudermos, pois, como você deve ter notado, o Spring Boot, usa o Spring Framework claro.

Assim, todo projeto precisa ter esse core rodando

implementation("org.springframework.boot:spring-boot-starter")

Se você for curioso, assim como eu, pode conferir o projeto aqui 👇

Todo projeto precisa ter uma classe main, onde o Jar irá rodar para dar início no framework

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

Temos duas informações importantes aqui, a anotação @SpringBootApplication e o método estático SpringApplication.run().

Sobre o @SpringBootApplication

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
@Documented @Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

Essa anotação é uma meta-anotação, que decora a classe main com outras anotações:

  • @SpringBootConfiguration : Que define um Bean com @Configuration para a classe principal
  • @ComponentScan : Que indica quais pacotes o Spring irá escanear para procurar por Beans que deverão ser criados no container.
  • @EnableAutoConfiguration : Que faz um import muito importante para o processo do Spring. @Import(AutoConfigurationImportSelector.class), que em determinado momento na execução irá fazer um SpringFactoriesLoader.loadFactoryNames(), carregando diversos Beans que devem ser auto-configuráveis no Contexto do Spring.

Sobre o SpringApplication.run()

Esse runner da aplicação possui diversas responsabilidades, dentre elas:

  • Criar uma instância de SpringApplication()
  • Dar um run() na instância
  • Criar o ApplicationContext do Spring
  • Carregar o Logger
  • Carregar o banner
  • Disparar todos os listeners do startup do Contexto
  • Carregar todos os Factories do Spring

Abaixo um trecho do código que Spring que faz o serviço:

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java#L286

Uma coisa que eu realmente recomendo é usar o Debug da IDE e observar todo o ciclo de vida do Startup do framework. É muito divertido…

Durante o processo o Spring irá carregar e criar as instancias dos Beans.

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java#L420

Para fazer isso, o método getSpringFactoriesInstances irá usar SpringFactoriesLoader.loadFactoryNames(type, classLoader) nesse contexto, passando o classLoader para loadFactoryNames. Que por sua vez irá executar classLoader.getResources(FACTORIES_RESOURCE_LOCATION), onde FACTORIES_RESOURCE_LOCATION é META-INF/spring.factories

https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java#L135

Assim, percorrendo todo o classpath ele irá achar os arquivos de spring.factories e irá carregar todos os EnableAutoConfigurations que achar. Pegar as referências e criar as instancias. Abaixo a lista que o Spring Boot carrega com todo o setup do framework.

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Essa é uma parte da mágica do Spring Boot, onde cada configuração supostamente automática que ele faz quando adicionamos uma dependência no classpath, esta mapeada previamente esse projeto spring-boot-autoconfigure.

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories#L25
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
...
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
...

Ou seja, cada classe nesse enorme array org.springframework.boot.autoconfigure.EnableAutoConfiguration= será um bean de configuração dentro do Application Context.

Anatomia do projeto Spring Boot

Vale falar que o projeto Spring Boot é um mono repo contendo todos os starters e módulos necessários para o Spring Boot funcionar.
Cada Starter carrega suas dependências

Em spring-boot existe todo o código do framework necessário para ele rodar, inclusive classes necessárias para módulos específicos funcionarem, como: Web, Data, Hibernate, Log…

Em spring-boot-autoconfigure temos o spring.factories com todos os EnableAutoConfigurations e os respectivas classes de auto-configuração.

Em spring-boot-starters contém todos os builds de cada JAR para cada Starter, inclusive o proprio spring-boot-starter que é peça chave para os demais. Cada Starter carrega suas próprias dependências.

Como por exemplo o spring-boot-starter-web, que carrega o spring-boot-starter, spring-boot-starter-tomcat, spring-web e spring-webmvc.

Você deve estar se perguntando: “Então quando eu uso o Spring Boot ele carrega todas as classes de todos os projetos? Não deveria carregar apenas os Jars dos Starters que declaramos no build?

A resposta é a outra parte de mágia do Spring Boot…

Criação Condicional de Beans

O Spring criou uma técnica para validar condições antes da criação de beans, isso porque, ao criar o Bean, podem haver dependências e pré-condições para a criação. Exemplo quando o Spring-boot-starter-data-jpa.

Sua classe de @Configuration descrita no spring.factories é org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration e nela podemos observar que existem diversas anotações decorando a classe e o método que iria criar o @Bean.

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java#L82

Cada @Conditiional serve a um propósito e executa uma validação ou pre-condição para que o Bean seja criado.

Podemos achar a lista completa de condições no package org.springframework.boot.autoconfigure.condition em https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition

Os nomes são sugestivos, mas vamos a alguns exemplos:

  • @ConditionalOnBean : Valida a existência de outro bean para a criação deste
  • @ConditionalOnMissingBean : Valida que não existe um Bean criado para criar outro
  • @ConditionalOnProperty : Valida se existem propriedades definidas no application.properties podendo validar valores e assumir caso não exista.

Esses são apenas alguns exemplos, e com criatividade e conhecimento, pode-se criar combinações bem flexíveis e poderosas.

Em um momento oportuno irei escrever mais sobre cada condição, mas eu fiz alguns exemplos aqui 👇

Conclusão

Sem entrar em detalhes de como o IoC do Spring Framework funciona e sobre a criação de Beans, podemos ver como o Spring Boot funciona e toda a convenção e setup que existe.

Como o start do framework faz com que ele carregue muita configuração previamente definida, observando o classpath do projeto para achar mais Jars que podem habilitar mais configurações.

Cada configuração encontrada passa por uma série de condicionais com os @Conditional* para enriquecer o projeto com novos comportamentos e funcionalidades.

Espero que tenham gostado, pretendo mergulhar em outros assuntos no futuro sobre Spring.

Se algo não ficou claro, não deixe de comentar e/ou me chamar, farei o possível para revisar :)

--

--

Andre Luis Gomes
Opensanca

Software Engineering Specialist, Brazil. Developer as job and hobby. Blogger, and Podcaster.