那些年你背过的题目:Spring IOC 及依赖反转

265 阅读7分钟

Spring IOC(控制反转)和依赖反转(Dependency Inversion)是理解Spring框架的核心概念。

1. 控制反转(IOC)

控制反转,简称IOC(Inversion of Control),是一种设计原则,用于将对象创建、配置和管理的控制权从应用程序代码中移交给容器。在Spring框架中,IOC是通过依赖注入(Dependency Injection,DI)来实现的。

1.1 IOC 的工作原理:

  • 容器:Spring 提供了一个IOC容器,负责管理Java对象的生命周期及其相互依赖关系。
  • Bean:在Spring中受IOC容器管理的对象称为Bean。Beans定义在容器配置文件(如XML)或注解中。
  • 配置文件:容器使用配置文件(XML或基于注解的Java配置类)来描述Beans及其依赖关系。
  • 依赖注入:容器在创建Bean时,将Bean所需的依赖自动注入到它们中。

1.2 IOC 容器类型:

  • BeanFactory:基本的IOC容器,提供基础的依赖注入功能。
  • ApplicationContext:BeanFactory的子接口,提供更高级的特性,如事件机制、国际化支持等。

1.3 IOC 容器的使用:

// 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="myService" class="com.example.MyService"/>
</beans>

// Java 注解示例
@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyService();
    }
}

2. 依赖反转(Dependency Inversion)

依赖反转原则(Dependency Inversion Principle, DIP)是SOLID面向对象设计原则之一。它强调高层模块不应该依赖于低层模块,两者都应该依赖于抽象。通俗地讲,就是要依赖接口或抽象类,而不是具体的实现类。

2.1 依赖反转的核心思想

  • 高层模块不应该依赖低层模块。两者都应该依赖抽象。
  • 抽象不应该依赖细节。细节应该依赖抽象。

通过依赖反转,可以实现更松耦合的代码结构,提高系统的可维护性和扩展性。

2.2 依赖反转与Spring IOC的关系

Spring IOC通过依赖注入来实现依赖反转。具体来说,Spring会在运行时将具体的实现类注入到需要依赖的地方,从而实现模块之间的解耦。例如:

// 接口定义
public interface Service {
    void execute();
}

// 接口实现
public class ServiceImpl implements Service {
    @Override
    public void execute() {
        System.out.println("Service Executed");
    }
}

// 使用依赖注入的类
public class Client {
    private Service service;

    // 通过构造函数进行依赖注入
    public Client(Service service) {
        this.service = service;
    }

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

// Spring 配置
@Configuration
public class AppConfig {
    @Bean
    public Service service() {
        return new ServiceImpl();
    }

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

在上述示例中,Client类依赖于Service接口,而不是ServiceImpl的具体实现。通过Spring IOC容器,ServiceImpl的实例被注入到Client中,实现了依赖反转。

总结

  • Spring IOC:通过依赖注入实现控制反转,将对象创建、配置和管理的责任交给Spring容器。
  • 依赖反转原则(DIP) :强调模块之间应依赖抽象而非具体实现,Spring通过IOC和依赖注入实现了这一设计原则。

IOC容器实现原理

Spring的IOC(Inversion of Control,控制反转)容器是Spring框架的核心组件之一,它管理应用程序中对象的生命周期和依赖关系。下面详细介绍IOC容器的实现原理:

IOC容器的基本概念

  • Bean:在Spring中受IOC容器管理的对象称为Bean。
  • 配置元数据:描述如何实例化、配置和组装Beans的信息,可以通过XML文件、注解或Java配置类来提供。
  • 依赖注入(DI) :IOC容器根据配置元数据将Bean的依赖关系自动注入到它们中。

IOC容器的主要实现类

Spring框架提供了多种IOC容器实现类,其中最常用的是BeanFactoryApplicationContext接口。ApplicationContextBeanFactory的子接口,提供了更多高级功能。

1. BeanFactory

BeanFactory 是 Spring 框架最基本的 IOC 容器,提供基本的 DI 功能。

2. ApplicationContext

ApplicationContext 接口扩展了 BeanFactory,提供更丰富的功能,例如事件机制、国际化支持等。常见的实现类有:

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext

IOC容器的工作流程

  1. 读取配置元数据

    • Spring IOC容器从XML文件、注解或Java配置类中读取Bean定义信息。这些信息包括Bean的类、作用域、依赖关系等。
  2. 解析并注册Bean定义

    • 容器解析配置元数据,将Bean定义信息存储在一个内部的数据结构中,通常是一个BeanDefinition对象。
  3. 实例化Bean

    • 当容器启动或第一次请求某个Bean时,IOC容器会根据BeanDefinition实例化该Bean。Spring使用Java反射机制创建Bean实例。
  4. 依赖注入

    • 在实例化Bean之后,IOC容器会自动注入该Bean所依赖的其他Beans。这可以通过构造方法、Setter方法或字段注入(基于注解)来实现。
  5. 初始化Bean

    • 如果Bean实现了InitializingBean接口或定义了自定义的初始化方法,容器会调用相应的方法进行初始化。
  6. 管理Bean的生命周期

    • IOC容器还负责管理Bean的生命周期,包括销毁阶段。如果Bean实现了DisposableBean接口或定义了自定义的销毁方法,容器会在销毁Bean之前调用相应的方法。

具体示例:XML配置

以下演示Spring IOC容器的工作流程:

step 1: 定义Bean类

public class MyService {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public void printMessage() {
        System.out.println("Message: " + message);
    }
}

step 2: 配置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="myService" class="com.example.MyService">
        <property name="message" value="Hello, Spring!"/>
    </bean>
</beans>

step 3: 使用IOC容器

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        MyService myService = (MyService) context.getBean("myService");
        myService.printMessage();
    }
}

具体示例:注解和Java配置

step 1: 定义Bean类和配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        MyService service = new MyService();
        service.setMessage("Hello, Spring with Java Config!");
        return service;
    }
}

step 2: 使用IOC容器

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService myService = context.getBean(MyService.class);
        myService.printMessage();
    }
}

在上述Java配置示例中,我们使用注解和Java配置类来定义和管理Bean。AppConfig类使用@Configuration注解标注,并且使用@Bean注解定义了一个名为myService的方法,该方法返回一个MyService实例。然后在MainApp类中,我们通过AnnotationConfigApplicationContext加载这个配置类并获取Bean实例。

IOC容器实现原理深入解析

以下是一些关键组件和流程:

1. BeanDefinition接口

BeanDefinition是Spring内部用来描述Bean元数据的接口,包括Bean的类名、作用域、是否懒加载、构造函数参数、属性依赖等信息。在读取配置文件或注解时,Spring会将这些信息封装到BeanDefinition对象中。

2. BeanFactoryApplicationContext

  • BeanFactory:最基本的IOC容器接口,提供核心的依赖注入功能。
  • ApplicationContext:扩展自BeanFactory,增加了更多高级功能,如国际化支持、事件传播、统一资源加载等。

3. Bean的创建过程

  • 实例化:使用反射机制根据BeanDefinition创建Bean实例。
  • 属性填充:在实例化之后,Spring会根据BeanDefinition中的信息进行依赖注入,即填充Bean的属性。
    • 构造器注入:通过解析BeanDefinition中的构造函数参数,使用反射调用构造函数进行实例化。
    • Setter方法注入:通过反射调用对应的Setter方法进行属性注入。
    • 字段注入:直接使用反射为字段赋值。
  • 初始化:如果Bean实现了InitializingBean接口或者定义了自定义的初始化方法(通过init-method指定),则调用相应的初始化方法。
  • 将Bean放入单例缓存池:对于单例Bean,将实例化后的Bean放入单例缓存池中,以供后续使用。
  • 销毁:当容器关闭时,如果Bean实现了DisposableBean接口或者定义了自定义的销毁方法(通过destroy-method指定),则调用相应的销毁方法。

4. 依赖注入方式

  • 构造器注入:通过Bean的构造函数进行依赖注入。
  • Setter注入:通过Setter方法进行依赖注入。
  • 字段注入:通过注解(如@Autowired)直接注入到字段中。