一、IoC简介
IoC(Inversion of Control,控制反转)是Spring框架的核心部分,是一个设计模式,它允许将对象的创建和依赖关系的管理从应用程序代码中分离出来,交给外部容器处理。这种模式的核心思想是“控制”的反转,即由传统的由对象自己创建依赖转变为由外部容器在运行时注入依赖。
二、IoC容器简介
IoC容器是Spring框架中实现IoC概念的核心组件,主要负责实例化、配置和组装应用程序中的对象,并管理它们的生命周期。容器使用描述性配置(如XML配置文件、注解或Java配置类)来创建对象,并自动装配这些对象的依赖关系。从而使得开发者能够更专注于业务逻辑的实现,不需要在代码中编写繁琐的工厂方法或使用new关键字来手动创建对象及其依赖。
三、IoC容器的主要职责
Spring IoC容器的主要职责包括:
- 对象实例化:根据描述性配置创建对象的实例。
- 对象生命周期管理:IoC容器可以控制对象的创建、初始化、销毁等生命周期事件。
- 自动装配:容器可以根据类型、名称或注解自动检测和装配对象的依赖关系。
- AOP支持:容器集成了面向切面编程的功能,允许在不修改原有业务逻辑的情况下增加横切关注点。
- 声明式事务管理:容器提供了声明式事务管理,使得事务控制可以通过简单的配置来实现。
- 资源加载:容器可以帮助加载和管理外部资源,如数据库连接池、消息队列等。
四、IoC容器的工作流程
IoC容器的工作流程通常包括以下几个步骤:
- 定义Bean:在Spring配置文件或注解中定义应用程序需要的各种Bean,每个Bean对应应用程序中的一个对象。
- 注册Bean:将定义的Bean告诉Spring容器,这样容器就知道如何创建和管理这些Bean。
- Bean的实例化:根据Bean的定义,容器负责创建对象实例。这可以通过构造函数、静态工厂方法或实例工厂方法来实现。
- 依赖注入:容器在创建Bean的实例后,会自动将其他Bean注入到当前Bean中,满足其属性或方法的依赖。这一过程称为依赖注入(DI,Dependency Injection)。
- 配置Bean的生命周期:Spring容器允许开发者自定义Bean的初始化和销毁方法。可以通过实现InitializingBean接口或使用@PostConstruct注解指定初始化后的操作;通过实现DisposableBean接口或使用@PreDestroy注解指定销毁前的操作。
- 管理Bean的作用域:Spring容器支持多种Bean作用域,如单例(singleton)、原型(prototype)、请求(request)、会话(session)等,开发者可以根据实际需求选择合适的作用域。
五、IoC容器创建对象实现方式
在Spring框架中,IoC容器可以通过XML配置文件、注解或Java配置类来创建对象。下面分别介绍下这三种方式的实现样例。
- 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。
- 注解配置
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。
- 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的依赖注入。
六、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,依赖注入)是一种设计模式,它允许将对象的依赖关系从对象内部转移到外部配置。这种方式降低了类之间的耦合度,提高了代码的模块化,使得单元测试更加容易进行。同时,Spring的依赖注入是实现控制反转(IoC)原则的关键机制。
八、依赖注入的优点
- 解耦:组件之间的依赖关系不再是硬编码的,而是由容器注入,降低了模块间的耦合度。
- 可测试性:由于依赖关系可以被注入,因此可以轻松地用模拟对象替换真实的依赖,便于单元测试。
- 灵活性和可维护性:可以在不同的上下文中重用组件,只需改变依赖关系即可,不需要修改组件内部的代码。
九、依赖注入实现方式
Spring支持三种主要的依赖注入方式:构造器注入、Setter注入和字段注入。
- 构造器注入:通过构造器将依赖关系传递给组件。这种方式适用于依赖不可变的对象,或者需要确保依赖项在使用前已经被初始化的场景。
@Component
public class ExampleService {
private final AnotherDependency anotherDependency;
@Autowired
public ExampleService(AnotherDependency anotherDependency) {
this.anotherDependency = anotherDependency;
}
}
- Setter注入:通过Setter方法将依赖关系传递给组件。这种方式提供了更大的灵活性,允许在对象创建后更改依赖项。
@Component
public class ExampleService {
private AnotherDependency anotherDependency;
@Autowired
public void setAnotherDependency(AnotherDependency anotherDependency) {
this.anotherDependency = anotherDependency;
}
}
- 字段注入:直接在字段上使用@Autowired注解来注入依赖关系。虽然这种方式在使用上最为简单,但它通常不被推荐,因为它违反了封装原则,并且使得单元测试更加困难。
@Component
public class ExampleService {
@Autowired
private AnotherDependency anotherDependency;
}
Spring容器在启动时会扫描带有@Component、@Service、@Repository和@Controller等注解的类,并自动完成依赖注入。如果需要解决同一接口的多个实现时的歧义问题,可以使用@Qualifier注解指定具体的Bean名称。