Spring--IoC与DI

一、IoC简介

IoC(Inversion of Control,控制反转)是Spring框架的核心部分,是一种设计思想,而不是一个具体的技术实现。它通过将对象创建和管理的控制权从应用代码转移到Spring容器中,实现了松耦合设计。以下是对Spring IoC的详细解释:

  1. IoC概述‌:
    • IoC的核心思想是控制权的转移,即将对象的创建和生命周期管理等控制权交给IoC容器。
    • IoC容器负责创建、组装、管理bean,这些对象被称为Spring Beans。
  2. ‌IoC的作用‌:
    • 降低耦合性:将组件之间的依赖关系从代码中解耦,使得组件不再直接依赖于特定的实现类,而是依赖于抽象接口或者规范。
    • 简化代码:通过IoC容器管理对象的生命周期和依赖关系,可以减少开发人员编写大量的样板代码。
    • 提高系统的可管理性和可维护性:IoC容器可以集中管理应用程序中的所有组件。
    • 提高系统的灵活性和可测试性:由于依赖关系和配置信息被集中管理,可以通过修改配置文件或者注解来实现不同的配置。

二、IoC容器简介

IoC容器(Inversion of Control Container)是一种用于实现IoC(控制反转)设计模式的工具。它负责创建、配置和管理对象及其依赖关系,自动完成对象的创建、初始化、注入等操作,从而简化开发流程,提高开发效率。

2.1、IoC容器的主要职责

IoC容器的主要职责包括:

  1. 对象的创建和管理‌:
    • IoC容器负责创建和管理应用程序中的对象。这包括根据配置信息(如XML文件、注解或Java配置类)来实例化对象,并将这些对象存储在容器中。容器还负责对象的生命周期管理,包括对象的创建、初始化、使用和销毁等各个阶段。
  2. 依赖关系的处理‌:
    • IoC容器通过依赖注入(Dependency Injection)来管理对象之间的依赖关系。这意味着,当一个对象需要另一个对象时,容器会自动将所需的依赖项注入到该对象中。这降低了对象之间的耦合度,使得对象之间不再直接依赖于具体的实现类,而是依赖于接口或抽象类。
  3. 配置信息的读取和应用‌:
    • IoC容器负责读取和管理配置信息。这些配置信息通常用于定义对象、对象的属性、依赖关系等。容器会将这些配置信息应用到对象实例上,确保对象按照预期的方式运行。
  4. ‌支持AOP(面向切面编程)‌:
    • 支持AOP编程,允许开发者将横切关注点(如日志记录、事务管理等)与业务逻辑分离,提高了代码的可读性和可维护性。
  5. ‌支持多种配置方式‌:
    • IoC容器支持多种配置方式,包括XML配置、注解配置和Java配置等。这使得开发者可以根据实际情况选择最适合自己的配置方式。
2.2、IoC容器的工作流程

IoC容器的工作流程主要包括配置、容器初始化、依赖注入和对象使用四个阶段‌:

  1. 配置‌:
    • 通过配置文件(如XML)、注解(如@Component、@Autowired)或Java配置类来定义Spring Bean及其依赖关系‌。这些配置信息详细说明了如何创建和管理对象,以及对象之间的依赖关系。
  2. ‌容器初始化‌:
    • 当Spring容器启动时,它会根据配置文件或注解扫描等方式,加载并解析这些配置信息,然后创建并初始化定义的Bean‌。这个过程中,容器会读取配置,准备Bean定义,并实例化单例Bean。
  3. 依赖注入‌:
    • 容器在创建对象时,会自动注入所需要的依赖对象。这通常通过构造器注入、Setter注入或字段注入(不推荐)等方式实现‌。依赖注入是IoC的核心,它确保了对象之间的依赖关系由容器来管理,从而降低了对象之间的耦合度。
  4. ‌对象使用‌:
    • 当应用程序需要使用这些对象时,它们已经由容器创建并初始化好了。开发者可以直接从容器中获取对象实例,并使用它们来完成业务逻辑‌。由于容器管理了对象的生命周期和依赖关系,因此开发者无需关心对象的创建和销毁过程。
2.3、IoC容器创建对象实现方式

在Spring框架中,IoC容器可以通过XML配置文件、注解或Java配置类来创建对象。下面分别介绍下这三种方式的实现样例。

(1)XML配置

<!-- 引入Spring的命名空间,定义Bean及其依赖关系 -->
<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 -->
    <bean id="userService" class="com.example.service.UserServiceImpl">
        <!-- 配置依赖 -->
        <property name="userDao" ref="userDao"/>
    </bean>

    <!-- 定义另一个Bean -->
    <bean id="userDao" class="com.example.dao.UserDaoImpl"/>

</beans>
在这个例子中,我们定义了两个Bean:userService和userDao。
userService依赖于userDao,我们通过<property>元素将userDao的实例注入到userService中。
ref属性用于引用另一个Bean的ID。

(2)注解配置

1. 首先,确保你已经在Spring配置文件中开启了注解扫描:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.example"/>

</beans>

2. 然后,在你的类上使用@Component、@Service、@Repository或@Controller等注解来标记它们为Spring管理的Bean:
@Service
public class UserServiceImpl implements UserService {
    
    private final UserDao userDao;

    @Autowired // 自动装配依赖
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    // ...
}
在上面的代码中,@Service注解表明UserServiceImpl是一个服务层的Bean,
而@Autowired注解则用于自动装配UserDao依赖。Spring容器在启动时会扫描指定的包路径,
查找带有上述注解的类,并将它们注册为Bean。

(3)Java配置类
Spring容器在启动时会扫描带有@Configuration注解的类,并执行所有标记有@Bean注解的方法,创建相应的Bean,并管理它们的生命周期。

// 标记为@Configuration的类告诉Spring这是一个配置类
@Configuration
public class AppConfig {

	// 使用@Bean注解创建并返回一个对象,这个对象将作为Spring容器中的Bean
    @Bean
    public AnotherDependency anotherDependency() {
        return new AnotherDependency();
    }

    @Bean
    public ExampleService exampleService(AnotherDependency anotherDependency) {
        ExampleService exampleService = new ExampleService();
        exampleService.setAnotherDependency(anotherDependency);
        return exampleService;
    }
}
在这个例子中,anotherDependency方法创建了AnotherDependency的一个实例。
然后在exampleService方法中,AnotherDependency实例被作为参数传入,并设置给ExampleService实例。
这样,Spring容器就会处理ExampleService的依赖注入。
2.4、IoC容器机制源码解析

Spring IoC容器的源码解析可以从以下几个核心组件入手:
(1)BeanDefinition
  BeanDefinition是描述Bean的元数据,包括Bean的类型、作用域、构造函数参数、依赖关系等。它是一个接口,具体实现类如RootBeanDefinition和ChildBeanDefinition等。
  Spring IOC容器从各种源加载BeanDefinition,主要包括XML文件、注解和Java配置。

  • XML配置:使用XmlBeanDefinitionReader读取XML文件,并通过DocumentLoader解析XML文档,最后通过BeanDefinitionParserDelegate解析标签并创建BeanDefinition对象。
  • 注解配置:使用AnnotatedBeanDefinitionReader扫描类路径下带有特定注解的类,并创建相应的BeanDefinition。
  • Java配置:通过JavaBeanDefinitionReader读取Java配置类,该类通常实现了BeanDefinitionRegistryPostProcessor接口,并使用@Configuration注解标注。

(2)BeanFactory和ApplicationContext
  BeanFactory是IOC容器的核心接口,负责Bean的实例化、配置和组装。ApplicationContext是BeanFactory的子接口,提供了更多的企业级功能,如事件传播、资源加载等。
  创建的BeanDefinition需要注册到容器内部,以便后续能够创建对应的Bean实例。这步便是由BeanFactory来实现的,所有的BeanDefinition都存储在其内部的Map中,键为Bean的名字,值为BeanDefinition对象。
(3)容器的启动
  容器启动的入口通常是通过ClassPathXmlApplicationContext或AnnotationConfigApplicationContext等实现类的构造函数开始的,它们会初始化BeanFactory,加载BeanDefinition,并注册Bean。
(4)BeanDefinitionRegistryPostProcessor
  这个接口允许在注册BeanDefinition之后、实例化Bean之前进行自定义的处理,如动态添加BeanDefinition或修改已有的BeanDefinition。
(5)实例化Bean
  AbstractBeanFactory类是BeanFactory的一个抽象实现类,它提供了创建Bean的基本方法。其中doGetBean方法是创建Bean的核心,它会根据Bean的名字和类型从内部的Map中获取BeanDefinition,然后通过createBean方法实例化Bean。
(6)依赖注入
  依赖注入是通过InstantiationStrategy接口实现的。对于简单的Bean,通常使用默认的SimpleInstantiationStrategy;对于需要代理的Bean,则会使用CglibSubclassingInstantiationStrategy。
(7)Bean的生命周期
  在Bean实例化和依赖注入之后,Spring会调用initMethodName指定的初始化方法(如afterPropertiesSet)。在Bean被销毁之前,会调用destroyMethodName指定的销毁方法(如destroy)。
(8)AOP代理
  Spring支持面向切面编程(AOP),通过ProxyFactory和AopProxy接口创建代理,将横切关注点应用到目标Bean上。

三、DI 简介

‌DI(Dependency Injection,依赖注入)是实现IoC(Inversion of Control,控制反转)的一种方式或手段‌。其核心理念在于,组件之间的依赖关系不是由组件本身在编译时静态决定的,而是由IoC容器在运行期动态地将依赖关系注入到组件之中‌。通过依赖注入,组件类不再直接依赖于其依赖对象的具体实现,而是依赖于抽象(如接口),从而实现了组件类和其依赖对象的解耦‌。这种解耦使得组件类的代码更加整洁,也更易于测试和维护‌。

3.1、依赖注入的优点
  1. 降低耦合度‌:依赖注入使得代码之间的依赖关系更加松散。通过外部注入依赖,类之间的耦合度降低,每个类只关注自身的功能,而不关心依赖的创建方式。这有助于实现代码的模块化和可重用性‌。
  2. 提高代码可测试性‌:依赖注入使得在测试中可以轻松替换真实依赖为模拟对象,从而简化测试过程。由于依赖是从外部注入的,测试时可以独立测试每个类,而不必依赖复杂的上下文。这有助于提高代码的测试覆盖率和测试效率‌。
  3. 提高代码可维护性‌:依赖注入使得代码更加模块化,每个组件只关注自己的职责。当需要更改依赖的实现时,只需在配置中进行更改,而不需要修改业务逻辑代码。这有助于降低代码的维护成本和提高代码的可读性‌。
  4. 提高代码可重用性‌:依赖注入允许将共享的依赖项提取到外部容器中,并在需要时注入到组件中。这有助于减少代码重复,提高代码重用性。通过定义接口和注入实现类,可以方便地扩展和替换依赖,而不影响现有代码‌。
3.2、依赖注入实现方式

Spring支持三种主要的依赖注入方式:构造器注入、Setter注入和字段注入。

  1. 构造器注入:通过在类的构造函数中声明依赖项,并在创建对象时通过构造函数参数传递依赖项。这种方式可以确保依赖项在对象创建时就被初始化,并且对象在整个生命周期中始终保持不变。
    • 优点:可确保依赖项的完整性,支持不可变对象。
    • 缺点:当依赖项过多时,构造函数可能会变得冗长且难以阅读。
@Component
public class ExampleService {
    private final AnotherDependency anotherDependency;

    @Autowired
    public ExampleService(AnotherDependency anotherDependency) {
        this.anotherDependency = anotherDependency;
    }
}
  1. Setter注入:通过在类中提供setter方法来注入依赖项。这种方式更加灵活,因为依赖项可以在对象创建后的任何时间被注入。
    • 优点:灵活性高,可以在对象创建后配置依赖项,支持重新配置已存在的对象。
    • 缺点:依赖项可能在对象生命周期内的任何时间被修改,导致对象状态的不确定性,不支持不可变对象。
@Component
public class ExampleService {
    private AnotherDependency anotherDependency;

    @Autowired
    public void setAnotherDependency(AnotherDependency anotherDependency) {
        this.anotherDependency = anotherDependency;
    }
}
  1. 字段注入:直接在字段上使用@Autowired注解来注入依赖关系。
    • 优点:代码简单明了、侵入性小、易于实现。
    • 缺点:测试难度增加‌、可能产生空指针异常、‌可能导致循环依赖‌。
@Component
public class ExampleService {
    @Autowired
    private AnotherDependency anotherDependency;
}

你可能感兴趣的:(spring,spring,java,后端)