🔬 Spring Boot 自动配置 vs 原生 Spring

以 DispatcherServlet 为例的源码级深度对比

基于 spring-framework 7.x / spring-boot 4.x 本地源码

📋 目录

🎯 场景:搭一个 Spring MVC Web 项目

先看 DispatcherServlet 初始化时需要哪些组件(源码 spring-webmvc/DispatcherServlet.java:441):

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);          // 1. 文件上传解析器
    initLocaleResolver(context);             // 2. 国际化解析器
    initHandlerMappings(context);            // 3. 处理器映射器(URL → Controller)
    initHandlerAdapters(context);            // 4. 处理器适配器(调用Controller方法)
    initHandlerExceptionResolvers(context);  // 5. 异常解析器
    initRequestToViewNameTranslator(context);// 6. 视图名称翻译器
    initViewResolvers(context);              // 7. 视图解析器
    initFlashMapManager(context);            // 8. Flash属性管理器
}
⚠️ 这 8 个组件,原生 Spring 全部要你手动配置,Spring Boot 全部自动搞定。

原生 Spring 手动配置全过程

① web.xml(必须手动声明 Servlet)

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

② spring-mvc.xml(每个组件都要手写)

<context:component-scan base-package="com.example.controller"/>
<mvc:annotation-driven/>

<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- 文件上传 -->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10485760"/>
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

<!-- 静态资源 -->
<mvc:resources mapping="/static/**" location="/static/"/>

<!-- 国际化 -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="zh_CN"/>
</bean>

<!-- 异常解析 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value="error"/>
</bean>

③ 还要手动整合数据源、事务、MyBatis...

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 还没完...还有 Jackson消息转换器、跨域配置、字符编码Filter等 -->
⚠️ 痛点总结:搭一个 Web 项目,配置文件 200+ 行 XML,没有条件判断——写了就生效,classpath 缺类就报错。

Spring Boot 自动配置全过程

Spring Boot 用户只需要:

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

# application.yml(只写差异化配置)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: "123456"
💡 没了!DispatcherServlet、视图解析器、文件上传、静态资源、字符编码、Jackson、HikariCP数据源、事务管理器……全部自动配好。

🔍 全链路源码追踪(5步)

1@SpringBootApplication 拆开看

// 源码:spring-boot-autoconfigure/SpringBootApplication.java
@SpringBootConfiguration     // 就是 @Configuration
@EnableAutoConfiguration     // ← 核心!开启自动配置
@ComponentScan(              // 组件扫描
    excludeFilters = {
        @Filter(type = CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = CUSTOM, classes = AutoConfigurationExcludeFilter.class)
    })
public @interface SpringBootApplication { ... }

2@EnableAutoConfiguration 导入选择器

// 源码:EnableAutoConfiguration.java
@AutoConfigurationPackage      // 自动注册主类所在包
@Import(AutoConfigurationImportSelector.class)  // ← 关键!
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

3AutoConfigurationImportSelector 5步流水线

// 源码:AutoConfigurationImportSelector.java
protected AutoConfigurationEntry getAutoConfigurationEntry(...) {
    // ① 获取注解属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // ② 【核心】从 .imports 文件加载所有候选配置类
    List<String> configurations = getCandidateConfigurations(...);
    // ③ 去重
    configurations = removeDuplicates(configurations);
    // ④ 排除 exclude 列表
    Set<String> exclusions = getExclusions(...);
    configurations.removeAll(exclusions);
    // ⑤ 【核心】条件过滤!@ConditionalOnClass / @ConditionalOnMissingBean
    configurations = getConfigurationClassFilter().filter(configurations);
    // ⑥ 发送事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

4ImportCandidates 读取 .imports 文件

// 源码:ImportCandidates.java
private static final String LOCATION = "META-INF/spring/%s.imports";

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    String location = String.format(LOCATION, annotation.getName());
    // 实际读取:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    List<String> importCandidates = new ArrayList<>();
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        importCandidates.addAll(readCandidateConfigurations(url));  // 逐行读取类名
    }
    return new ImportCandidates(importCandidates);
}
💡 版本差异:Spring Boot 2.7 之前用 spring.factories,2.7+ 迁移到 .imports,3.x 完全使用 .imports

5OnClassCondition 多线程高效过滤

// 源码:OnClassCondition.java
class OnClassCondition extends FilteringSpringBootCondition {
    protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, ...) {
        // 多核CPU时拆成两半并行处理,提高效率!
        if (autoConfigurationClasses.length > 1
                && Runtime.getRuntime().availableProcessors() > 1) {
            return resolveOutcomesThreaded(autoConfigurationClasses, ...);
        }
    }

    private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
        // 只检查 classpath 是否存在该类,不真正加载!
        if (ClassNameFilter.MISSING.matches(className, classLoader)) {
            return ConditionOutcome.noMatch(...);  // 类不存在 → 不生效
        }
        return null;  // 类存在 → 候选通过
    }
}

⚔️ 核心对比:DispatcherServletAutoConfiguration 源码全解析

// 源码:spring-boot-webmvc/DispatcherServletAutoConfiguration.java

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)  // ← 最高优先级
@AutoConfiguration                                // ← 标记为自动配置类
@ConditionalOnWebApplication(type = Type.SERVLET) // ← 条件1:必须是Servlet Web应用
@ConditionalOnClass(DispatcherServlet.class)       // ← 条件2:classpath必须有DispatcherServlet
public final class DispatcherServletAutoConfiguration {

    // ===== 内部配置类1:创建 DispatcherServlet =====
    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)  // ← 条件3:用户没自定义才生效!
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)  // ← 绑定 spring.mvc.* 配置
    protected static class DispatcherServletConfiguration {

        @Bean(name = "dispatcherServlet")
        DispatcherServlet dispatcherServlet(WebMvcProperties props) {
            DispatcherServlet ds = new DispatcherServlet();
            // 从 application.yml 的 spring.mvc.* 读取,替代XML中的 <property>
            ds.setDispatchOptionsRequest(props.isDispatchOptionsRequest());
            ds.setDispatchTraceRequest(props.isDispatchTraceRequest());
            ds.setPublishEvents(props.isPublishRequestHandledEvents());
            ds.setEnableLoggingRequestDetails(props.isLogRequestDetails());
            return ds;
        }

        // 兼容性Bean:用户可能命名错误的multipartResolver
        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = "multipartResolver")  // ← 容器没有才注册!
        MultipartResolver multipartResolver(MultipartResolver resolver) {
            return resolver;  // 自动重命名为标准名
        }
    }

    // ===== 内部配置类2:注册 DispatcherServlet 到Servlet容器 =====
    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)  // ← 依赖上面的配置类
    protected static class DispatcherServletRegistrationConfiguration {

        @Bean(name = "dispatcherServletRegistration")
        @ConditionalOnBean(value = DispatcherServlet.class, name = "dispatcherServlet")
        DispatcherServletRegistrationBean dispatcherServletRegistration(
                DispatcherServlet ds, WebMvcProperties props,
                ObjectProvider<MultipartConfigElement> multipartConfig) {
            // 自动注册Servlet映射路径(默认"/")—— 替代 web.xml 中的 <servlet-mapping>
            DispatcherServletRegistrationBean registration =
                new DispatcherServletRegistrationBean(ds, props.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(props.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }
    }

    // ===== 自定义条件判断:用户是否已定义DispatcherServlet =====
    private static final class DefaultDispatcherServletCondition extends SpringBootCondition {
        public ConditionOutcome getMatchOutcome(ConditionContext context, ...) {
            List<String> beans = Arrays.asList(
                beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
            // 用户已经定义了名为 "dispatcherServlet" 的Bean → 不匹配(用户优先!)
            if (beans.contains("dispatcherServlet")) {
                return ConditionOutcome.noMatch(...);
            }
            // 容器中没有任何DispatcherServlet → 匹配(自动配置补上)
            if (beans.isEmpty()) {
                return ConditionOutcome.match(...);
            }
            return ConditionOutcome.match(...);
        }
    }
}

🌲 自动配置决策树(以 DispatcherServlet 为例)

@AutoConfiguration 触发 │ ├── @ConditionalOnWebApplication(SERVLET) ──── 不是Web应用?❌ 整个类不生效 │ ├── @ConditionalOnClass(DispatcherServlet) ──── classpath没有?❌ 整个类不生效 │ ├── DispatcherServletConfiguration │ ├── DefaultDispatcherServletCondition ──── 用户已自定义?❌ 跳过 │ ├── ✅ 创建 dispatcherServlet Bean │ │ └── 属性从 WebMvcProperties(spring.mvc.*)读取 │ └── multipartResolver Bean │ ├── @ConditionalOnBean(MultipartResolver) ──── 没有?❌ 跳过 │ ├── @ConditionalOnMissingBean(name="multipartResolver") ──── 已有?❌ 跳过 │ └── ✅ 自动重命名为标准名(兼容用户命名错误) │ └── DispatcherServletRegistrationConfiguration ├── DispatcherServletRegistrationCondition ──── 用户已注册?❌ 跳过 ├── @ConditionalOnBean(dispatcherServlet) ──── Bean不存在?❌ 跳过 └── ✅ 注册 ServletRegistrationBean ├── 路径:spring.mvc.servlet.path(默认 "/") └── 启动顺序:spring.mvc.servlet.load-on-startup(默认 -1)

📊 逐行对比表

配置项 原生 Spring Spring Boot
Servlet 声明 web.xml 中 <servlet> + <servlet-mapping>(8行) 自动创建+注册,@ConditionalOnMissingBean
Servlet 路径 <url-pattern>/</url-pattern> spring.mvc.servlet.path(默认 /)
启动加载 <load-on-startup>1</load-on-startup> spring.mvc.servlet.load-on-startup
文件上传 手动配 MultipartResolver(6行) 自动配 + 兼容命名错误
视图解析器 手动配 InternalResourceViewResolver 自动配(默认适合REST)
静态资源 <mvc:resources> 自动映射 classpath:/static/
字符编码 手动配 CharacterEncodingFilter 自动配 UTF-8
Jackson 手动配 MappingJackson2HttpMessageConverter classpath有就自动配
数据源 手动8行+连接池参数 3行YAML,自动选HikariCP
事务管理器 手动配 + <tx:annotation-driven/> 有DataSource就自动配

🔧 核心条件注解一览

注解作用源码条件类
@ConditionalOnClassclasspath 有该类时生效OnClassCondition
@ConditionalOnMissingClassclasspath 没有该类时生效OnClassCondition
@ConditionalOnBean容器中有该 Bean 时生效OnBeanCondition
@ConditionalOnMissingBean容器中没有该 Bean 时生效OnBeanCondition
@ConditionalOnProperty配置属性满足条件时生效OnPropertyCondition
@ConditionalOnWebApplication是 Web 应用时生效OnWebApplicationCondition
@ConditionalOnExpressionSpEL 表达式为 true 时生效OnExpressionCondition

💬 面试金句

Q: Spring Boot 自动配置原理?

A: "自动配置 = SPI发现 + 条件过滤 + 用户优先。通过 @Import(AutoConfigurationImportSelector).imports 文件加载候选配置类,再由 @ConditionalOnClass@ConditionalOnMissingBean 过滤。以 DispatcherServlet 为例:@ConditionalOnWebApplication 保证非Web不加载,@ConditionalOnClass 保证classpath没有时不报错,DefaultDispatcherServletCondition 保证用户自定义了就退让。"
Q: 为什么不需要 web.xml?

A: "DispatcherServletAutoConfiguration 通过 ServletRegistrationBean 以编程方式注册 DispatcherServlet 并映射路径。原来 web.xml 的 <servlet>、<servlet-mapping> 等,现在由自动配置 + @EnableConfigurationProperties(WebMvcProperties.class) 绑定 YAML 完成。"
Q: 自动配置和手动配置冲突怎么办?

A: "用户 Bean 永远优先。每个自动配置方法都有 @ConditionalOnMissingBean,容器已有同类型/同名 Bean 就跳过。比如用户自定义了 DispatcherServlet,DefaultDispatcherServletCondition 会返回 noMatch,自动配置就不创建。"
Q: @ConditionalOnClass 是怎么判断 classpath 是否有某个类的?

A: "OnClassCondition 使用 ClassNameFilter.MISSING.matches(className, classLoader) 来检测。它只检查类是否存在,不真正加载类,避免触发类初始化。多核CPU时还会将候选配置类拆成两半并行过滤,提升启动速度。"