从“为什么要用 Spring“到“它到底帮我解决了什么“

这篇博客适合已经会写 Java、会写接口和实现类,但还不太理解 Spring 到底解决了什么问题的人。

不会一上来背概念,而是从我们写代码时真实会遇到的问题开始:明明已经用了接口和实现类,为什么还要 Spring?为什么不能自己写一个工厂、配置文件、反射来管理对象?


1. 没有 Spring 时,代码会怎么写?

假设我们有一个支付功能。

先定义一个支付接口:

复制代码
public interface PaymentService {
    void pay(double amount);
}

然后写一个支付宝实现:

复制代码
public class AlipayPayment implements PaymentService {
    public void pay(double amount) {
        System.out.println("支付宝支付了:" + amount + "元");
    }
}

再写一个订单服务,它需要调用支付服务:

复制代码
public class OrderService {
    private PaymentService paymentService = new AlipayPayment();

    public void createOrder(double amount) {
        System.out.println("创建订单...");
        paymentService.pay(amount);
    }
}

这段代码看起来没有问题。

接口也有了,实现类也有了,OrderService 依赖的是 PaymentService 接口,而不是直接依赖 AlipayPayment 的具体逻辑。

但问题在于这一行:

复制代码
private PaymentService paymentService = new AlipayPayment();

虽然变量类型是接口,但真正创建对象的地方,仍然写死了 new AlipayPayment()

也就是说,OrderService 虽然面向接口编程,但它仍然知道:"我现在用的是支付宝。"


2. 这里真正的问题不是"不能用接口"

很多人会说:

我不用 Spring,也可以写接口和实现类啊。

这句话是对的。

接口和实现类是 Java 多态的能力,不是 Spring 发明的。

Spring 解决的不是"能不能写接口"的问题,而是:

当项目里有很多类都依赖同一个接口时,谁来负责决定"到底 new 哪个实现类"?

如果项目很小,只有几个类,手动 new 其实没什么问题。

但如果项目变大,比如 OrderServiceProductServiceRefundServicePayServiceCheckoutService 等 30 个类都依赖 PaymentService,每个类里都写了:

复制代码
private PaymentService paymentService = new AlipayPayment();

那么当需求变化时,麻烦就来了。


3. 需求一变,手动 new 的缺点就暴露了

假设现在业务要求:

支付服务从支付宝切换到微信支付。

如果所有地方都是手动 new,你可能需要这样改:

复制代码
// OrderService.java
private PaymentService paymentService = new WechatPayment(); // 改!

// ProductService.java
private PaymentService paymentService = new WechatPayment(); // 改!

// RefundService.java
private PaymentService paymentService = new WechatPayment(); // 改!

如果项目里有 30 个地方,就要改 30 个地方。

这会带来几个问题:

问题 说明
修改范围大 需要找到所有依赖这个实现的地方
容易漏改 漏掉一个地方,线上就可能出现 bug
耦合度高 业务类里仍然知道具体实现是谁
测试困难 想换成 Mock 支付时,不方便替换
环境切换麻烦 开发环境、测试环境、生产环境想用不同实现时,不好处理

所以,真正的问题不是"能不能用接口",而是:

当依赖很多、实现经常变化、环境经常切换时,手动管理对象会变得很麻烦。


4. 一个现实比喻:餐厅里的厨师和经理

可以把项目里的各个 Service 想象成餐厅里的厨师。

厨师只需要负责炒菜,也就是业务逻辑。

但如果没有 Spring,厨师不仅要炒菜,还要自己决定:

  • 今天用哪个供应商的菜
  • 哪个洗碗工来洗碗
  • 哪个收银员来收银
  • 哪个保安来检查权限
  • 哪个保洁来打扫卫生

这显然不合理。

Spring 就像餐厅里的经理。

它负责:

  • 招聘员工
  • 分配岗位
  • 管理员工之间的关系
  • 统一处理公共事务,比如日志、事务、权限校验

厨师只需要说:

我需要一个支付服务。

至于具体给支付宝还是微信支付,由经理决定。

这就是 Spring 想做的事情:把对象的创建和管理从业务代码里抽出来。


5. 用 Spring 之后,代码会变成什么样?

使用 Spring 后,支付接口不变:

复制代码
public interface PaymentService {
    void pay(double amount);
}

支付宝实现加上注解:

复制代码
@Service("alipay")
public class AlipayPayment implements PaymentService {
    public void pay(double amount) {
        System.out.println("支付宝支付了:" + amount + "元");
    }
}

微信支付也加上注解:

复制代码
@Service("wechat")
public class WechatPayment implements PaymentService {
    public void pay(double amount) {
        System.out.println("微信支付了:" + amount + "元");
    }
}

订单服务不再手动 new

复制代码
@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;

    public void createOrder(double amount) {
        System.out.println("创建订单...");
        paymentService.pay(amount);
    }
}

这里最关键的变化是:

复制代码
@Autowired
private PaymentService paymentService;

OrderService 不再说:

我要 new 一个支付宝。

而是说:

我需要一个 PaymentService,具体给谁,由 Spring 容器决定。


6. 切换实现时,业务代码可以不动

如果现在要切换到微信支付,只需要让 Spring 知道默认使用微信支付。

比如给微信支付加上 @Primary

复制代码
@Service("alipay")
public class AlipayPayment implements PaymentService {
    public void pay(double amount) {
        System.out.println("支付宝支付了:" + amount + "元");
    }
}

@Service("wechat")
@Primary
public class WechatPayment implements PaymentService {
    public void pay(double amount) {
        System.out.println("微信支付了:" + amount + "元");
    }
}

此时,所有没有特别指定的地方:

复制代码
@Autowired
private PaymentService paymentService;

都会默认注入 WechatPayment

也就是说,原来 30 个 Service 里的业务代码一行都不用改。

这就是 Spring IoC 的价值之一:

把"用哪个实现"的决定权,从业务代码里拿出来,交给容器统一管理。


7. 为什么需要 @Primary?

假设容器里有两个 PaymentService 实现:

复制代码
@Service("alipay")
public class AlipayPayment implements PaymentService { ... }

@Service("wechat")
public class WechatPayment implements PaymentService { ... }

当某个类这样注入时

复制代码
@Autowired
private PaymentService paymentService;

Spring 会按类型去找 PaymentService

但此时它找到了两个:

  • alipay
  • wechat

Spring 不知道你到底要哪一个,就会报错。

@Primary 的作用就是告诉 Spring:

当有多个同类型 Bean 时,如果没有特别说明,默认选我。

例如:

复制代码
@Service("alipay")
@Primary
public class AlipayPayment implements PaymentService { ... }

这样,默认会注入支付宝实现。

但如果某个地方明确想要微信支付,可以用 @Qualifier 指定:

复制代码
@Autowired
@Qualifier("wechat")
private PaymentService paymentService;

@Qualifier 的优先级高于 @Primary

所以可以这样理解:

注解 作用
@Primary 默认首选
@Qualifier 明确指定某一个

8. 那能不能不用 Spring,自己写一个"自动找实现类"的机制?

可以。

而且这个想法并不是错的。

比如我们可以这样设计:

复制代码
public class OrderService {
    private PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

这里 OrderService 不再自己 new,而是通过构造方法接收一个 PaymentService

然后再写一个配置文件:

properties

复制代码
paymentService.impl=com.example.AlipayPayment

再写一个工厂

复制代码
public class ServiceFactory {

    public static PaymentService getPaymentService() throws Exception {
        Properties props = loadProperties("app.properties");
        String className = props.getProperty("paymentService.impl");

        Class<?> clazz = Class.forName(className);
        return (PaymentService) clazz.getDeclaredConstructor().newInstance();
    }
}

使用时:

复制代码
PaymentService paymentService = ServiceFactory.getPaymentService();

OrderService orderService = new OrderService(paymentService);
orderService.createOrder(100.0);

这样确实可以做到:

  • 业务代码不直接 new 实现类
  • 切换实现时只改配置文件
  • 多个类共用同一个工厂获取依赖

这个方案已经具备了 IoC 的雏形。

所以,"接口声明 + 配置文件 + 反射创建对象"这个思路本身并不依赖 Spring。


9. 既然自己也能做,为什么还要 Spring?

因为当项目变大以后,自己写工厂会变得越来越复杂。

你很快会遇到这些问题:

问题 说明
单例管理 同一个 Bean 是否应该只创建一次?
依赖注入 A 依赖 B,B 依赖 C,怎么自动组装?
初始化顺序 A 依赖 B,但 B 还没创建怎么办?
循环依赖 A 依赖 B,B 又依赖 A,怎么处理?
生命周期 Bean 创建后要做初始化,销毁前要释放资源,怎么管理?
AOP 能力 日志、事务、权限校验怎么统一处理?
多环境配置 开发、测试、生产环境如何自动切换?

如果只有一两个类,自己写工厂没问题。

但如果有几百个 Bean,每个 Bean 又有多个依赖,还涉及事务、代理、生命周期管理,手写工厂就会变成另一个复杂系统。

Spring 的价值就在这里:

它不是发明了"面向接口编程",也不是发明了"反射",而是把对象创建、依赖管理、生命周期、AOP、Web 开发、事务管理这些能力整合成了一个成熟框架。

也就是说,Spring 不是让你"不能自己实现",而是让你"不用每次都自己实现"。


10. Spring 的演进过程

Spring 的发展可以简单理解成一条演进路线:

复制代码
手动 new
    ↓
XML 配置
    ↓
注解配置
    ↓
Spring Boot 自动配置

每一步都在解决上一步的问题。

10.1 手动 new

优点:

  • 简单直接
  • 不需要框架

缺点:

  • 实现类和业务代码耦合
  • 切换实现要改代码
  • 依赖多时维护困难
10.2 XML 配置

业务代码可以保持干净:

复制代码
public class OrderService {
    private PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

然后在 XML 中配置:

xml

复制代码
<bean id="alipay" class="com.example.AlipayPayment" />

<bean id="orderService" class="com.example.OrderService">
    <constructor-arg ref="alipay" />
</bean>

优点:

  • 业务代码和配置分离
  • 切换实现时改配置即可

缺点:

  • XML 文件多了以后很难维护
  • 配置分散,不够直观
10.3 注解配置

现在更常见的是注解方式:

复制代码
@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;
}

优点:

  • 配置和代码在一起,直观
  • 开发效率高

缺点:

  • 业务代码中会混入框架注解
10.4 Spring Boot 自动配置

Spring Boot 进一步简化了配置。

很多场景下,你只需要加一个注解:

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

然后就可以开始写业务代码。

Spring Boot 并不是取代 Spring,而是让 Spring 更容易使用。


11. Spring 在项目里到底扮演什么角色?

在一个 Web 后端项目里,Spring 通常像一个隐形的大管家。

你写代码时不一定能明显感觉到它,但它一直在背后工作。

比如你写一个 Service:

复制代码
@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;

    @Transactional
    public void createOrder(double amount) {
        System.out.println("创建订单...");
        paymentService.pay(amount);
    }
}

Spring 会帮你做这些事情:

  • 创建 OrderService 对象
  • 创建 PaymentService 实现对象
  • PaymentService 注入到 OrderService
  • 管理这些对象的生命周期
  • 如果加了 @Transactional,还会处理事务

你不需要手动写:

复制代码
OrderService orderService = new OrderService(new AlipayPayment());

而是直接从 Spring 容器中获取已经组装好的对象。


12. 一次请求的数据流

假设用户点击"保存订单",数据在 Spring 项目里大致会这样流转:

复制代码
用户点击保存订单
        ↓
请求到达 Web 容器,比如 Tomcat
        ↓
Spring MVC 的 DispatcherServlet 接收请求
        ↓
根据请求路径找到对应的 Controller
        ↓
Controller 接收参数,调用 Service
        ↓
Service 执行业务逻辑
        ↓
Service 调用 Mapper 或 DAO 操作数据库
        ↓
数据库返回结果
        ↓
Service 返回结果给 Controller
        ↓
Controller 把结果封装成 JSON 返回给前端

在这个过程中,Spring 可能还会自动处理:

  • 参数校验
  • 日志记录
  • 权限检查
  • 事务开启和提交
  • 异常处理
  • 对象注入

如果业务执行过程中发生异常,并且方法上有事务控制,Spring 会帮助回滚事务。

如果请求还没处理完服务就宕机了,那已经提交的数据不会丢失,但没有提交的事务也不会生效。

Spring 能管理事务,但不能凭空恢复网络请求或客户端状态。


13. 一个完整的对比示例

13.1 不用 Spring
复制代码
public class OrderService {
    private PaymentService paymentService = new AlipayPayment();

    public void createOrder(double amount) {
        paymentService.pay(amount);
    }
}

public class ProductService {
    private PaymentService paymentService = new AlipayPayment();

    public void buyProduct(double amount) {
        paymentService.pay(amount);
    }
}

public class RefundService {
    private PaymentService paymentService = new AlipayPayment();

    public void refund(double amount) {
        paymentService.pay(amount);
    }
}

切换实现时,需要修改每一个类中的 new AlipayPayment()

13.2 使用 Spring
复制代码
@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;

    public void createOrder(double amount) {
        paymentService.pay(amount);
    }
}

@Service
public class ProductService {

    @Autowired
    private PaymentService paymentService;

    public void buyProduct(double amount) {
        paymentService.pay(amount);
    }
}

@Service
public class RefundService {

    @Autowired
    private PaymentService paymentService;

    public void refund(double amount) {
        paymentService.pay(amount);
    }
}

切换实现时,只需要改变 Spring 容器中默认使用的 PaymentService 实现。

业务类本身不需要修改。


14. 面试时可以怎么说?

如果面试时被问到"什么是 Spring",可以这样回答:

Spring 是一个轻量级的 Java 开发框架,主要用来简化企业级应用开发。它的核心价值不是让代码行数变少,而是降低类与类之间的耦合度。

在没有 Spring 的时候,如果多个 Service 都依赖同一个接口,我们通常会在代码里手动 new 实现类。这样一旦实现类发生变化,比如支付服务从支付宝切换到微信支付,就需要修改很多地方的代码。

Spring 通过 IoC 把对象的创建和依赖管理交给容器。业务代码只需要声明自己需要什么依赖,比如使用 @Autowired 注入一个接口,而具体使用哪个实现类,由 Spring 容器来决定。这就好比餐厅里的厨师不需要自己去找食材供应商,餐厅经理会统一安排好,厨师只需要专心炒菜。

除了 IoC,Spring 还有 AOP,可以把日志、事务、权限校验这些公共逻辑从业务代码中抽出来统一处理。Spring Boot 又进一步简化了配置,让项目可以更快启动和开发。

这段回答不需要一字不差地背,重点是表达清楚三个层次:

  1. Spring 解决什么问题
  2. IoC 和 AOP 分别做什么
  3. Spring Boot 的作用是什么

15. 面试追问:Spring IoC 和手动 new 的本质区别是什么?

这个问题很适合作为追问。

可以这样回答:

手动 new 时,对象之间的依赖关系是写死在代码里的。比如 OrderService 里直接 new AlipayPayment(),那么 OrderService 就和 AlipayPayment 产生了直接依赖。

使用 Spring IoC 后,对象之间的依赖关系由容器管理。OrderService 只需要声明自己需要一个 PaymentService,不需要关心具体实现是谁。Spring 容器启动时会根据配置、注解或类型匹配,把合适的实现注入进去。

所以本质区别是:手动 new 是业务代码自己决定依赖对象;Spring IoC 是把依赖对象的创建和管理交给外部容器,从而实现解耦。

16. 最后可以记住的一句话

Spring 不是让"接口和实现类"变得可能,因为 Java 本身就可以做到。

Spring 真正做的是:

把"用哪个实现类"这件事,从业务代码里拿出来,交给容器统一管理。

所以理解 Spring 时,不要只记:

Spring 是一个框架。

而要理解它解决的真实问题:

当项目变大、依赖变多、实现经常变化时,手动管理对象会让代码越来越难维护。Spring 通过 IoC 容器统一管理对象创建和依赖关系,让业务代码只关注业务逻辑,不关注对象是怎么来的。

相关推荐
不爱编程的小九九1 小时前
小九源码-springboot004-springboot智能阅读推荐系统
java·spring boot·后端
AC赳赳老秦2 小时前
OpenClaw 多源采集公开行业数据:从原始信息到研究报告初稿的自动化实践
java·c语言·c++·python·php·deepseek·openclaw
Java成神之路-2 小时前
深入理解三大高频队列:ArrayDeque、LinkedList、PriorityQueue 方法、特性与场景选择
java
roman_日积跬步-终至千里3 小时前
【资源控制】自助查询的智能路由
java·大数据·数据库
步行cgn3 小时前
MyBatis Error evaluating expression ‘ids‘. Return value (3) was not iterable 错误详
java·后端
lhldsg3 小时前
社区健身场地规划实战指南:从器材配置到智能化管理经验分享
java·开发语言·经验分享·小程序
LayZhangStrive3 小时前
后端通识 - 后端工程开发常用的流程图类别
java·流程图·后端开发
WIN赢4 小时前
【抽象思想-从复杂中抽离简单、收敛的口子】
java·前端·javascript
captain3764 小时前
多线程单例模式
java·单例模式·java-ee
xiaotianyuanma4 小时前
【计算机毕业设计】基于Spring Boot的体育馆预约系统的设计与实现
java·spring boot·课程设计