韩国是“Spring共和国”吗?
如果你想在韩国以后端开发者的身份找到工作,有一个问题几乎无法回避:“你会用 Spring 吗?”
打开招聘网站,十家公司里有九家都会在要求里明确写上“有 Spring Boot 经验”。夸张到甚至有了“Spring共和国”这种说法。大学时期的我,一直以为这只是潮流而已。
“直接用 Java 写不就行了吗,为什么非要用这么重的 Spring?”“Node.js 或 Python(Django)不是更简单吗?”
但真正进入实务、经历过大型项目之后,我才明白:Spring 不是单纯的流行,而是一套为了让庞大而复杂的企业系统能够“安全而快速”地构建起来,被无数前辈开发者不断打磨出的标准工作台。
这一系列,讲的就是如何在这张标准工作台上,搭建一座不会轻易倒塌的城堡,也就是 Monolith。
框架:为什么不能完全按自己的意思来
在正式进入技术细节之前,有一件事必须先说清楚。到底什么是框架(Framework)?它和库(Library)又有什么区别?
我在前面的 Re: Booting 系列里提到过一点,但这两者最关键的区别,其实在于“到底是谁掌握主导权”。
- 库(Library):是“工具”。需要的时候,我自己拿出锤子来用,再把锯子拿过来用。盖房子时,完全可以按自己的意思来盖。(例如:React、jQuery)
- 框架(Framework):是“骨架(Frame)”。就像一座柱子和墙壁都已经立好的样板房。我只能按既定规则挑选壁纸颜色、摆放家具,却不能随意拆掉柱子。
Spring 是框架。所以说“使用 Spring”,几乎就等于在发誓:“我愿意先放下自己的编码习惯,按照 Spring 的方式来。”而这种“按它说的做”的核心,正是今天要讲的控制反转(IoC)。
“拜托,别再老是写 new 了”
这份誓约的第一条规则就很震撼:不要使用 Java 最基础不过的 new 运算符。
大学时,如果我需要一个对象,理所当然就会写 MemberService service = new MemberService();。我需要什么,就自己创建出来再用。这不是再正常不过了吗?
可入职后我打开 Spring Boot 项目时,代码里怎么找都看不到 new。取而代之的是 @Autowired、@RequiredArgsConstructor 这类陌生的注解。
“不是,对象都没创建,方法到底怎么调用?这玩意儿真的能跑?”
我问前辈开发者时,得到的回答听起来有点云里雾里:“Spring Container 会自己给你注入(Inject),所以不用自己创建。反而如果你老是直接写 new,以后维护起来会很痛苦。”
我当时完全不能理解。代码是我写的,为什么连创建一个对象都不能自己说了算?那种感觉,简直像是Spring 的独裁。

厨师与食材经理(IoC & DI)
为了理解这种看似“独裁”的规则为什么必要,我们把世界观切换成“餐厅厨房”。
1. 传统方式(我说了算)
我是厨师,也就是一个 Class。要做菜,我需要一把刀,也就是 Dependency。
- 行动:我亲自去超市买回一把刀。
Knife knife = new Knife(); - 问题:如果我买的是“日式厨刀”,结果菜单突然换成了中餐,需要的变成了“中式菜刀”呢?那我就得把握着刀的这只手(代码)拆开重改。每换一次工具,厨师也会跟着受影响。(强耦合)
2. Spring 的方式(Spring 说了算)
我还是那个厨师。但这一次,厨房里多了一个“食材经理(Spring Container)”。
- 行动:我只需要喊一声“给我一把刀”。至于是哪个牌子的刀,我根本不关心。
- DI (Dependency Injection):经理把刀塞到我手里(Inject)。今天可能给我日式厨刀,明天也可能给我中式菜刀。
- 优点:就算刀换了,我也不需要改代码。我只要用递到手里的刀做菜就行。(松耦合)
也就是说,我不再自己管理我所需要的资源,而是交给外部的管理者来统一管理,再由他交到我手上。这就是控制反转(IoC: Inversion of Control),而它的具体实现方式,就是依赖注入(DI)。

[Code Verification] 为什么 new 很危险
光靠嘴说很难真正有感觉。我们直接用代码来对比。假设我们要做一个“信用卡支付系统”。
1. 坏例子:直接创建(强耦合)
public class PaymentService {
// 我直接選擇並建立了「三星卡」(使用 new)
private final SamsungCard card = new SamsungCard();
public void pay() {
card.payment(); // 只能用三星卡付款
}
}
问题点:某天老板突然说:“我们换成手续费更低的 现代卡 吧!”那我就得打开 PaymentService 的代码,把所有 SamsungCard 全部改成 HyundaiCard。如果这样的代码散落在 100 个地方呢?那就准备加班吧。
2. 好例子:依赖注入(松耦合)
public class PaymentService {
private final Card card; // 不指定具體發卡公司(使用介面)
// 建構子注入:相信「外面會有人把卡交給我」
public PaymentService(Card card) {
this.card = card;
}
public void pay() {
card.payment();
}
}
这样一来,PaymentService 就不再关心传进来的到底是哪一种卡实现了。
- 只要在 Spring 配置(Configuration)里改一行,写上“给我换成现代卡”,就能让
PaymentService的代码一行都不用动,直接更换支付方式。
这就是实务里常说的“更容易维护的代码(OCP 原则)”的核心。之所以使用 Spring Boot,正是因为这种灵活性。
Spring Container:对象们的酒店
那么,这个负责创建对象并把它们交出来的“经理”,到底是谁?答案就是 Spring Container。
当我们启动 Spring Boot 服务器、执行 run 的那一刻,内部其实会发生很多事情。
- 扫描(Component Scan):Spring 会把贴有
@Component、@Service、@Repository的类一个不落地找出来。 - 注册(Bean Registration):为找到的每个类只创建一个对象(Singleton),存进自己专属的仓库(Container)里。这些对象就叫作“Bean”。
- 注入(Auto Wiring):弄清对象之间需要的依赖关系,再把它们一一配对连接起来。
我们真正要做的,只不过是在类上贴一张“请帮我管理这个”的标签,比如 @Service。剩下的事,就交给 Spring 这个巨大的酒店经理去处理。
实务建议:使用构造器注入
在 Spring 中,依赖注入有很多方式:@Autowired 字段注入、Setter 注入、构造器注入。但在实务里,几乎都会推荐“构造器注入”。(也就是常和 Lombok 的 @RequiredArgsConstructor 一起使用的那种方式。)
@Service
@RequiredArgsConstructor // 這就是自動產生建構子注入的關鍵
public class OrderService {
// 可以加 final,所以很安全
private final PaymentService paymentService;
}
原因:
- 不可变性(Final):一旦注入就无法再更改。应用运行到一半时,零件不应该被换掉。
- 防止循环引用:A 需要 B、B 又需要 A 的这种“无限等待”状态,在服务器启动时就能立刻被揪出来。
- 易于测试:不借助 Spring、用纯 Java 代码写单元测试时,我可以自己直接传入假对象(Mock)。
结语:不是魔法师,而是指挥家
一开始,我很抗拒 Spring,因为它看起来像是在随意摆布我的代码。但现在我明白了。Spring 不是折磨我的独裁者,而是一个帮我梳理复杂对象关系的优秀指挥家。
正因如此,我花在“到底要创建什么对象”上的时间变少了,可以把更多精力放在“如何用这些对象写好业务逻辑”上。这大概就是为什么韩国开发者会如此偏爱 Spring。
好了,现在我们已经把对象管理的权限交给了 Spring,服务器也准备启动了。但 Web 应用并不是自己单独运行的,它还必须和界面,也就是 Frontend 对话。PHP 时代,HTML 和代码混在一个文件里确实很方便;可为什么现在要把 Frontend(Vue、React)和 Backend 分开开发?而且为什么把两边一接起来,就会冒出红色的 CORS 错误?
下一篇,我们就来拆开看看 Web 架构的变化,SSR vs CSR,以及横在它们之间的 CORS 到底是什么。


