Autoconfiguración de Spring Boot
Entiende cómo funciona la autoconfiguración de Spring Boot bajo el capó con ejemplos prácticos de configuración de Tomcat embebido y beans condicionales.
Spring Boot ha ganado mucha popularidad desde que fue lanzado por primera vez por Pivotal (ahora parte de VMware). Como se describe en su sitio web, facilita la creación de aplicaciones basadas en Spring independientes y de grado de producción que puedes “simplemente ejecutar”. También intenta ser opinionado para reducir el boilerplate de configuración. Pero, ¿te has preguntado cómo logra Spring Boot eso? ¿Cómo configura tu aplicación automágicamente?
En este post, intentaré explicar el mecanismo de autoconfiguración enfocándome en la parte del servidor web embebido, que es una de las más interesantes. Al pasar por este ejemplo, entenderás cómo funciona la autoconfiguración y qué condiciones se usan para incluir (o no) una característica específica en tu aplicación.
Cómo funciona la autoconfiguración de Spring Boot
La lógica de autoconfiguración en Spring Boot está implementada usando algunas anotaciones @Conditional* de Spring, introducidas en Spring 4. Estas anotaciones permiten a Spring incluir (o no) beans en el Application Context dependiendo de la presencia de otros beans, clases en el classpath, propiedades de configuración, etc.
Al combinar estas condiciones con los JARs presentes en tu classpath, Spring Boot puede hacer una suposición bastante buena sobre qué funcionalidad necesita tu aplicación y configurar el Application Context en consecuencia.
Veamos cómo funciona esto explicando cómo se configura el servidor Tomcat embebido.
La configuración de Tomcat embebido
Cuando incluyes spring-boot-starter-web en tu proyecto, Spring Boot configurará automáticamente un servidor Tomcat embebido para ti. Pero ¿cómo sucede esta magia?
Sigamos la cadena de dependencias:
1. Dependencias Starter
Cuando agregas spring-boot-starter-web a tu proyecto, trae varias dependencias transitivas:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Este starter incluye:
spring-boot-starter-tomcat(Tomcat embebido)spring-webmvc(Spring MVC)- Otras dependencias relacionadas con web
2. Clases de autoconfiguración
La anotación @EnableAutoConfiguration (incluida en @SpringBootApplication) activa Spring Boot para buscar clases de autoconfiguración en el classpath.
Una de estas clases es ServletWebServerFactoryAutoConfiguration:
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
// Configuration logic here
}
3. Configuración condicional
Nota la anotación @ConditionalOnClass(ServletRequest.class). Esto significa que esta configuración solo se activará si ServletRequest.class está presente en el classpath.
La anotación @Import trae configuraciones específicas para diferentes servidores embebidos. Veamos la de Tomcat:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class,
search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
4. Las condiciones explicadas
La configuración EmbeddedTomcat tiene varias condiciones:
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }): Esta configuración solo se activa si todas estas clases están presentes en el classpath.@ConditionalOnMissingBean(value = ServletWebServerFactory.class): Este bean solo se crea si no existe ya un beanServletWebServerFactoryen el contexto de aplicación.
5. Cadena de dependencias
La presencia de estas clases en el classpath viene de la cadena de dependencias:
spring-boot-starter-web
└── spring-boot-starter-tomcat
└── tomcat-embed-core
└── Contiene Tomcat.class, UpgradeProtocol.class, etc.
Probar la autoconfiguración
Puedes ver qué autoconfiguraciones se están aplicando a tu aplicación agregando esto a tu application.properties:
logging.level.org.springframework.boot.autoconfigure=DEBUG
debug=true
Cuando ejecutes tu aplicación, verás una salida como:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
ServletWebServerFactoryAutoConfiguration matched:
- @ConditionalOnClass found required class 'javax.servlet.ServletRequest'
- found 'session' scope
ServletWebServerFactoryConfiguration.EmbeddedTomcat matched:
- @ConditionalOnClass found required classes 'javax.servlet.Servlet',
'org.apache.catalina.startup.Tomcat', 'org.apache.coyote.UpgradeProtocol'
- @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.server.ServletWebServerFactory;
SearchStrategy: CURRENT) did not find any beans
Negative matches:
-----------------
ServletWebServerFactoryConfiguration.EmbeddedJetty:
Did not match:
- @ConditionalOnClass did not find required class 'org.eclipse.jetty.server.Server'
ServletWebServerFactoryConfiguration.EmbeddedUndertow:
Did not match:
- @ConditionalOnClass did not find required class 'io.undertow.Undertow'
Escenarios comunes de autoconfiguración
Cambiar servidores embebidos
Si quieres usar Jetty en lugar de Tomcat, puedes:
- Excluir Tomcat del starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
- Agregar el starter de Jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Ahora las condiciones @ConditionalOnClass coincidirán con Jetty en lugar de Tomcat, y Spring Boot configurará Jetty automáticamente.
Configuración personalizada del servidor
También puedes proporcionar tu propio bean ServletWebServerFactory para personalizar la configuración del servidor:
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(9000);
factory.setSessionTimeout(10, TimeUnit.MINUTES);
return factory;
}
Dado que ahora hay un bean ServletWebServerFactory en el contexto, la condición @ConditionalOnMissingBean en la autoconfiguración evitará que se cree el predeterminado.
Anotaciones clave de autoconfiguración
Aquí están las anotaciones condicionales más comunes usadas en la autoconfiguración de Spring Boot:
@ConditionalOnClass: La configuración se incluye solo si las clases especificadas están presentes en el classpath@ConditionalOnMissingClass: La configuración se incluye solo si las clases especificadas NO están presentes@ConditionalOnBean: La configuración se incluye solo si el bean especificado está presente en el contexto@ConditionalOnMissingBean: La configuración se incluye solo si el bean especificado NO está presente en el contexto@ConditionalOnProperty: La configuración se incluye basándose en la presencia/valor de propiedades de configuración@ConditionalOnWebApplication: La configuración se incluye solo en aplicaciones web@ConditionalOnNotWebApplication: La configuración se incluye solo en aplicaciones no web
Crear autoconfiguración personalizada
Puedes crear tu propia autoconfiguración:
- Creando una clase de configuración con anotaciones condicionales
- Agregándola al archivo
META-INF/spring.factoriesen tu JAR - Incluyendo el JAR en el classpath de tu proyecto
Ejemplo spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyCustomAutoConfiguration
Entender la magia
La “magia” de la autoconfiguración de Spring Boot se reduce a:
- Valores predeterminados opinionados: Spring Boot hace suposiciones razonables sobre lo que necesitas basándose en tus dependencias
- Configuración condicional: Usando anotaciones
@Conditional*para incluir/excluir configuraciones basadas en classpath y contexto - Dependencias Starter: Conjuntos de dependencias cuidadosamente elaborados que traen las clases correctas para activar la autoconfiguración
- Capacidad de sobrescritura: Siempre puedes sobrescribir los valores predeterminados proporcionando tus propios beans
El ejemplo de Tomcat embebido muestra cómo todas estas piezas trabajan juntas:
- El
spring-boot-starter-webtrae las clases de Tomcat - La presencia de estas clases activa la autoconfiguración de Tomcat
- Si proporcionas tu propio bean de factory del servidor, la autoconfiguración se retira
Conclusión
La autoconfiguración de Spring Boot no es magia—es un uso inteligente de las características de configuración condicional de Spring combinadas con dependencias starter bien diseñadas. Al entender cómo funcionan @ConditionalOnClass, @ConditionalOnMissingBean, y otras anotaciones condicionales, puedes entender mejor qué está haciendo Spring Boot por ti y cómo personalizarlo cuando sea necesario.
El ejemplo del servidor embebido demuestra los principios clave:
- Las dependencias traen clases que activan la autoconfiguración
- Las anotaciones condicionales determinan cuándo deben aplicarse las configuraciones
- Siempre puedes sobrescribir los valores predeterminados proporcionando tus propios beans
- La salida de debug te ayuda a entender qué configuraciones se están aplicando
Esta comprensión te ayudará a depurar problemas, personalizar configuraciones, e incluso crear tus propias autoconfiguraciones cuando construyas bibliotecas reutilizables o frameworks internos.
Get More Architecture Insights
Join hundreds of software architects and engineers getting practical insights delivered to their inbox.