一、从 SPI 到 Spring Boot 自动装配
1,什么是 SPI
SPI (Service Provider Interface) 是 Java 提供的一种 服务发现机制,用于实现模块之间的解耦,基本思想是:
我来定义接口,由你来提供实现,并通过标准配置注册,我再用 ServiceLoader 动态加载你的实现类。
这种机制常用于第三方模块的自动发现和加载,典型场景包括 JDBC 驱动,日志系统等
这就是 Java SPI 的核心机制:接口标准化、实现解耦、运行时动态加载。
2,SPI 示例 Demo
定义一个支付接口:
public interface PayService {
void pay(double amount);
}
第三方提供实现
public class AlipayService implements PayService {
public void pay(double amount) {
System.out.println("Paid " + amount + " via Alipay.");
}
}
在资源目录下添加配置文件
- 路径:resources/META-INF/services/com.example.PayService
- 内容:
--- 文件名:com.example.PayService
com.example.impl.AlipayService
调用方式:
public static void main(String[] args) {
// ServiceLoad 加载第三方实现类
ServiceLoader<PayService> pays = ServiceLoader.load(PayService.class);
// 调用第三方类的实现
pays.iterator().next().pay(99.99);
}
3,SPI 优势与局限:
优势:
- 支持接口和实现的完全解耦
- 不修改主程序代码,仅通过引入不同的 Jar 包即可切换实现
局限性:
- 不支持实现类优先级排序,存在多个实现时无法指定用哪个
- 不支持注入参数(如配置文件中的值),不具备依赖注入能力
- 配置能力有限,缺乏条件控制与运行时灵活性
4,Spring Boot 的演进思路
Spring Boot 在设计自动装配机制时,借鉴了 SPI 思想,但做了大量增强:
- 定义统一的自动配置接口标准(通常是 @Configuration 类)
- 借助 spring.factories 声明自动装配类
- 结合 Spring IOC 容器,通过条件注解决定是否注入
- 借助 @ConfigurationProperties 实现动态参数注入
- 最终实现按需,灵活,可配置,可扩展的自动装配能力
二、Spring Boot 是如何发现和加载这些自动装配类的
Spring Boot 项目的启动类标注了 @SpringBootApplication 注解,它是三个注解的组合:
- @SpringBootConfiguration
- @ComponentScan
- @EnableAutoConfiguration
其中,最关键的是 @EnableAutoConfiguration,它是 Spring Boot 核心自动装配的入口。
自动装配的加载流程( 以 Spring Boot 2.x 为例):
1,触发导入选择器 AutoConfigurationImportSelector
@EnableAutoConfiguration 背后由 AutoConfigurationImportSelector 执行,负责扫描并导入所有声明为自动装配类的配置
2,读取所有 Jar 包中的 META-INF/spring.factories
Spring Boot 会从所有依赖 Jar 包中加载路径为 META-INF/spring.factories 的配置文件,读取 EnableAutoConfiguration 对应的配置类列表:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...
Spring Boot 扫描到这些类后,并不是全部直接生效,而是要结合条件注解进行 按需激活 。
3,根据条件注解判断是否需要激活配置类
自动装配的关键能力是 按需注入,Spring Boot 提供了一整套 @Conditional 注解,用来实现 Bean 的有条件注册:
- @ConditionalOnClass:某个类存在才加载(典型用法:第三方依赖是否存在)
- @ConditionalOnProperty:某个配置项为 true 才加载
- @ConditionalOnMissingBean:若容器中没有某个 Bean 时才注入(提供默认实现)
- @ConditionalOnWebApplication:当前项目是 Web 应用时加载
示例:
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
这个条件表示:如果用户没有自定义ObjectMapper,那么就提供一个默认的,如果用户手写了:
@Bean
public ObjectMapper myObjectMapper() {
...
}
那么 Spring Boot 就不会注册默认的那个。
4,将自动装配类注册为 Spring Bean,并注入配置参数
被激活的配置类其实就是一个普通的 @Configuration 配置类,里面会通过 @Bean 方法,将这些对象注入到 Spring 容器中。
示例:
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
有些组件还需要从配置文件中读取参数(如连接池大小,超时时间等),此时,Spring Boot 提供了强大的配置绑定机制@ConfigurationProperties,实现类型安全的注入。
@ConfigurationProperties配置绑定机制:
@ConfigurationProperties 可以将整个配置前缀统一绑定为一个 Java 对象,支持嵌套对象,列表,Map等复杂结构。
示例:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String host;
private int port;
private int timeout;
// getter/setter
}
在自动配置类中即可注入使用:
@Bean
public RedisConnectionFactory redisConnectionFactory(RedisProperties props) {
return new LettuceConnectionFactory(props.getHost(), props.getPort());
}
Spring Boot 会自动从配置文件中读取 spring.redis.* 的值并在 bean 对象的实例化之前注入,开发者无需手动解析配置。
自动装配的关键特性
- 统一的 Bean 注册入口:所有第三方提供的 Bean 都通过标准配置类注入
- 灵活的条件控制机制:能够根据环境、依赖、配置等条件动态决定是否启用
- 与配置文件的集成:可通过 @ConfigurationProperties 自动绑定配置
- 模块解耦与替换能力强:原始 Bean 可以通过 @ConditionalOnMissingBean 覆盖
三、Spring Boot 如何通过自动装配接管 Spring MVC —— DispatcherServlet 的全自动注册过程
在传统的 Spring MVC 项目中,我们需要在 web.xml 中手动注册 DispatcherServlet,非常繁琐:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Spring Boot 的目标是 零配置,我们只需要引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
就能自动完成 DispatcherServlet 的创建和注册。背后的完整流程如下:
1,spring.factories 中声明自动配置类
在 spring-boot-autoconfig 包中的 META-INF/spring.factories 文件中,Spring Boot 提前注册了用于 Web 环境的自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
...
这意味着,一旦 EnableAutoConfiguration 被启用( 通过@SpringBootApplication 间接开启 ),这个类就具备被 Spring 容器加载的资格。
2,启动时加载配置类
Spring Boot 启动时,会通过 AutoConfigurationImportSelector 扫描所有依赖包的 spring.factories 文件,并将这些配置类导入容器中。
这时,DispatcherServletAutoConfiguration 会作为候选自动配置类被注册。
3,DispatcherServletAutoConfiguration 的主要职责
这个类其实做了两件事:
- 定义 DispatcherServlet Bean
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
DispatcherServlet 是 Spring MVC 的核心调度器,用于接收请求、分发处理、调用控制器,它的注册是 Web 应用初始化的关键步骤。本质上就是从 spring-webmvc 提供的 DispatcherServlet 类创建的。
- 定义 ServletRegistrationBean,用于注册 Servlet
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(dispatcherServlet, this.serverProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
return registration;
}
这个 ServletRegisterationBean 就相当于 Spring Boot 版的 web.xml,它会把 DispatcherServlet 注册到内嵌的 Tomcat 中,完成路径映射。
4,条件注解保障默认行为和可扩展性
- @ConditionalOnMissingBean:如果你自己没有定义 DispatcherServlet 或 ServletRegisterationBean ,Spring Boot 才会帮你注册
- @ConditionalOnBean:保证在存在 DispatcherServlet 的前提下才进行注册。
这种设计非常灵活,如果我们自己写一个 @Bean,Spring Boot 就 让位,否则就用默认的。
5,结合配置文件,实现个性化控制
Spring Boot 还支持通过配置文件中的 spring.mvc.* 属性对 DispatcherServlet 的行为进行定制:
spring:
mvc:
servlet:
path: /api/* # 修改 DispatcherServlet 映射路径
throw-exception-if-no-handler-found: true
这些配置会在自动配置类中通过 @ConfigurationProperties 注入,影响 Servlet 的创建逻辑,做到动态、可配置、类型安全。
小结:Spring Boot 用一套自动装配链路实现了实现了传统 Spring MVC 的 Servlet 注册逻辑,不仅减少了冗余配置,还通过条件注解,配置绑定等机制,实现了默认即用,按需替换,灵活扩展的开发体验
四、总结
Spring Boot 的自动装配不仅是对传统 Java SPI 思想的继承,更是在其基础上进行了条件控制,配置绑定,IOC 集成等一系列增强,形成了现代化的组件解耦与模块治理机制。
我们日常开发时只是 @Autowired 一行代码,但背后都是一次次精妙的自动装配过程。