二、Spring Framework基础:IoC(控制反转)和DI(依赖注入)

news/2025/2/24 14:40:51

Spring Core:深入理解 IoC 和 DI 原理

在 Java 开发中,Spring Framework 是一个极为重要的框架,而 IoC(控制反转)和 DI(依赖注入)是 Spring 的核心特性。它们不仅帮助开发者简化代码的复杂性,还极大地提高了代码的可维护性和可扩展性。本文将深入探讨 IoC 和 DI 的原理,并通过实际代码示例帮助你更好地理解。

1. IoC(控制反转)是什么?

1.1 定义

IoC(Inversion of Control,控制反转)是一种设计思想,用于降低代码之间的耦合度。它的核心思想是:将对象的创建和管理交给框架,而不是由程序员手动创建和管理

在传统的编程中,对象的创建和依赖关系是由程序员手动管理的。例如:

java">public class Client {
    private Service service = new Service();  // 客户端直接创建服务对象

    public void doSomething() {
        service.doService();
    }
}

在这个例子中,Client 类直接依赖于 Service 类,这种依赖关系是硬编码的,耦合度很高。如果 Service 类的实现发生变化,Client 类也需要修改。

而 IoC 的模式下,对象的创建和管理由 Spring 容器负责,客户端不再直接创建服务对象:

java">public class Client {
    private Service service;  // 服务对象由外部注入

    public Client(Service service) {
        this.service = service;
    }

    public void doSomething() {
        service.doService();
    }
}

在这种模式下,Client 类不再直接创建 Service 对象,而是通过外部注入的方式获取依赖。这种方式将对象的控制权从程序员手中“反转”到了框架,因此得名“控制反转”。

1.2 IoC 的优势

  1. 降低耦合度:对象之间的依赖关系由 Spring 容器管理,减少了类之间的直接依赖。
  2. 提高可维护性:代码更加清晰,依赖关系通过配置管理,易于修改和扩展。
  3. 便于测试:依赖关系可以通过 Mock 对象注入,便于单元测试。

2. DI(依赖注入)是什么?

2.1 定义

DI(Dependency Injection,依赖注入)是 IoC 的一种实现方式。它通过外部配置(如 XML、注解或 Java 配置类)将依赖关系注入到目标对象中。DI 的目的是让对象的依赖关系由外部容器管理,而不是由对象自己创建。

2.2 DI 的实现方式

DI 有以下几种常见的实现方式:

2.2.1 构造器注入

构造器注入是通过构造方法将依赖注入到目标对象中。这种方式的优点是依赖关系明确,且对象在创建时必须提供所有依赖,保证了对象的不可变性。

java">public class Client {
    private final Service service;

    public Client(Service service) {
        this.service = service;
    }

    public void doSomething() {
        service.doService();
    }
}
2.2.2 Setter 方法注入

Setter 方法注入是通过 Setter 方法将依赖注入到目标对象中。这种方式的优点是灵活性高,可以在对象创建后动态修改依赖。

java">public class Client {
    private Service service;

    public void setService(Service service) {
        this.service = service;
    }

    public void doSomething() {
        service.doService();
    }
}
2.2.3 字段注入

字段注入是通过注解直接将依赖注入到字段中。这种方式的优点是代码简洁,但缺点是破坏了封装性,且依赖关系不明显。

java">public class Client {
    @Autowired
    private Service service;

    public void doSomething() {
        service.doService();
    }
}

2.3 DI 的优势

  1. 解耦:对象的依赖关系由外部容器管理,减少了类之间的直接依赖。
  2. 灵活性:依赖关系可以通过配置动态修改,而不必修改代码。
  3. 便于测试:依赖关系可以通过 Mock 对象注入,便于单元测试。

3. Spring IoC 容器的实现原理

Spring 的 IoC 容器是整个 Spring 框架的核心。它负责管理对象的生命周期、依赖注入和配置管理。

3.1 BeanFactory

BeanFactory 是 Spring IoC 容器的最基础实现,它提供了基本的依赖注入功能。它是一个接口,通常通过其实现类(如 ClassPathXmlApplicationContextFileSystemXmlApplicationContext)来使用。

java">import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class IoCDemo {
    public static void main(String[] args) {
        BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
        Service service = factory.getBean("service", Service.class);
        service.doService();
    }
}

3.2 ApplicationContext

ApplicationContextBeanFactory 的扩展,提供了更多高级功能,如事件传播、国际化支持等。它通常用于实际开发中。

java">import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IoCDemo {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Service service = context.getBean("service", Service.class);
        service.doService();
    }
}

3.3 Bean 的生命周期

Spring 容器管理 Bean 的生命周期,从创建到销毁的整个过程。以下是 Bean 生命周期的主要阶段:

  1. 实例化:Spring 容器调用无参构造方法实例化 Bean。
  2. 依赖注入:通过构造器或 Setter 方法注入依赖。
  3. 初始化:调用 @PostConstruct 注解的方法或实现 InitializingBean 接口的 afterPropertiesSet 方法。
  4. 使用:Bean 可以被其他 Bean 使用。
  5. 销毁:调用 @PreDestroy 注解的方法或实现 DisposableBean 接口的 destroy 方法。

3.4 配置方式

Spring 提供了多种配置方式,包括 XML 配置、注解配置和 Java 配置。

3.4.1 XML 配置
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="service" class="com.example.Service"/>
</beans>
3.4.2 注解配置

Spring 提供了注解来简化配置,如 @Component@Service@Controller@Repository 等。

java">import org.springframework.stereotype.Service;

@Service
public class Service {
    public void doService() {
        System.out.println("Service is running...");
    }
}

然后通过 @ComponentScan 扫描注解:

java">import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.example")
public class AppConfig {
}
3.4.3 Java 配置

除了注解,还可以通过 Java 配置类来定义 Bean:

java">import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public Service service() {
        return new Service();
    }
}

4. 实现一个简单的 IoC 容器

为了更好地理解 Spring 的 IoC 容器,我们可以尝试实现一个简单的 IoC 容器。

4.1 定义一个简单的 Bean

java">public class Service {
    public void doService() {
        System.out.println("Service is running...");
    }
}

4.2 定义一个简单的 BeanFactory

java">import java.util.HashMap;
import java.util.Map;

public class SimpleBeanFactory {
    private Map<String, Object> beans = new HashMap<>();

    public void registerBean(String name, Object bean) {
        beans.put(name, bean);
    }

    public Object getBean(String name) {
        return beans.get(name);
    }
}

4.3 测试 IoC 容器

java">public class IoCDemo {
    public static void main(String[] args) {
        SimpleBeanFactory factory = new SimpleBeanFactory();
        factory.registerBean("service", new Service());

        Service service = (Service) factory.getBean("service");
        service.doService();
    }
}

5. 构造器注入与 Setter 方法注入的对比

5.1 构造器注入

  • 优点
    • 依赖关系明确,对象在创建时必须提供所有依赖。
    • 保证对象的不可变性。
  • 缺点
    • 如果依赖关系较多,构造器参数会变得复杂。

5.2 Setter 方法注入

  • 优点
    • 灵活性高,可以在对象创建后动态修改依赖。
    • 代码简洁,易于理解。
  • 缺点
    • 对象的依赖关系不明显,可能导致对象在未完全初始化时被使用。

6. 实践案例:使用 Spring IoC 和 DI

6.1 定义一个业务逻辑类

java">import org.springframework.stereotype.Service;

@Service
public class BusinessService {
    public void doSomething() {
        System.out.println("Business logic is running...");
    }
}

6.2 定义一个客户端类

java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Client {
    private final BusinessService service;

    @Autowired
    public Client(BusinessService service) {
        this.service = service;
    }

    public void execute() {
        service.doSomething();
    }
}

6.3 配置类

java">import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public BusinessService businessService() {
        return new BusinessService();
    }

    @Bean
    public Client client(BusinessService service) {
        return new Client(service);
    }
}

6.4 测试

java">import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IoCDemo {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Client client = context.getBean(Client.class);
        client.execute();
    }
}

7. 总结

通过本文的介绍,你应该对 Spring 的 IoC 和 DI 原理有了更深入的理解。IoC 和 DI 是 Spring 框架的核心思想,它们通过将对象的创建和管理交给框架,极大地降低了代码的耦合度,提高了代码的可维护性和可扩展性。

在实际开发中,你可以根据需求选择合适的依赖注入方式(构造器注入、Setter 方法注入或字段注入),并利用 Spring 提供的强大功能来简化开发。

如果你在学习过程中有任何疑问,欢迎随时交流!


http://www.niftyadmin.cn/n/5864475.html

相关文章

github配置sshkey

使用命令生成sshkey ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 依此会要求输入以下信息&#xff0c;可以使用默认值 设置保存密钥的路径 设置SSH密钥密码&#xff08;备注&#xff1a;空内容表示不设置SSH密钥密码&#xff09; 再次确认SSH密钥密…

Linux 命令大全完整版(14)

5. 文件管理命令 chgrp(change group) 功能说明&#xff1a;变更文件或目录的所属群组。语  法&#xff1a;chgrp [-cfhRv][–help][–version][所属群组][文件或目录…] 或 chgrp [-cfhRv][–help][–version][–reference<参考文件或目录>][文件或目录…]补充说明&…

stm32四种方式精密控制步进电机

在搭建完clion的开发环境后&#xff0c;我决定重写之前的项目并优化完善&#xff0c;争取做出完全可落地的东西&#xff0c;也结合要写的论文内容一同学习下去。 因此&#xff0c;首当其冲的就是回到步进电机控制领域&#xff0c;把之前使用中断溢出进行步进电机控制的方案进行…

图像处理篇---图像处理中常见参数

文章目录 前言一、分贝&#xff08;dB&#xff09;的原理1.公式 二、峰值信噪比&#xff08;PSNR, Peak Signal-to-Noise Ratio&#xff09;1.用途2.公式3.示例 三、信噪比&#xff08;SNR, Signal-to-Noise Ratio&#xff09;1.用途2.公式3.示例 四、动态范围&#xff08;Dyna…

如何清理cache-loader生成的缓存目录?

清理 cache-loader 生成的缓存目录可以帮助避免潜在的缓存问题和不必要的磁盘占用。以下是几种清理缓存的有效方法&#xff1a; 一、手动清理 1. 定位缓存目录 在 Webpack 配置中&#xff0c;你可以指定 cache-loader 的缓存目录。默认情况下&#xff0c;缓存目录可能位于项…

深入解析JVM垃圾回收机制

1 引言 本节常见面试题 如何判断对象是否死亡&#xff08;两种方法&#xff09;。简单的介绍一下强引用、软引用、弱引用、虚引用&#xff08;虚引用与软引用和弱引用的区别、使用软引用能带来的好处&#xff09;。如何判断一个常量是废弃常量如何判断一个类是无用的类垃圾收…

一款社交媒体中查用户名的工具

简介 追踪 400 多个社交网络中的用户名社交媒体账户以查找账户 安装 # python环境 pip安装 pip install sherlock-project # Mac环境 brew安装 brew install sherlock # docker安装 docker pull sherlock/sherlock 使用方式 ->$ sherlock -h usage: sherlock [-h] [-…

Linux 命令大全完整版(08)

3. 文档编辑命令 joe 功能说明&#xff1a;编辑文本文件。语  法&#xff1a;joe [-asis][-beep][-csmode][-dopadding][-exask][-force][-help][-keepup][-lightoff][-arking][-mid][-nobackups][-nonotice][-nosta][-noxon][-orphan][-backpath<目录>][-columns<…