拓冰建站拓冰建站
首页 / 资讯中心 / 正文

一文讲透 Spring IoC 与 Bean:依赖注入、容器区别及单例线程安全

Spring 最核心的能力是 IoC 容器对象不再由业务代码主动创建而是交给容器统一实例化、装配和管理。理解 IoC、DI、Bean 容器和单例线程安全是掌握 Spring 的基础。一、什么是 IoC 和 DIIoC控制反转是一种设计思想。传统代码由对象主动new出依赖而在 Spring 中对象的创建权和依赖管理权交给容器控制权由业务对象反转到了 Spring。DI依赖注入是实现 IoC 的主要手段。Bean 只需要声明自己依赖什么Spring 在创建 Bean 时查找合适对象并注入。例如OrderService依赖OrderRepository。传统写法由OrderService自己创建 Repository使用 DI 后Repository 由容器提供Service public class OrderService { private final OrderRepository orderRepository; public OrderService(OrderRepository orderRepository) { this.orderRepository orderRepository; } }因此两者的关系可以概括为IoC 是目标和思想DI 是 Spring 实现 IoC 的方式。二、Spring 的依赖注入方式1. 构造器注入Spring 创建对象时通过构造方法传入依赖Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository userRepository; } }只有一个构造器时可以省略Autowired。构造器注入适合必须存在的依赖也是 Spring 官方推荐的主要方式。2. Setter 注入Spring 先创建对象再调用 Setter 方法完成注入Service public class UserService { private UserRepository userRepository; Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository userRepository; } }Setter 注入更适合可选依赖或者确实需要在创建后重新配置依赖的场景。3. 字段注入Autowired private UserRepository userRepository;字段注入写法简短但依赖被隐藏无法声明为final脱离 Spring 容器进行单元测试时也不方便因此一般不建议在核心业务代码中使用。4. 普通方法或工厂方法参数注入Spring 也可以给配置方法、Bean方法或其他注入方法的参数提供依赖Bean public OrderService orderService(OrderRepository repository) { return new OrderService(repository); }从官方定义看依赖注入主要分为构造器注入与 Setter 注入字段和普通方法注入是注解驱动开发中的常见形式。三、构造器注入与 Setter 注入的区别对比项构造器注入Setter 注入适用依赖必需依赖可选依赖完整性创建后立即完整可用可能出现未注入状态不可变性支持final字段通常不能使用final单元测试可直接传入 Mock 对象需要调用 Setter循环依赖构造器循环依赖通常无法解决某些单例循环依赖可能被处理后续修改创建后不宜替换可以重新设置实际开发中应优先使用构造器注入它能明确暴露必需依赖使对象处于完整状态并有利于构建不可变组件。如果构造器参数过多通常说明类承担了太多职责应考虑拆分而不是改用字段注入隐藏问题。四、BeanFactory 与 ApplicationContextBeanFactory是 Spring IoC 容器的基础接口提供 Bean 的创建、查找、依赖注入和作用域管理等核心能力。ApplicationContext是更高层的容器接口继承了BeanFactory的能力并面向实际应用提供更多功能。能力BeanFactoryApplicationContextBean 创建和依赖注入支持支持自动注册后置处理器通常需手动处理支持国际化MessageSource不直接提供支持应用事件发布不直接提供支持资源访问和环境配置基础能力更完整常见使用场景框架底层、特殊容器控制普通 Spring 应用常见的AnnotationConfigApplicationContext、Web 应用中的WebApplicationContext都属于ApplicationContext体系。实际项目通常直接使用ApplicationContext只有需要精细控制容器启动过程时才直接操作BeanFactory。还有一个常见说法是“BeanFactory 懒加载ApplicationContext 饿加载”。这并不严谨普通单例 Bean 在ApplicationContext中默认会预实例化但可以使用Lazy改为延迟创建是否懒加载最终取决于容器启动方式和 Bean 配置。五、Spring 单例 Bean 线程安全吗Spring 的singleton只保证同一个 Bean 定义在同一个容器中共享一个实例并不自动保证该对象线程安全。当多个请求同时调用单例 Bean 时它们访问的是同一个对象。1. 无状态 Bean 通常是线程安全的如果 Bean 不保存会随请求变化的共享数据方法只使用参数和局部变量多个线程之间就不会互相影响Service public class PriceService { public int calculate(int price, int count) { int total price * count; return total; } }total存放在线程各自的方法栈中因此不会产生共享竞争。需要注意有成员变量不一定代表有状态。例如下面的final依赖引用也是成员变量但它不用于保存某个请求的临时数据Service 通常仍被视为无状态private final UserRepository userRepository;判断关键不是“有没有成员变量”而是“有没有被多个线程共同读写的可变状态”。2. 有状态 Bean 可能不安全Service public class CounterService { private int count 0; public void increase() { count; } }count包含读取、加一和写回多个步骤。多个线程并发执行时可能相互覆盖出现更新丢失因此该单例 Bean 不是线程安全的。六、有状态 Bean 如何保证线程安全最优方案通常不是给所有方法加锁而是尽量消除共享可变状态改用局部变量请求相关数据放在方法参数或局部变量中。使用不可变对象成员变量使用final对象创建后不再修改。使用线程安全工具简单计数可使用AtomicInteger共享集合可使用ConcurrentHashMap。使用锁复合操作需要整体原子性时可使用synchronized或Lock但要评估锁竞争。调整 Bean 作用域请求状态可使用request会话状态可使用session确需每次获取新实例时可考虑prototype。谨慎使用 ThreadLocal可用于保存线程独享数据但在线程池中必须在finally中调用remove()否则可能数据串用或内存泄漏。状态下沉到专业组件将共享数据交给数据库、Redis 等具备并发控制能力的存储系统。Spring 官方通常建议无状态 Bean 使用singleton真正有会话状态的 Bean 考虑prototype或合适的 Web 作用域。但改变作用域不等于自动解决所有业务并发问题还要结合对象的使用方式判断。参考资料Spring 依赖注入、BeanFactory API、Spring Bean 作用域。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门