一篇讲清楚Spring Boot:自动装配、启动器、过滤器、拦截器、设计模式

从"怎么用"到"为什么这么设计",彻底搞懂Spring Boot

Spring Boot 已经成为 Java 开发的事实标准,但很多人对它的理解停留在"引入 starter 就能用"的层面。至于"为什么引入一个依赖就能自动生效",始终是个黑盒。

这篇文章不堆砌概念,用一条逻辑线把 Spring Boot 的核心机制串起来,争取一篇讲透。

一、先回答一个最根本的问题:Spring Boot 到底解决了什么?

在 Spring Boot 出现之前,用 Spring 开发一个 Web 项目,你需要做这些事:

1. 引入依赖:

xml 复制代码
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
    </dependency>
    <!-- 还要操心版本号对不对得上 -->

2. 写配置:

xml 复制代码
    <!-- web.xml -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
    </servlet>

    <!-- spring-mvc.xml -->
    <mvc:annotation-driven/>
    <context:component-scan base-package="com.demo"/>
    <bean class="InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

每一个项目都在重复这些几乎一模一样的配置。这就是 Spring Boot 要解决的问题------消除样板式的配置和依赖管理,让开发者专注于业务本身。

Spring Boot 的解决方案概括为三个核心:

核心机制 解决的问题
Starter(启动器) 依赖聚合,不再手动维护版本和依赖列表
自动装配 配置自动生成,不再手动写 XML/Java Config
内嵌容器 打包成 jar 直接运行,不再部署 war 到 Tomcat

下面我们逐个拆解。

二、Starter:一切从引入一个依赖开始

2.1 Starter 是什么?

Starter 是一个 Maven 依赖,但它特殊的点在于:它本身几乎不包含业务代码,只是一个"依赖清单" 。

xml 复制代码
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

引入这一个依赖,传递依赖会带来:

  • spring-webmvc(Spring MVC 框架)
  • jackson-databind(JSON 序列化)
  • tomcat-embed-core(内嵌 Tomcat)
  • spring-boot-autoconfigure(自动配置)
  • 以及它们的兼容版本

你什么都不用配,一个 Web 应用就 ready 了。

2.2 Starter 的命名规则

类型 命名格式 例子
官方 Starter spring-boot-starter-* spring-boot-starter-web、spring-boot-starter-data-jpa
第三方 Starter *-spring-boot-starter mybatis-spring-boot-starter、dubbo-spring-boot-starter

看到这个命名,你就知道它是个 Starter。

2.3 Starter 的核心价值

Starter = 依赖聚合 + 触发自动配置

引入一个 Starter,做了两件事:

  1. 帮你把该场景需要的所有 jar 包一次性引入
  2. 触发 Spring Boot 的自动装配机制,让这些 jar 包的配置自动生效

这就是 Starter 的设计逻辑------场景化封装。想用 Web 功能?引入 web-starter。想用数据库?引入 data-jpa-starter。按需取用,开箱即用。

三、自动装配:Spring Boot 最核心的机制

引入 starter 只是第一步,真正让配置自动生效的是自动装配(Auto-Configuration) 。

3.1 入口:@SpringBootApplication

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

@SpringBootApplication 是一个组合注解,它包含了三个核心注解:

java 复制代码
    @SpringBootConfiguration  // 等价于 @Configuration------这是个配置类
    @EnableAutoConfiguration   // 开启自动装配------关键!
    @ComponentScan            // 扫描当前包及子包的组件
    public @interface SpringBootApplication {
    }

真正起作用的是 @EnableAutoConfiguration------自动装配的总开关。

3.2 核心机制:扫描 spring.factories

当 @EnableAutoConfiguration 生效后,Spring Boot 会调用 SpringFactoriesLoader,去所有 jar 包的 META-INF/spring.factories 文件中读取配置。

java 复制代码
    // AutoConfigurationImportSelector 中的核心逻辑
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        EnableAutoConfiguration.class,  // 查找 key = EnableAutoConfiguration
        beanClassLoader
    );

打开 spring-boot-autoconfigure 包的 META-INF/spring.factories:

text 复制代码
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
    ...

这里列出了 120+ 个自动配置类。

注意 :读取到这些类名只是第一步,它们并不会全部生效。关键在下一步。

3.3 条件注解:决定谁真正生效

如果 120 多个配置类全部生效,必然会有 Bean 冲突、性能浪费。Spring Boot 用 @Conditional 系列注解来做按需加载。

java 复制代码
    @Configuration
    @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
    @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
    @ConditionalOnProperty(prefix = "spring.datasource", name = "url")
    public class DataSourceAutoConfiguration {
        // 数据源自动配置
    }

解读这三个条件:

注解 条件 含义
@ConditionalOnClass(DataSource.class) 类路径有 DataSource 没有 JDBC 驱动就不配置数据源
@ConditionalOnMissingBean(...) 容器没有 ConnectionFactory 用户自己配了就用用户的
@ConditionalOnProperty(...) 配置了 spring.datasource.url 没配 URL 就不配置

只有当所有条件都满足时,这个自动配置类才会生效。

常见条件注解速查表:

注解 生效条件
@ConditionalOnClass 类路径存在指定类
@ConditionalOnMissingClass 类路径不存在指定类
@ConditionalOnBean 容器中存在指定 Bean
@ConditionalOnMissingBean 容器中不存在指定 Bean
@ConditionalOnProperty 配置文件中指定属性满足条件
@ConditionalOnWebApplication 当前是 Web 环境
@ConditionalOnNotWebApplication 当前不是 Web 环境

3.4 自动装配全流程(总结)

一句话概括:扫描 spring.factories 找候选 → 条件注解过滤 → 满足条件的才生效。

四、过滤器(Filter)和拦截器(Interceptor)

4.1 先看它们在请求链路中的位置

注意两个关键点:

  • Filter 在 DispatcherServlet 之前执行,比拦截器更早接触到请求
  • Interceptor 在 Controller 前后执行,更靠近业务逻辑

4.2 核心区别对比

对比项 Filter Interceptor
所属规范 Servlet 规范(Java EE) Spring MVC 框架
是否依赖 Spring 否 是
执行时机 DispatcherServlet 之前 Controller 前后
拦截范围 所有请求(含静态资源如 .css、.js) 只拦截进入 Controller 的请求
能否注入 Spring Bean 不能直接注入 可以(@Autowired)
典型用途 字符编码过滤、跨域处理(CORS)、XSS 防护 登录校验、权限控制、操作日志

4.3 代码怎么写

过滤器:

java 复制代码
@Component
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        System.out.println("请求到达:" + req.getRequestURI());
        chain.doFilter(request, response);  // 放行,继续往下走
        System.out.println("响应返回");
    }
}

拦截器:

java 复制代码
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                             Object handler) throws Exception {
        // 返回 true 放行,false 拦截
        Object user = request.getSession().getAttribute("user");
        if (user == null) {
            response.sendRedirect("/login");
            return false;
        }
        return true;
    }
}

注册拦截器(Filter 不需要注册,@Component 即可生效):

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/admin/**")      // 拦截 /admin/ 下的所有请求
                .excludePathPatterns("/login");     // 放行登录接口
    }
}

4.4 为什么 Filter 不能直接注入 @Autowired?

因为 Filter 是 Servlet 容器(如 Tomcat)管理的,在 Spring 容器初始化之前就已经创建完成了。而 Interceptor 是 Spring 管理的,和 Spring Bean 生命周期同步。

如果你想在 Filter 里用 Spring Bean,可以通过 DelegatingFilterProxy 包装,或者用 FilterRegistrationBean 注册时把 Filter 声明为 Spring Bean(Spring Boot 的 @Component 方式本质上就是这个)。

4.5 选型建议

场景 选什么
登录状态校验 Interceptor(需要注入 UserService)
权限控制 Interceptor
字符编码设置 Filter(Servlet 层级,处理原始请求)
跨域处理 Filter(CORS 是 Servlet 层面的)
请求日志 都可以,Filter 更全面(含静态资源)

五、设计模式:看看 Spring Boot 的"骨架"

5.1 工厂模式------SpringFactoriesLoader

SpringFactoriesLoader 根据 key(如 EnableAutoConfiguration.class)从多个 spring.factories 文件中读取配置,批量创建对象。这是典型的工厂模式。

java 复制代码
// 工厂根据 key 生产对象
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(key, classLoader);
for (String name : classNames) {
    Class<?> clazz = Class.forName(name);
    Object instance = clazz.getDeclaredConstructor().newInstance();
    // ...
}

5.2 策略模式------@Conditional 条件注解

每个 @Conditional 注解对应一种判断策略,Spring 根据当前环境(类路径、容器 Bean、配置属性等)决定走哪条策略。

java 复制代码
@ConditionalOnClass(DataSource.class)    // 策略1:检查类路径
@ConditionalOnMissingBean                 // 策略2:检查容器
@ConditionalOnProperty                    // 策略3:检查配置文件

不同策略组合,决定最终配置是否生效。

5.3 模板方法模式------AbstractApplicationContext.refresh()

Spring 容器启动的核心方法 refresh() 定义了整个流程的算法骨架,某些步骤交给子类去具体实现。

java 复制代码
public void refresh() throws BeansException {
    // 这些步骤的顺序是固定的
    prepareRefresh();                     // 准备刷新(子类可扩展)
    obtainFreshBeanFactory();             // 获取 BeanFactory
    prepareBeanFactory();                 // 准备 BeanFactory(子类可扩展)
    postProcessBeanFactory(beanFactory);  // ★ 钩子方法,子类可重写
    invokeBeanFactoryPostProcessors();    // 调用 BeanFactory 后处理器
    registerBeanPostProcessors();         // 注册 Bean 后处理器
    initMessageSource();                  // 初始化消息源
    // ...
}

子类(如 AnnotationConfigApplicationContext)通过重写 postProcessBeanFactory 等钩子方法,注入自定义逻辑,而整体流程骨架保持不变。

5.4 责任链模式------FilterChain

多个 Filter 组成一个链条,请求沿着链条依次经过每个 Filter,每个 Filter 可以选择处理请求后放行(chain.doFilter()),也可以直接拦截返回。

java 复制代码
public void doFilter(ServletRequest request, ServletResponse response, 
                     FilterChain chain) {
    // 前置处理
    log.info("Filter 前置");
    // 放行到下一个 Filter 或最终资源
    chain.doFilter(request, response);
    // 后置处理
    log.info("Filter 后置");
}

这是责任链模式最经典的实现。

5.5 设计模式之间的关系

六、一张图收尾

写在最后

这篇文章没有追求大而全,而是尽量把逻辑链串清楚:

  1. Starter 帮你聚合依赖、触发自动配置
  2. 自动装配 通过 spring.factories 发现候选,通过 @Conditional 按需生效
  3. Filter 和 Interceptor 是 Web 层两种不同粒度的拦截机制,搞清楚它们的执行顺序和适用场景
  4. 设计模式 是 Spring Boot 的架构骨架,理解它们才能理解"为什么这么设计"

把这四条线串起来,Spring Boot 在你面前就不再是黑盒了。

如果你觉得这篇文章讲清楚了,点个赞支持下。如果有疑问,欢迎评论区交流~

相关推荐
AIGC小尼17 小时前
本地 AI 漫剧流水线模型缓存与特征向量持久化:IPAdapter 预编码、特征池复用与内存池管理实战
人工智能·spring boot·缓存·预编码算法·comfyui·ai漫剧
tachibana217 小时前
Go 语言 Map 的底层实现原理
开发语言·后端·golang·make
九零HTTP17 小时前
从 Cookie 到 Token,登录态到底是怎么维持的
后端
Wang's Blog17 小时前
Java 项目部署之 Docker工具快速入门: Docker 镜像仓库
java·docker
程序边界17 小时前
用了三年数据同步软件,我想好好聊聊KFS这套东西到底强在哪(上)
后端
sp4217 小时前
简单的 Spring 文件上传与下载组件
后端
企智汇-项目管理软件17 小时前
离散制造管理系统怎么选?一套系统管住从研发到交付的全周期
java·人工智能·制造·项目经理·项目管理软件·项目管理系统·项目管理软件工程
量化分析码农17 小时前
【Python量化实盘工程 #04】回测收益虚高30%?滑点与冲击成本建模让模拟更真实
后端
QuantiCore_IO17 小时前
量化回测复权数据的工程实践:从 API 获取到数据质量验证
后端·github·api
程序边界17 小时前
数据同步软件下半场:断了丢不丢、错了能不能修(下)
后端