尧图精选

Spring与Spring Boot核心区别:从IoC到自动配置原理与选型指南

🕒 发布时间:2026/9/10 10:46:13 📁 来源:尧图网络
先说个自己踩过的坑。早年做项目用纯Spring搭了一套基础框架光XML配置文件就写了一千多行Bean之间的依赖关系全靠脑补改一个Bean的scope要全局搜索所有引用点。后来Spring Boot出来一开始我是拒绝的觉得“这玩意儿把配置都藏起来了出了问题怎么查”。直到在一个紧急交付的项目里用Spring Boot把原来两周的环境搭建压缩到半天我才真正意识到——这两者根本不是替代关系而是Spring对“怎么用”这件事做出的两次回答。这篇文章就把Spring和Spring Boot的区别、底层原理、选型思路一次性讲透。不管是准备面试的应届生还是被老项目折磨的维护者或者准备从零搭建新服务的团队都能找到自己需要的点。1. 一个容器两种姿态Spring与SpringBoot的本质定位1.1 先搞清楚Spring是什么——一个控制反转容器很多人上来就对比Spring和Spring Boot但核心前提是Spring是一个完整的编程模型和运行时环境Spring Boot是Spring生态里用来“降低使用门槛”的一套自动化方案。说句不好听的你要是没搞懂Spring的IoC控制反转和AOP面向切面编程直接上Spring Boot遇到问题会完全不知道从哪里下手。Spring最核心的价值是IoC容器。传统开发里一个Service要依赖另一个Component你得自己new一个实例出来这个实例的生命周期、依赖关系全靠手写管理项目一庞大便全是“蜘蛛网”。IoC的思想是所有对象的创建和装配交给容器统一管理你的代码只需要声明“我需要什么”容器在合适的时机把合适的实例注入进来。打个比方传统方式是去餐厅你想吃鱼香肉丝得自己买菜、洗菜、切菜、炒菜最后还得刷锅IoC是直接告诉服务员“我要鱼香肉丝”后厨容器自己完成备料、烹饪、上菜你只负责吃。至于后厨是川菜师傅还是鲁菜师傅具体实现类服务员会按你的口味偏好配置来安排。Spring容器管理Bean的方式早期主要靠XML配置后来演进到注解Java Config。比如Service public class OrderService { private final PaymentClient paymentClient; public OrderService(PaymentClient paymentClient) { this.paymentClient paymentClient; } }这段代码在Spring看来就是“帮我管理OrderService这个Bean它需要PaymentClient的实例请在创建时自动注入”。你不需要在任何地方new OrderService所有生命周期由容器接管。这就是为什么Spring被誉为Java后端开发的基石——它解决了“对象怎么管理”这个最基础也最头疼的问题。1.2 Spring Boot不是什么框架而是Spring的“开箱即用”方案Spring Boot最容易被误解的一点就是大家把它当作一个新框架。实际上Spring Boot没有发明新的Bean管理机制没有推翻Spring MVC的请求处理模型也没有另起炉灶搞一套新的AOP。它做的事情可以概括为三个词自动配置、起步依赖、生产就绪。自动配置Auto Configuration解决的是“Spring项目要跑起来需要写大量样板配置”的痛点。纯Spring项目里你要配置数据源、事务管理器、视图解析器、JSON序列化器每个都要手动声明一份Bean。而Spring Boot通过条件化配置根据你引入的依赖和classpath上的类自动判断该装配什么Bean。比如你引入了spring-boot-starter-data-jpa它检测到classpath里有Hibernate和DataSource相关类就自动帮你配置好EntityManagerFactory、TransactionManager你只需要在application.yml里写几行数据源连接信息。起步依赖Starter解决的是依赖版本兼容问题。在没有Spring Boot之前引入第三方库得自己对着版本矩阵查兼容性Spring 4.3配Hibernate 5.2还是5.3Jackson用2.8还是2.9错了就抛ClassNotFoundException排查一下午。Spring Boot把所有常用库的组织版本管理集中到一个BOMBill of Materials里你只需要引入spring-boot-starter-web它自动拉入Spring MVC、Jackson、Tomcat等一整套依赖并且版本之间是经过测试验证的。生产就绪Production Ready则是指Spring Boot自带健康检查、指标收集、外部化配置等能力。也就是说“把应用部署到生产环境”这件事Spring Boot帮你想到了前面。一句话总结定位Spring是你造房子用的钢筋混凝土框架Spring Boot是“开发商精装修交付”的样板间——骨架还是那套骨架但你不用自己拉水泥、找工人了。2. 自动装配Spring Boot最核心的魔法同时也是最容易翻车的地方2.1 自动装配的原理拆解说到Spring Boot避不开自动装配Auto Configuration。很多人面试被问“Spring Boot自动装配原理是什么”背答案能背出来“spring.factories、EnableAutoConfiguration、Conditional注解”但真要排查一个“为什么我自定义的Bean没生效”的问题就抓瞎了。自动装配的核心链路分四步第一步启动类上的SpringBootApplication是一个组合注解其中关键一环是EnableAutoConfiguration。这个注解通过Import引入了AutoConfigurationImportSelector类。第二步AutoConfigurationImportSelector在Spring启动时会扫描classpath下所有META-INF/spring.factories文件Spring Boot 2.7及以后版本是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports读取里面声明的自动配置类列表。第三步拿到这一大堆自动配置类后Spring并不会全量加载而是逐一检查每个配置类上的条件注解。常见的条件注解有这些条件注解作用使用频率ConditionalOnClassclasspath中存在指定类才生效极高ConditionalOnMissingBean容器中不存在指定Bean才生效极高ConditionalOnProperty配置项满足指定值才生效高ConditionalOnWebApplication当前应用是Web应用才生效中ConditionalOnExpressionSpEL表达式为真才生效低第四步符合条件的自动配置类被解析注册对应的BeanDefinition最后实例化成真正的Bean放进容器。我举个例子你引入spring-boot-starter-data-redis后RedisAutoConfiguration这个自动配置类会检查classpath里有没有RedisConnectionFactory相关的类有的话继续检查容器里Spring容器中没有RedisTemplate这个Bean没有就自动帮你构建一个默认的RedisTemplate。这就是为什么你什么都不写Spring Boot就能帮你用redisTemplate操作Redis。“为什么不写配置也能跑”答案就藏在“条件注解”这个设计里。Spring Boot把大量可能的场景都预先定义好了按条件触发条件不满足就跳过这也是它能兼容各种项目的原因。2.2 从源码层面理解SpringBootApplicationSpringBootApplication这个注解很多人一眼扫过没细看。它的定义如下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 { }这里面有三个核心注解SpringBootConfiguration本质上是Configuration表示这个类是一个配置类。EnableAutoConfiguration上面讲过了负责触发自动装配机制。ComponentScan决定了Spring扫描哪些包路径下的Component、Service、Repository、Controller等注解类。这里有一个新手极容易踩的陷阱ComponentScan默认扫描的是“启动类所在包及其子包”。如果你的启动类放在com.example.demo下而你的业务类放在com.example.business下那Spring根本扫描不到你会在启动后调接口时遇到“依赖注入失败”或者“404 No mapping”。我处理过很多类似的工单十个里有八个是包路径问题。手段上可以在ComponentScan里显式指定basePackages但更推荐的方式是把启动类放在包的顶层位置保证所有业务代码都在它的子包下。这样最符合Spring Boot约定的开发习惯也避免扫描范围混乱引起的性能问题。2.3 常见的自动装配失效问题自动装配“太方便”的另一面是出了问题排查起来比较绕。我总结了几类高频故障供参考第一类自定义配置类不生效。你在某个包里写了一个Configuration类定义了自己封装的数据源或RestTemplate但启动后发现没生效。排查方向第一眼看包路径是否被扫描到第二步看配置类上的注解是否写完整第三步看是不是被同一个类型的自动配置类抢先装配了。第二类工程中同时有两份同类型Bean。比如你自己定义了一个RestTemplate但Spring Boot的RestTemplateAutoConfiguration在ConditionalOnMissingBean条件下发现你已经有这个Bean它就会跳过默认配置这其实是符合预期的。但如果两个都是自动配置加载的比如引入了多个Starter导致同类型Bean冲突启动时就会报BeanDefinitionStoreException或NoUniqueBeanDefinitionException。这个时候优先检查是不是引入了重复职责的Starter比如同时引入spring-boot-starter-data-redis和spring-boot-starter-data-redis-reactive。第三类“我改了配置但没生效”。很多配置项有对应的属性类比如ServerProperties、DataSourceProperties它们通过ConfigurationProperties绑定到application.yml里的前缀。如果你改的配置键拼写错误或者缩进层级不对Spring Boot会静默忽略不报错但行为不符合预期。排查时可以开启配置元数据校验或者直接在启动日志里看自动配置报告。排查自动装配问题的通用方法是用debug模式启动在application.yml里加debug: true启动后控制台会打印一个“CONDITIONS EVALUATION REPORT”按Positive matches和Negative matches分类清清楚楚告诉你哪些自动配置生效了、哪些没生效、因为什么条件没生效。这个报告是排查自动装配问题的最强武器没有之一。3. 逐个维度对比依赖、配置、部署、监控到底差在哪3.1 依赖管理Spring Boot如何终结版本地狱纯Spring项目的依赖管理最痛的点在版本兼容。你引入一个Spring核心模块要手动挑选spring-core、spring-context、spring-beans、spring-aop等模块的版本它们必须保持一致再引入第三方库又要考虑和Spring版本是否兼容。Spring Framework 5.3对应Spring Boot 2.xSpring Framework 6.x对应Spring Boot 3.x这个对应关系一旦搞错运行时会出现各种奇怪的NoSuchMethodError或ClassNotFoundException。Spring Boot的解决方式是提供一个父POMparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent这个父POM里用dependencyManagement锁定了所有经过Spring官方测试的依赖版本。你在子模块引入依赖时只需要写groupId和artifactId不用写version版本由父POM统一管控。这极大降低了依赖维护成本。不过这里有个误区Spring Boot管理的是“官方支持的通用依赖”的版本如果你引入的业务第三方库不在管理列表里还是得自己指定版本。另外如果你不想继承spring-boot-starter-parent也可以用spring-boot-dependencies BOM来统一管理版本效果类似。实际项目中我是强烈建议用Spring Boot管理依赖的踩过的版本坑能让人少长几根白头发。3.2 配置方式从千行XML到几十行YAML纯Spring框架里配置主要有三种方式XML配置适合老项目所有Bean定义都放在applicationContext.xml里。Java Config用Configuration Bean注解代码化的配置方式。注解驱动直接用Component、Autowired等注解声明Bean。这三种方式可以共存但共存的结果就是“配置分散、心智负担高”。比如我早年维护过一个系统数据源在XML里配事务管理器在Java Config里配Service层用注解注入找一份配置要三处翻。Spring Boot把这一锅粥收敛成了kebab-case或驼峰风格的配置项写在application.yml里加上配置属性的强绑定做到“一处修改、全局生效”spring: datasource: url: jdbc:mysql://localhost:3306/app_db username: root password: secret hikari: maximum-pool-size: 20对应地你不需要再手动创建DataSource这个Bean——Spring Boot的DataSourceAutoConfiguration会读取这些属性自动构建HikariDataSource实例。这就是“约定优于配置”的核心体现大部分场景下你只需要告诉它“连接串、账号、密码”剩下的连接池大小、超时时间等都有合理的默认值。Spring Boot还支持Profile区分环境。dev、test、prod各写一份配置文件通过spring.profiles.active指定当前环境部署到不同环境就不用改代码、改配置了。这在纯Spring项目里要自己写PropertyPlaceholderConfigurer麻烦得多。3.3 内置容器与独立部署这是最直观的差异纯Spring MVC项目要部署到Tomcat传统流程是这样的写代码 - 打成WAR包 - 丢进Tomcat的webapps目录 - 启动Tomcat - 项目才运行。这个模式有几个痛点你得安装并维护一个独立的Servlet容器容器和项目的版本要匹配团队每个人的本地环境都有差异。Spring Boot最直观的体验差异在“java -jar应用”的部署方式。Spring Boot应用是内嵌Tomcat也可以是Jetty、Undertow的启动时自动在应用进程内拉起一个Web容器打成可执行的fat jar后一个命令就能跑起来java -jar app.jar --spring.profiles.activeprod这种方式不仅适合传统物理机部署也非常适合容器化场景。比如把Spring Boot应用打成Docker镜像镜像里只需要装一个JRE不需要再装Tomcat因为Tomcat已经生在内嵌环境里了。当然如果出于运维合规要求必须用外部Tomcat管理多个应用也可以把Spring Boot应用打成传统WAR包部署到独立Tomcat需要继承SpringBootServletInitializer并重写configure方法。但我的建议是能用内嵌容器就别用外部容器不然就失去了Spring Boot最大的自动化红利。3.4 生产级能力监控、健康检查、指标Spring Boot帮你省了大钱“能跑起来”和“能稳着跑”是两码事。纯Spring项目上线后健康检查、存活探针、日志指标这些都要额外去接。Spring Boot内置了Spring Boot Actuator模块健康检查发布一个/actuator/health端点返回UP/DOWN状态Kubernetes的liveness/readiness探针可以直接调它。指标收集通过Micrometer暴露JVM指标、HTTP请求指标、数据源连接池指标可以对接Prometheus等监控体系。审计日志和条件报告启动阶段自动记录各种信息。我举一个实际例子在Kubernetes里部署Spring Boot应用配置一个存活探针livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 periodSeconds: 10这样如果应用卡死或线程池打满K8s会根据探针失败情况自动重启Pod。这种能力在纯Spring项目里你得自己写一个Servlet或Filter来做健康检查接口还容易做成“只检查Tomcat活着业务线程池挂了也不知道”——Actuator的健康检查会聚合数据源、Redis、消息队列等多个组件的状态可用性判断比自写探针准确得多。3.5 生态整合Spring Cloud、Spring AI与微服务最佳拍档Spring Boot能成为微服务事实标准和它在生态里的“底座”地位分不开。Spring Cloud是基于Spring Boot构建的分布式系统开发工具集几乎所有微服务组件都依赖Spring Boot的自动配置机制来简化接入。比如Spring Cloud Alibaba的Nacos配置中心你引入对应Starter后只要在bootstrap.yml里配置好Nacos地址和相关命名空间应用的配置就能从远程配置中心读取支持动态刷新。搭建一个微服务骨架从零到能跑通服务注册、配置管理、负载均衡半天时间就够了这在纯Spring时代难以想象。再比如目前很火的Spring AI是基于Spring Boot的自动配置来简化AI大模型接入的。你引入spring-ai-openai-spring-boot-starter配好API Key容器里就会自动注入ChatClient、EmbeddingModel等Bean你在业务代码里直接注入就能调用大模型接口Service public class AIService { private final ChatClient chatClient; public AIService(ChatClient chatClient) { this.chatClient chatClient; } public String ask(String question) { return chatClient.call(question); } }这些组件能如此“顺滑”地集成根源都在Spring Boot自动配置上。如果你对自动配置原理理解得不够看这些新生态的文档就会陷入“为什么我引入依赖后没有Bean可用”的困惑。4. 选型指南什么场景用Spring什么场景用Spring Boot4.1 别急着无脑上Spring Boot这些场景纯Spring反而合适虽然Spring Boot是主流但“无脑Spring Boot”也不对。你先想清楚自己的项目形态第一个例外你是给某个老系统做一个小补丁模块老系统基于纯Spring XML配置搭建处于“能跑但没人敢碰”的状态。这时候硬上Spring Boot会造成配置体系冲突——两边各自管理不同的Bean行为还可能互相覆盖维护成本会更高。第二个例外你对Spring内核原理的学习阶段。Spring Boot屏蔽了太多细节如果你用Spring Boot入门就很难理解BeanFactory和ApplicationContext的区别、Bean的生命周期钩子BeanPostProcessor、InitializingBean、DisposableBean是怎么被容器触发的。我见过一些用Spring Boot参加工作、工作了几年还不理解循环依赖原理的同事不是他们不努力是框架把过程都藏起来了。学习基本功纯Spring 手写配置是最好的路径。第三个例外高度定制化的容器环境。有的金融行业客户要求应用必须部署在指定的Web容器里由运维统一管理开发团队不能随意携带内嵌容器。这种情况下Spring Boot的默认姿势打fat jar反而不达标用传统WAR 外部Tomcat更符合环境约束。4.2 新项目选型优先按“团队经验 交付周期”来决定新项目选型我给的顺序是这样的90%的情况下选择Spring Boot。不管是单体应用、微服务、定时任务、批处理、消息消费者Spring Boot都能以最小的成本让你跑起来。团队里哪怕只有一个人熟悉Spring Boot也能快速带起一个项目骨架。如果你的项目是多个服务拆分需要服务发现、配置中心、网关等微服务组件Spring Boot Spring Cloud或Spring Cloud Alibaba基本是默认组合。如果你需要AI能力接入可以用Spring AI它是目前Java生态里和Spring Boot融合度最高的AI框架比自己在代码里封装HTTP调用大模型接口要省事得多。如果你要开发的是非常轻量的工具型Web服务其实也可以考虑直接用嵌入式Jetty或纯Servlet容器连Spring Boot都不上但这种情况极少——因为后续加功能多半还是要加Spring生态的。还有一点Spring Boot 3.x要求JDK 17以上。如果你的公司基础设施还在JDK 8那Spring Boot只能选2.7.x这个最后的JDK 8兼容版本。这个约束一定在项目启动前确认清楚不然技术选型做完了才知道环境不支持就尴尬了。4.3 老项目向Spring Boot迁移的路径老项目迁移不是“重写”而是渐进式改造。我自己做过几次总结了一套相对稳妥的路径第一步引入Spring Boot依赖并搭建启动类项目仍然以WAR形式部署到外部Tomcat保证现有功能不拆、不修。这时候Spring Boot和原有Spring配置可以共存因为SpringBootApplication的启动类只会扫描自己所在包和子包不影响原XML加载的Bean。第二步逐步把新写的代码用Spring Boot的方式组织新功能优先使用自动配置和注解开发。第三步当新代码占比足够大后把原有XML里的配置迁移到Java Config或application.yml里。注意数据源、事务管理器这类基础设施配置要最先迁移因为它们是其他Bean的依赖基础。第四步切换为内嵌容器部署打成fat jar验证功能后替换旧部署方式。整个过程中最容易出问题的是“双容器”阶段的Bean冲突和事务管理器重复注入。我的建议是迁移期间尽量不要同时改配置和业务逻辑一次只改一处部署验证通过再继续改下一处。别贪多项目不炸才是第一优先级。5. 常见问题与面试高频点从三级缓存到循环依赖5.1 Spring三级缓存原理面试最爱问的“烧脑题”Spring的高频面试题里三级缓存和循环依赖是必考点。这里我把原理讲清楚。先说什么是三级缓存。Spring容器在创建Bean时为了支持循环依赖两个Bean互相引用内部维护了三个Map// 一级缓存已经完成初始化的单例Bean MapString, Object singletonObjects; // 二级缓存已经实例化但还没完成属性填充的早期Bean MapString, Object earlySingletonObjects; // 三级缓存存放ObjectFactory用于生成早期Bean的代理对象 MapString, ObjectFactory? singletonFactories;循环依赖的解决流程大致是A依赖BB依赖A。创建顺序是先创建AA实例化后把A的ObjectFactory放入三级缓存A要填充属性发现需要B就转而创建BB实例化后B需要填充属性发现需要A此时从三级缓存里拿到A的ObjectFactory调用getObject()得到A的早期引用放入二级缓存并注入给BB完成初始化后回到AA从二级缓存拿到自己的早期引用填进去完成初始化。最终A和B都放入一级缓存。这个机制能工作的前提是循环依赖的Bean必须是单例的且不涉及构造器注入构造器注入没法提前暴露未完成实例化的Bean。5.2 循环依赖在Spring Boot里可能“失效”基础知识点背完之后面试官喜欢增设一个场景Spring Boot默认开启循环依赖吗Spring Boot 2.6版本起默认禁用了循环依赖启动时如果检测到循环引用会直接报错The dependencies of some of the beans in the application context form a cycle原因是Spring官方认为循环依赖是设计上的坏味道与其让容器悄悄容忍不如在启动阶段报错强制你重构。这种“宁愿报错也不继续运行”的思想和传统Spring“能跑就让它跑”的风格截然不同。解决办法有三个方向重构代码拆掉循环依赖。最推荐。用Lazy注解延迟注入其中一个依赖打破循环。在application.yml里设置spring.main.allow-circular-referencestrue重新开启。我在项目里见过好几次“生产环境突然启动失败”就是因为升级Spring Boot版本后循环依赖被拦住了。结论代码里别写循环依赖它永远是技术债早拆早安心。5.3 Spring Boot版本太高带来的排查难题“Spring Boot版本太高”是热搜词里的一个典型痛感场景。新项目直接上Spring Boot 3.5然后发现某些第三方库还没适配Jakarta EE 9的命名空间原来的javax.servlet变成jakarta.servlet老代码的import全报错。Spring Cloud版本和Spring Boot版本要严格匹配配错了就各种组件初始化失败。某些自定义配置的元数据逻辑在新版本里失效比如配置属性名称被重新整理。我的建议是企业项目不要盲目追求最新版本选择“当下生态最成熟、第三方库兼容性最广”的版本。如果你想用Spring Boot 3.x需要确认你依赖的ORM、消息队列、缓存等客户端都支持Jakarta命名空间。升级之前先把依赖树打出来mvn dependency:tree看看有没有老版本的javax依赖混在classpath里不然很容易出现“编译通过、启动就挂”的玄学问题。6. 最后分享几个实操中沉淀下来的使用技巧说了这么多最后聊几个我实际用下来比较有价值的细节。第一用spring-boot-maven-plugin打包时如果遇到“jar中没有主清单属性”的问题检查一下是不是没引入这个插件或者打包方式被覆盖了。加上配置build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build第二配置里用随机端口做多实例本地调试也是个好技巧。比如本地起多个服务端口冲突是家常便饭。配置server.port0时Spring Boot会自动找空闲端口并结合日志输出“Tomcat started on port(s): 53721 (http)”这种本地调试体验比死写死端口舒服得多。第三Spring Boot单元测试的标配是SpringBootTest。但要注意把整个上下文拉起来跑测试会非常慢动辄几十秒。更高效的做法是按需使用WebMvcTest或MybatisTest这类切片测试只加载需要的层速度和准确性都能兼顾。实操中多看自动配置源码比看任何二手博客都管用。Spring Boot的代码质量很高把RedisAutoConfiguration、DataSourceAutoConfiguration这些类读一遍你对“条件装配”的理解就会上一个台阶排查问题也能更快定位到根因。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →