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 设计成无状态之后,单例是完全安全的。

相关推荐
阿维的博客日记17 小时前
线程任务执行报错后,线程会不会挂掉,Java线程池
java·线程池
yh弓长17 小时前
算法积累笔记
java·算法
LeocenaY17 小时前
C/C++ 面试题总结
java·c++·面试
雨落在了我的手上17 小时前
初识java(十一):继承
java·开发语言
XS03010617 小时前
MyBatis关联映射
java·mybatis
码农小旋风17 小时前
IDEA 不只接 Claude 和 Codex:本地模型和第三方 API 也能直接用
java·ide·人工智能·chatgpt·intellij-idea·claude
骆驼整理说17 小时前
Cursor辅助编程工具
java·ai编程
xiep143833351017 小时前
华为系列服务器开启Monitor/MWAIT
java·服务器·网络
yaoxin52112317 小时前
417. 现代 Java IO 最佳实践 - 高效遍历、ZIP 处理与临时文件管理
java·开发语言·windows