Spring 框架- 容器单例池的理解

问题 :在 Spring 框架中对于Bean要怎么设计以及使用呢 举个贴合生产的例子

用一个电商下单的真实场景,把三层架构完整走一遍。


先看整体结构:现在用完整代码走一遍"用户下单"这个流程:


Repository 层 --- 只管数据库操作

java 复制代码
// 只负责一件事:操作数据库
// 没有任何业务逻辑,没有任何可变状态
@Repository
public class OrderRepository {

    private final JdbcTemplate jdbc;  // Spring 提供的数据库工具,也是 Bean

    public OrderRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    // 保存订单到数据库
    public void save(Order order) {
        jdbc.update(
            "INSERT INTO orders(user_id, product_id, amount) VALUES (?,?,?)",
            order.getUserId(), order.getProductId(), order.getAmount()
        );
    }

    // 查询用户的所有订单
    public List<Order> findByUserId(Long userId) {
        return jdbc.query(
            "SELECT * FROM orders WHERE user_id = ?",
            (rs, row) -> new Order(rs.getLong("id"), rs.getLong("user_id")),
            userId
        );
    }
}

@Repository
public class UserRepository {

    private final JdbcTemplate jdbc;

    public UserRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    public User findById(Long userId) {
        return jdbc.queryForObject(
            "SELECT * FROM users WHERE id = ?",
            (rs, row) -> new User(rs.getLong("id"), rs.getString("name")),
            userId
        );
    }

    // 扣减用户余额
    public void deductBalance(Long userId, BigDecimal amount) {
        jdbc.update(
            "UPDATE users SET balance = balance - ? WHERE id = ?",
            amount, userId
        );
    }
}

Service 层 --- 只管业务逻辑

java 复制代码
// 核心:业务规则在这里,调用多个 Repository 协作
// 没有可变状态,所有字段都是 final
@Service
public class OrderService {

    // 注入需要的 Repository,都是单例,共用同一个内存地址
    private final OrderRepository orderRepository;
    private final UserRepository  userRepository;

    public OrderService(OrderRepository orderRepository,
                        UserRepository  userRepository) {
        this.orderRepository = orderRepository;
        this.userRepository  = userRepository;
    }

    // 业务方法:下单
    // 注意:@Transactional 保证这三步要么全成功,要么全回滚
    @Transactional
    public Order placeOrder(Long userId, Long productId, BigDecimal amount) {

        // 业务规则1:检查用户是否存在
        User user = userRepository.findById(userId);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }

        // 业务规则2:检查余额是否足够
        if (user.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }

        // 业务规则3:扣减余额
        userRepository.deductBalance(userId, amount);

        // 业务规则4:创建订单记录
        Order order = new Order(userId, productId, amount);
        orderRepository.save(order);

        return order;
        // 三步全成功 → 事务提交
        // 任何一步抛异常 → 事务回滚,数据库恢复原样
    }
}

Controller 层 --- 只管接收请求和返回响应

less 复制代码
// 只负责:接收 HTTP 请求、调 Service、把结果包装成 JSON 返回
// 不写任何业务逻辑,不写任何 SQL
@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;  // 注入 Service

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    // 接收 POST /orders 请求
    @PostMapping
    public ResponseEntity<String> placeOrder(@RequestBody PlaceOrderRequest req) {

        // 参数校验
        if (req.getUserId() == null || req.getAmount() == null) {
            return ResponseEntity.badRequest().body("参数不能为空");
        }

        // 调 Service 处理业务
        try {
            Order order = orderService.placeOrder(
                req.getUserId(),
                req.getProductId(),
                req.getAmount()
            );
            return ResponseEntity.ok("下单成功,订单ID:" + order.getId());

        } catch (RuntimeException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }
}

请求完整流程

scss 复制代码
POST /orders
    ↓
OrderController.placeOrder()    ← 接收请求,校验参数
    ↓
OrderService.placeOrder()       ← 执行业务规则,控制事务
    ↓
UserRepository.findById()       ← 查用户
UserRepository.deductBalance()  ← 扣余额
OrderRepository.save()          ← 存订单
    ↓
返回 "下单成功,订单ID:123"

Bean 设计的三条规则

规则一:每层只做自己的事,不越界

typescript 复制代码
// ❌ 错误:Controller 里写 SQL
@PostMapping
public String placeOrder() {
    jdbc.update("INSERT INTO orders...");  // Controller 不该碰数据库
}

// ❌ 错误:Repository 里写业务规则
public void save(Order order) {
    if (order.getAmount() < 0) { ... }  // 业务规则不该放这里
}

规则二:Bean 不存可变状态

java 复制代码
// ❌ 危险:Service 里存了用户信息(多线程共享会出错)
@Service
public class OrderService {
    private User currentUser;  // 不同请求的线程会互相覆盖这个值
}

// ✅ 正确:需要的数据通过方法参数传递
public Order placeOrder(Long userId, ...) {
    User user = userRepository.findById(userId);  // 每次从数据库取
}

规则三:依赖向下,不向上

kotlin 复制代码
// ✅ 正确的方向
Controller → Service → Repository → 数据库

// ❌ 错误:Repository 不能依赖 Service
@Repository
public class OrderRepository {
    @Autowired
    private OrderService orderService;  // 禁止!形成循环依赖,Spring 会报错
}

这三条规则保证了:每个 Bean 职责清晰、无状态、可以安全地被单例共享,不管多少个线程同时进来,都不会互相干扰。

生产中一个 Spring 应用启动后,这四个 Bean 在单例池里各自只有一个实例,从应用启动到关闭,全程就这一份。


但是你可能会有一个疑问:

如果只有一个 OrderService,同一时刻有 1000 个用户同时下单,1000 个请求同时进来,都用同一个 OrderService 对象,不会乱吗?

答案是不会,原因是这样的:

java 复制代码
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final UserRepository  userRepository;

    // 1000 个请求同时进来,调的是同一个方法
    @Transactional
    public Order placeOrder(Long userId, BigDecimal amount) {

        // 但是每个请求的 userId、amount 是各自的局部变量
        // 局部变量存在各自线程的栈内存里,互不干扰
        User user = userRepository.findById(userId);
        userRepository.deductBalance(userId, amount);
        Order order = new Order(userId, amount);  // 每个请求 new 自己的 Order
        orderRepository.save(order);
        return order;
    }
}

关键在于:userIdamountuserorder 这些都是方法内的局部变量 ,每个线程有自己独立的栈,局部变量互不共享。OrderService 对象本身只有两个 final 的依赖字段,从不改变。


用一张图说清楚:

ini 复制代码
单例池(共享)          线程栈(每个请求独立)
─────────────────       ──────────────────────────
OrderService @7a4f    ← 线程A:userId=1, amount=100, order=OrderA
OrderService @7a4f    ← 线程B:userId=2, amount=200, order=OrderB
OrderService @7a4f    ← 线程C:userId=3, amount=300, order=OrderC

同一个对象,但每个线程的局部变量完全独立

1000 个请求共用同一个 OrderService,但每个请求的数据活在自己的线程栈里,谁也看不到谁的数据。


所以生产中的真实情况是:

对象 数量 存在哪里
OrderController 1 个 单例池
OrderService 1 个 单例池
OrderRepository 1 个 单例池
UserRepository 1 个 单例池
每次请求的 Order 对象 每个请求一个 线程栈/堆,用完 GC 回收
每次请求的参数(userId 等) 每个请求一份 线程栈,方法结束即消失

Bean 是单例复用的,但每次请求产生的数据是独立的、临时的。这就是为什么 Bean 设计成无状态之后,单例是完全安全的。

相关推荐
lulu121654407832 分钟前
OpenRouter Fusion 多模型融合架构深度拆解:预算级模型组团打平 Fable 5,多模型协作才是 AGI 的正确打开方式?
java·人工智能·架构·ai编程·agi
雨辰AI37 分钟前
生产级实测:SpringBoot3 + 达梦数据库接口从 200ms 优化至 20ms 完整调优指南
java·数据库·spring boot·后端·政务
(Charon)1 小时前
【C++ 面试高频:内存管理、RAII 和智能指针详解】
java·开发语言·word
凡人叶枫1 小时前
Effective C++ 条款39:明智而审慎地使用 private 继承
java·数据库·c++·嵌入式开发
轻刀快马2 小时前
跨越软硬件的共鸣(二):从 Cache 写策略看 Redis 与 DB 的一致性博弈
java·开发语言·redis·计算机组成原理
折哥的程序人生 · 物流技术专研2 小时前
Java 23 种设计模式:从踩坑到精通 | 装饰器模式 —— 比继承更灵活的扩展方式,你用过吗?
java·装饰器模式·java面试·结构型模式·java设计模式·javaio·从踩坑到精通
lili00122 小时前
2026 企业 AI 选型新范式:OpenRouter Fusion 证明多模型融合性价比远超单模型,企业该如何重构技术栈? - 微元算力(weytoken)
java·人工智能·python·重构·ai编程
shushangyun_2 小时前
汽车服务行业B2B平台+AI解决方案哪家专业:2026年最新测评
java·运维·网络·数据库·人工智能·汽车
A.说学逗唱的Coke2 小时前
【大模型专题】Spring AI Alibaba × Skill 整合实战:让 AI 真正“会干活
java·人工智能·spring
大黄说说2 小时前
深入理解 Go 协程 Goroutine:并发编程的核心精髓
java·数据库·python