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;
    }
}

关键在于:userId、amount、user、order 这些都是方法内的局部变量 ,每个线程有自己独立的栈,局部变量互不共享。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 设计成无状态之后,单例是完全安全的。

相关推荐
xcl09255 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐6 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长7 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn7 小时前
Spring 注解使用详解
java·spring
一条小小yu7 小时前
Spring IoC的理解
java·后端·spring
落魄实习生7 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈7 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白9 小时前
核心功能(Service接口)
java·mybatis
乌暮10 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习