IOC容器
约 4463 字大约 15 分钟
2025-12-06
Spring IOC容器
基础概述
IOC(Inversion of Control,控制反转)容器是一种核心设计思想的视线,它将对象的创建、依赖注入、生命周期管理等职责交给容器统一管理,而不是由应用代码主动控制。
在Spring框架中,IOC容器通过依赖注入(DI)将所需要的资源传递给对象,实现松耦合和高可测试性。
传统的Java程序中,对象依赖new主动创建,而在IOC模式下,容器根据配置元数据(XML、注解、JavaConfig)解析生成BeanDefinition,并在运行时实例化对象、注入依赖,对象被动接受依赖,这就是“反转”。
IOC容器的类型
Spring主要提供了两类IOC容器接口:
- BeanFactory:基础容器,延迟加载,适合资源受限的场景;
- ApplicationContext:BeanFactory的子接口,预加载单例Bean,支持国际化、事件机制、AOP、注解扫描等企业级特性。
根据单一职责原则,Spring在设计IOC容器的时候,将其划分成了多个类型的BeanFactory。

核心接口层次:
- BeanFactory:最基本的Bean容器接口
- HierarchicalBeanFactory:支持父子容器的BeanFactory
- ListableBeanFactory:支持枚举所有Bean实例的BeanFactory
- AutowireCapableBeanFactory:支持自动装配功能的BeanFactory
配置接口:
- ConfigurableBeanFactory:可以配置的BeanFactory
- ConfigurableListableBeanFactory:综合了ListableBeanFactory和ConfigurabBeanFactory的特点
实现类层次:
- AbstractBeanFactory:BeanFactory抽象的实现
- AbstractAutowireCapableBeanFactory:支持自动装配的抽象实现
- AbstractApplicationContext:应用上下文的抽象实现
总结下来就是BeanFactory是容器本身,仅提供部分从容器中获取Bean,向容器中添加Bean的API接口。ApplicationContext是基于容器出现的一个完善的解决方案,它需要具备BeanFactory的基础特性,还需要能支持配置、注解扫描、事件机制以及AOP等等机制来满足企业级应用开发的需求。
简单的使用方式
早期在使用Spring IOC容器的时候,我们会选择ClassPathXmlApplicationContext或AnnotationConfigApplicationContext这两个类来作为IOC容器。
- ClassPathXmlApplicationContext:加载类路径下XML配置文件的应用程序上下文实现类;
- AnnotationConfigApplicationContext:基于注解配置的应用程序上下文实现类。
ClassPathXmlApplicationContext使用方式:
// 加载单个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 加载多个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"applicationContext.xml", "service-config.xml"}
);AnnotationConfigApplicationContext使用方式:
// 配置类启用组件扫描
@Configuration
@ComponentScan("com.example") // 扫描指定包下的组件
public class AppConfig {
// 可以包含其他 @Bean 方法
}
// 使用包扫描创建上下文
ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
// 或者
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);IOC容器的加载过程
无论是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,它都是AbstractApplicationContext的子类,所以部分的通用的加载过程都是发生在AbstractApplicationContext中的,而AnnotationConfigApplicationContext和ClassPathXmlApplicationContext的区别就是在于获取定义Bean的来源是不同的。
扫描获取Bean的定义
AnnotationConfigApplicationContext与ClassPathXmlApplicationContext的扫描方式是不同的,这里以目前最常用的AnnotationConfigApplicationContext为例子进行分析。
在AnnotationConfigApplicationContext中,通过它的构造方法:
public AnnotationConfigApplicationContext(String... basePackages) {
this();
this.scan(basePackages);
this.refresh();
}可以很清晰的看到,它是通过scan(basePackages)方法来扫描到Bean的定义的。
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan").tag("packages", () -> Arrays.toString(basePackages));
this.scanner.scan(basePackages);
scanPackages.end();
}其中主要的是:this.scanner.scan(basePackages);,这里的scanner属性是在无参的构造函数中初始化的,它的类型为ClassPathBeanDefinitionScanner。
public int scan(String... basePackages) {
/**
* 这里调用的DefaultListableBeanFactory中的getBeanDefinitionCount()
* 在AnnotationConfigApplicationContext中的BeanFactory是DefaultListableBeanFactory
* 所以这里调用的是:DefaultListableBeanFactory#getBeanDefinitionCount()方法
*/
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 扫描包中的组件(@Component、@Service等)
this.doScan(basePackages);
if (this.includeAnnotationConfig) {
// 注册注解处理器,使注解功能生效
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 返回本次扫描期间新增的Bean的个数
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}这个方法是比较关键的,它决定了Spring是否可以正确解析和处理这些注解。
doScan(basePackages)
doScan()方法主要是根据扫描根路径下所有符合注解机制的Bean,并将它们包装成BeanDefinition,它的执行步骤如下:
1️⃣首先会去执行findCandidateComponents()方法
这个componentsIndex是指Bean索引文件,它是一个编译时生成的索引文件(META-INF/spring.components),它包含了应用中Spring组件的信息,用于加速应用启动时组件扫描的过程。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// 如果在componentIndex存在,那么久按照索引文件中的内容扫描对应的Bean,反之久按照根据路径进行扫描;
return this.componentsIndex != null && this.indexSupportsIncludeFilters() ? this.addCandidateComponentsFromIndex(this.componentsIndex, basePackage) : this.scanCandidateComponents(basePackage);
}2️⃣一般而言,我们不会在classpath中放置spring.components文件,所以大部分的场景下都会走到scanCandidateComponents(basePackage)
scanCandidateComponents()方法的目的就是扫描到basePackage路径下所有的class文件,将它们封装成Resource[]数组。然后遍历这个Resource[]数组,判断每一个类是否符合要求(可以被实例化的类,避免接口、普通抽象类等)。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
// 创建一个集合,来存储加载进来的所有的Bean
Set<BeanDefinition> candidates = new LinkedHashSet();
try {
// 将basePackage中的.替换成/,即com.xxx.xxx该成com/xxx/xxx
String var10000 = this.resolveBasePackage(basePackage);
// 这里获得一个扫描通配符的路径:classpath*:com/xxx/xxx/**/*.class 文件
String packageSearchPath = "classpath*:" + var10000 + "/" + this.resourcePattern;
// 通过资源解析器将对应的路径下的class文件转成Resource对象
Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
// 日志相关
boolean traceEnabled = this.logger.isTraceEnabled();
boolean debugEnabled = this.logger.isDebugEnabled();
// 将根路径下得到的Resource文件循环遍历来获取其中的Bean定义信息
for(Resource resource : resources) {
String filename = resource.getFilename();
if (filename == null || !filename.contains("$$")) {
if (traceEnabled) {
this.logger.trace("Scanning " + resource);
}
try {
// 实例化一个元数据读取器,这里的类型是SimpleMetadataReader
MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
// 这个方法比较关键,它是判断当前的类是否被@Component、@Service等注解的标注的类
if (this.isCandidateComponent(metadataReader)) {
// 将当前的类封装成一个BeanDefinition,添加到扫描结果中
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {
if (debugEnabled) {
this.logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
} else if (debugEnabled) {
this.logger.debug("Ignored because not a concrete top-level class: " + resource);
}
} else if (traceEnabled) {
this.logger.trace("Ignored because not matching any filter: " + resource);
}
} catch (FileNotFoundException ex) {
if (traceEnabled) {
this.logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
} catch (ClassFormatException ex) {
if (!shouldIgnoreClassFormatException) {
throw new BeanDefinitionStoreException("Incompatible class format in " + resource + ": set system property 'spring.classformat.ignore' to 'true' if you mean to ignore such files during classpath scanning", ex);
}
if (debugEnabled) {
this.logger.debug("Ignored incompatible class format in " + resource + ": " + ex.getMessage());
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
}
}
}
return candidates;
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
}
/**
* 这个方法的作用是判断当前类是否能添加到IOC容器中
* metadata.isIndependent():检查类是否是独立的(非内部类或静态内部类)
* metadata.isConcrete():检查类是否是一个具体的类(非接口或非抽象类)
* metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()):特殊情况,是抽象类但是被Lookup注解标注了
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent()
&& (metadata.isConcrete() || metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()));
}在Spring中存储在IOC容器中的Bean并不是对应的对象,而是被封装成了BeanDefinition存储到IOC容器中。它包含了对应对象的类信息、以及是否要懒加载等元数据信息。
3️⃣获取到扫描到的所有的BeanDefinition后,它需要遍历每一个BeanDefinition进一步的处理,会再次回到doScan()方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
// 如果有多个扫描的根路径,就循环进行扫描
for(String basePackage : basePackages) {
// 通过findCandidateComponents()方法找到扫描到的Bean,并将他们保证成BeanDefinition
for(BeanDefinition candidate : this.findCandidateComponents(basePackage)) {
// 获取这个Bean的ScopeMetadata属性,并将当前Bean的类型设置为单例 (singleton)
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 为这个Bean生成一个名称,如果注解中包含名称就用注解中的,不包含的话就默认生成一个
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 判断当前Bean是否是AbstractBeanDefinition的子类,如果是的话还需要做一些后置工作来完善Bean的定义
if (candidate instanceof AbstractBeanDefinition abstractBeanDefinition) {
this.postProcessBeanDefinition(abstractBeanDefinition, beanName);
}
// 检查Bean定义是否是AnnotatedBeanDefinition子类,即通过注解方式定义的Bean
if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
// 如果Bean上面还有@Lazy、@Primary、@DependsOn等基础注解,统一在这里进行处理
AnnotationConfigUtils.processCommonDefinitionAnnotations(annotatedBeanDefinition);
}
// 此时BeanDefinition的基本属性已经完成,包含是否懒加载等等信息都已经完整了
// 这里再次检查BeanDefinition是否符合要求(主要解决重复扫描的问题,对于一个类的多次扫描,是否互相兼容)
if (this.checkCandidate(beanName, candidate)) {
// 将这个BeanDefinition包装成BeanDefinitionHolder,它包含BeanDefinition和一些元数据信息
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册这个Bean
this.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}4️⃣当我们扫描到的类符合要求后会被封装成BeanDefinitionHolder对象(BeanDefinition的子类),需要将这个BeanDefinitionHolder对象注册到IOC容器中,所以它最终调用到的使DefaultListableBeanFactory的registerBeanDefinition()方法。
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// 主要是检查校验下BeanDefinition相关信息是否符合要求
if (beanDefinition instanceof AbstractBeanDefinition abd) {
try {
abd.validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex);
}
}
// 从现有的IOC容器中查找这个Bean的BeanDefinition
BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
// 已经加载过了的这个Bean
if (existingDefinition != null) {
// 判断Spring配置中是否开启了allowBeanDefinitionOverriding选项
if (!this.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
// 角色权限检查(避免用户的Bean覆盖掉了框架的Bean)
if (existingDefinition.getRole() < beanDefinition.getRole()) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (!beanDefinition.equals(existingDefinition)) {
// 角色相同,但是定义不同的时候就打印对应的日志
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
} else if (this.logger.isTraceEnabled()) {
// 两个Bean完全相同的时候,就打印相应的日志
this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
}
// 将当前BeanDefinition缓存到beanDefinitionMap中
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
// 判断这个名称是否是这个Bean的别名
if (this.isAlias(beanName)) {
// 将给定的别名解析为最终 Bean 的名称,处理多级别名的情况
String aliasedName = this.canonicalName(beanName);
// 判断Spring配置中是否开启了allowBeanDefinitionOverriding选项
if (!this.isBeanDefinitionOverridable(aliasedName)) {
// 如果这个别名的对应的Bean已经在容器中的,就报错(区分下情况,抛出不同的异常)
if (this.containsBeanDefinition(aliasedName)) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, this.getBeanDefinition(aliasedName));
}
// allowBeanDefinitionOverriding的值为fale,则直接抛出异常
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition for bean '" + beanName + "' since there is already an alias for bean '" + aliasedName + "' bound.");
}
this.removeAlias(beanName); // 移除冲突的别名
}
// 判断当前是否有正在创建的Bean
if (this.hasBeanCreationStarted()) {
// 争抢一下锁,获取到锁后开始处理这个Bean
synchronized(this.beanDefinitionMap) {
// 将这个Bean的定义缓存beanDefinitionMap中
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
// 更新所有BeanDefinition的名称列表
this.beanDefinitionNames = updatedDefinitions;
// manualSingletonNames是记录手动注册的单例 Bean 的名称,这里从这里面清理掉这个bean的名称
this.removeManualSingletonName(beanName);
}
} else {
// 将这个BeanDefinition放入到beanDefinitionMap中
this.beanDefinitionMap.put(beanName, beanDefinition);
// 记录所有的BeanDefinitionNames
this.beanDefinitionNames.add(beanName);
// 从手动注册的单例Bean的Name集合中移除当前Bean的名称
this.removeManualSingletonName(beanName);
}
// 将冻结的BeanN的集合设置为空
this.frozenBeanDefinitionNames = null;
}
// 如果当前Bean是首次扫描的,并且在已经创建的单例Bean中不存在当前Bean,此时主要就是清理下缓存的信息
if (existingDefinition == null && !this.containsSingleton(beanName)) {
// 判断配置是否已经被冻结了,如果是的话就清除掉类型信息的缓存
if (this.isConfigurationFrozen()) {
this.clearByTypeCache();
}
} else {
/**
* 如果当前Bean存在已经加载过的单例实例,那么对于现有的BeanDefinition就需要重新设置
* 重新设置的策略是:删除旧的BeanDefinition,创建新的BeanDefinition
* 在删除旧的BeanDefinition的过程中需要处理原本缓存中旧的BeanDefinition信息
* 如果旧BeanDefinition是否写BeanDefinition的父亲Bean,还需要
* 用新的BeanDefinition来替换旧的BeanDefinition重新建立父子之间的关系
*/
this.resetBeanDefinition(beanName);
}
// 如果当前Bean上还有@Primary注解,将当前名称缓存到primaryBeanNames中
if (beanDefinition.isPrimary()) {
this.primaryBeanNames.add(beanName);
}
}
// Bean 定义的配置一致性,主要检查方法重写与工厂方法配置之间的冲突
public void validate() throws BeanDefinitionValidationException {
/**
* hasMethodOverrides():Bean 定义是否包含方法重写配置(如 lookup-method、replaced-method)
* getFactoryMethodName() != null:是否使用工厂方法创建 Bean
*/
if (this.hasMethodOverrides() && this.getFactoryMethodName() != null) {
throw new BeanDefinitionValidationException("Cannot combine factory method with container-generated method overrides: the factory method must create the concrete bean instance.");
} else {
// hasBeanClass():Bean 的 Class 对象是否已解析(不是延迟加载)
if (this.hasBeanClass()) {
// 验证重写的方法在目标类中是否存在并标记需要重写的方法(为后续的 CGLIB 代理做准备)
this.prepareMethodOverrides();
}
}
}registerBeanDefinition()方法是将扫描的BeanDefinition都放入到IOC容器的beanDefinitionMap中,其中着重处理的是BeanDefinition重复冲突的解决方案。
在GenericApplicationContext类中存在一个setAllowBeanDefinitionOverriding()方法,允许我们配置当前IOC容器是否允许同名Bean的覆盖,如果不允许的话,那么在扫描到有重复的时候都会通过抛出异常。
在allowBeanDefinitionOverriding的属性被设置为true的时候,表示当前IOC容器时允许覆盖同名的BeanDefinition的。在出现同名的BeanDefinition的时候Spring的原则是:删除旧的BeanDefinition,创建新的BeanDefinition,因此在将新的BeanDefinition添加到beanDefinitionMap中的时候,还需要调用resetBeanDefinition()来处理旧的BeanDefinition留下的一些缓存信息。
方法查找
方法查找要解决的问题是:当一个单例Bean依赖于一个原型Bean的时候,因此单例Bean只会被注入一次,因此对于单例Bean中需要注入的原型Bean,就无法保证每次都是全新的对象。
Spring支持通过@LookUp注解来实现方法查找,例如,OrderService(单例Bean)依赖于PaymentProcessor(原型Bean):
@Service
public class OrderServiceImpl implements OrderService {
/**
* 一般而言将一个Bean注入到另外一个Bean的时候,通常使用@Autowire注解来实现
* 但是这个依赖注入只会在初始化的时候发生一次,如果PaymentProcessor是一个原型Bean
* 因为依赖注入只发生一次,而OrderService是单例Bean,所以它依赖的PaymentProcessor仍然是同一个Bean
* 就必须使用@LookUp注解来实现方法查找,让每次得到的PaymentProcessor都是不同的Bean
* @Autowire
* private PaymentProcessor paymentProcessor;
*
*/
// 通过@LookUp注解告诉Spring,这个方法需要重写
@Lookup
public PaymentProcessor getPaymentProcessor() {
// 具体实现不重要,Spring会重写它。方法体通常返回null。
return null;
}
@Override
public void pay() {
PaymentProcessor paymentProcessor = getPaymentProcessor();
// 这里打印的 hashcode值每次都是不同的
System.out.println(paymentProcessor);
}
}在AbstractBeanDefinition中存在一个方法validate()方法,它的作用是验证这个Bean是否需要进行方法替换或方法查找。
public void validate() throws BeanDefinitionValidationException {
// 这一步就是来判断如果这个Bean配置了lookUp属性,有配置了factory-method属性,就会有冲突
// 冲突的原因为:lookUp属性是需要使用CGLIB来重写这个方法的,但是fatory-method是通过静态工厂来生成,两者的逻辑就会产生冲突
if (this.hasMethodOverrides() && this.getFactoryMethodName() != null) {
throw new BeanDefinitionValidationException("Cannot combine factory method with container-generated method overrides: the factory method must create the concrete bean instance.");
} else {
// beanClass有两种:Classs对象或String类型,只有在Class类型的时候,才返回true
/**
* 当beanClass为字符串的时候,说明这个类没有被加载为Class对象,这是因为Spring在扫描的阶段只是读取类的元信息
* 并不加载这个类(延迟加载的机制,避免@Component注解标注了1000个类,但实际只用到10个这种情况)
*/
if (this.hasBeanClass()) {
// 如果被@LookUp注解标注的方法有重载的实现形式,就将BeanDefinition的overload标注为true
this.prepareMethodOverrides();
}
}
}Spring对于被@LookUp标注的方式有一定的优化,当@LookUp标注的方法没有重载形式,那么它会从缓存中直接读取方法的信息。相反地,就只能在运行时动态解析到底要执行哪个方法。如果@LookUp方法没有重载的时候,就不需要每次都去动态解析,可以获得更快的执行速度。
获得扫描到的BeanDefinition流程图

在扫描阶段中关键点在于:
- 基于根路径扫描的时候需要过滤掉不符合条件的Bean信息:接口、普通的抽象类(没有@LookUp注解标注);
- 允许通过autowireCandidatePatterns属性来配置一个通配符,设置哪些Bean可以通过@Autowire注解注入到其它的Bean中,默认为false。但是如果扫描的Bean是AbstractBeanDefinition的子类,这个值为true(表示可以被注入到其它的Bean中);
- 当一个类被多次扫描的时候,只会有一个BeanDefinition;
将扫描到的BeanDefinition注册到IOC容器中流程图

在根据basePackages扫描到对应的BeanDefinition后,就需要将这个BeanDefinition添加到IOC容器内,主要的目标是将BeanDefinition添加到IOC容器beanDefinitionMaps中。
主要解决的是如果对一个Bean扫描了两次,也就是构建了两个相同的BeanDefinition(主要是根据beanName属性来判断的)的解决策略。Spring采取的策略就是将新的BeanDefinition添加到beanDefinitionMap中,然后删除掉旧的BeanDefinition存在的一系列的缓存。
提示
这是比较常用的一种思想,因为对于缓存信息的更新会比删除要复杂很多。删除虽然会导致一次缓存命中失效,但是相比更新的成本还是更低。