文章目录
- 核心概念
-
- 控制反转 (Inversion of Control – IoC)
- 依赖注入 (Dependency Injection – DI)
- Spring IoC 容器:机制与实现
-
- 依赖注入 (DI) 在 Spring 中的主要方式
- Spring 是如何知道要注入什么的?配置与扫描
- Spring IoC/DI 带来的巨大好处
- Spring IoC 容器的高级特性
- 总结
核心概念
想象一下你开发一个简单的应用,有一个 OrderService 用来处理订单逻辑,它需要依赖一个 PaymentService 来处理支付。在传统的编程方式(常被称为 “控制正转”)中,你会这样写:
public class OrderService {
private PaymentService paymentService = new CreditCardPaymentService(); // 或者 new PayPalPaymentService();
public void processOrder(Order order) {
// … 业务逻辑 …
paymentService.processPayment(order);
// … 更多业务逻辑 …
}
}
这里 OrderService 完全控制并负责创建其所依赖的 PaymentService 实例 (new CreditCardPaymentService())。这看似直接,但存在几个关键问题:
控制反转 (Inversion of Control – IoC)
IoC 是一种设计原则,它的核心理念是:将创建和绑定依赖对象的控制权从应用程序代码转移到外部容器(在 Spring 中就是 IoC 容器)。
- 反转了什么? 反转了创建和管理依赖对象的责任。应用程序代码被动地接收它所需要的依赖,而不是主动地去创建或查找。
- 好莱坞原则 (“Don’t call us, we’ll call you”): 很好地描述了 IoC。你的类(如 OrderService)不需要去找 (new) 它的依赖,只需要声明它需要什么 (“我需要一个 PaymentService”),IoC 容器(导演)会在合适的时机创建好并“打给你”(注入给你)。
- 目标:解耦 (Decoupling):应用程序代码不依赖于具体的依赖实现,而是依赖于抽象(接口)。具体的实现选择和组装工作由容器完成。这是实现松耦合的关键。
依赖注入 (Dependency Injection – DI)
依赖注入是实现控制反转原则最常见、最主要的设计模式。DI 定义了如何将依赖提供给目标对象。
- 核心思想: 在创建对象(Bean)时,由外部实体(IoC 容器)将其所依赖的其他对象(Beans)通过某种方式(构造器、Setter、字段)注入进去。
- 关键: 对象之间的关系(依赖)不再由对象自身在内部建立,而是由运行环境(容器)在对象外部建立并“注射”进去。
Spring IoC 容器:机制与实现
Spring 框架是 IoC 原则的卓越实现者。它的核心是 IoC 容器。主要的容器接口是 ApplicationContext(及其具体实现类,如 AnnotationConfigApplicationContext, ClassPathXmlApplicationContext, FileSystemXmlApplicationContext)。它的职责:
依赖注入 (DI) 在 Spring 中的主要方式
假设我们定义接口和实现:
public interface PaymentService {
void processPayment(Order order);
}
@Component // 或 @Service, @Repository等,标记这个类是Bean
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(Order order) {
// 信用卡支付逻辑
}
}
@Service // 标记OrderService为Bean
public class OrderService {
// 需要依赖一个PaymentService
private final PaymentService paymentService;
// 方式1:构造器注入 (推荐)
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService; // 容器在这里注入依赖
}
// 方式2:Setter方法注入
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService; // 容器通过调用此方法注入
}
// 方式3:字段注入 (不推荐,存在隐患,可以使用@Resource替代)
@Autowired
private PaymentService paymentService;
public void processOrder(Order order) {
// … 使用 paymentService …
paymentService.processPayment(order);
}
}
构造器注入 (Constructor Injection):
- 怎么做? 在类的构造器上声明依赖参数。
- Spring 的装配: 容器在创建 Bean (OrderService) 时,调用其构造器并传入所需的依赖 (PaymentService)。
- 优点:
- 强制依赖: 确保 Bean 在创建完成后就处于完全初始化状态,所有必要依赖都已满足。避免 NullPointerException。
- 不可变(Immutable): 通常配合 final 字段使用,使得依赖在对象生命周期内不可变,更安全(尤其是多线程)。
- 明确表达 Bean 的必需依赖项。
- Spring 鼓励使用的方式。 是 Spring Framework 团队的首选。
- 示例: public OrderService(PaymentService paymentService) { … }
Setter 注入 (Setter Injection):
- 怎么做? 为需要注入的依赖提供一个公共的 setter 方法(如 setPaymentService)。
- Spring 的装配: 容器首先通过无参构造器(或指定构造器)创建 Bean 实例,然后调用相应的 setter 方法来注入依赖。
- 优点:
- 可选依赖: 适合那些不是强制性的、可有可无、或者有默认实现的依赖。
- 灵活性: 对象可以在构造后进行重新配置(但实践中较少改变已装配 Bean 的依赖)。
- 示例: public void setPaymentService(PaymentService paymentService) { … }
字段注入 (Field Injection):
- 怎么做? 直接在需要依赖的类字段上标注 @Autowired (或其他注解如 @Inject, @Resource)。
- Spring 的装配: 容器利用反射机制直接将依赖注入到私有字段(或者protected、public字段,但私有更常见),不需要构造器或 setter。
- 缺点(严重,不推荐!最好使用@Resource):
- 违反了封装性: 直接修改私有字段绕过了任何可能存在的构造器或 setter 逻辑。
- 难以测试: 无法通过构造器或 setter 轻松传入模拟依赖,单元测试时必须依赖 Spring 容器或使用反射。
- 隐藏依赖: 类从外部看,哪些是必需的依赖不清晰(不像构造器那样一目了然)。
- 潜在的空指针异常: 如果脱离容器手动实例化类,字段依赖不会自动注入,容易引发 NPE。
- Spring 官方不推荐这种方式。 应该优先使用构造器注入,其次是 setter 注入。
- 示例: @Autowired private PaymentService paymentService;
Spring 是如何知道要注入什么的?配置与扫描
Spring 容器需要知道:
Spring 通过两种主要方式获取这些信息:
基于注解的配置 (Annotation-based Configuration – 现代主流方式):
- 组件扫描 (@ComponentScan): 在配置类(标注 @Configuration)上使用 @ComponentScan("包路径"),告诉容器去扫描指定包及其子包下所有标注了 @Component, @Service, @Repository, @Controller 的类,将它们自动注册为 Bean。
- 自动装配 (@Autowired 等): 在构造器、setter 方法或字段上使用 @Autowired 注解,容器会自动查找匹配类型的 Bean 进行注入(按类型优先)。
- 查找规则 (按顺序):
- 按类型查找(PaymentService)。
- 如果找到多个该类型的 Bean(比如有 CreditCardPaymentService 和 PayPalPaymentService 都实现了 PaymentService),则按名称匹配(变量名/参数名需要与其中一个 Bean 的名字匹配)。
- 或者使用 @Qualifier("beanName") 明确指定要注入哪个特定名称的 Bean。
@Configuration
@ComponentScan("com.example.services") // 扫描包
public class AppConfig {
// 可选:显式定义一个Bean,方法名默认是Bean的id
@Bean
public SpecialService specialService(PaymentService paymentService) { // 自动注入
return new SpecialService(paymentService);
}
}
基于 XML 的配置 (XML-based Configuration – 较老方式,逐步被注解替代):
- 在 XML 文件中显式定义 及其
- 使用 , , `` 元素手动配置依赖。
- 现在新建项目较少推荐纯 XML 配置,通常与注解结合或只用注解。
Spring IoC/DI 带来的巨大好处
Spring IoC 容器的高级特性
- Bean 作用域 (Scope): Singleton(默认,一个容器一个实例),Prototype(每次请求创建新实例),Request(HTTP 请求),Session(HTTP Session),Application(ServletContext),WebSocket(WebSocket Session)。
- 条件化装配 (Conditional Bean Registration): 使用 @Profile 或 @Conditional(及其自定义实现)根据特定条件(如环境变量、系统属性、类路径是否存在等)决定是否注册或激活某个 Bean。
- 延迟初始化 (Lazy Initialization): @Lazy 使得 Bean 只在第一次被请求使用时才创建。
- 事件监听 (Application Events): 容器支持发布和监听自定义应用事件,实现了 Bean 之间的一种松耦合通信机制。
总结
- 控制反转 (IoC) 是一个核心设计原则:将创建和协调依赖对象的责任反转给外部容器。它实现了应用程序组件之间的解耦。
- 依赖注入 (DI) 是实现 IoC 原则的主要设计模式:容器在创建对象时,将其所依赖的其他对象的引用注入进去。Spring 提供了构造器注入(最佳实践)、setter 注入和字段注入(避免使用)等方式。
- Spring IoC 容器 (ApplicationContext) 是 IoC/DI 的核心机制:它负责 Bean 的生命周期管理(创建、配置、装配、销毁)。
- 主要配置方式: 现代 Spring 项目主要通过组件扫描 (@ComponentScan)、自动装配 (@Autowired) 和在 @Configuration 类中使用 @Bean 方法来定义和组装 Bean。
- 核心优势: 松耦合、可测试性增强、代码简化、易于维护和扩展。
理解 IoC 和 DI 是掌握 Spring 框架精髓的关键第一步。 它们是 Spring 能够提供声明式事务管理、AOP、Spring MVC、Spring Boot 自动配置等诸多强大功能的基础架构支撑。采用 IoC/DI 模式编写的应用程序,其结构更加优雅、健壮且易于演变。
评论前必须登录!
注册