关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言
Spring
提供Java
一站式服务的生态圈,其中最重要是控制反转(IOC
)和面向切面编程(AOP
)这两个特性,更是在面试八股文中屡次被问到。AOP
已经在上一期文章中,通过自定义注解做了介绍。
本次,我们将一起了解经典的面试题:什么是IOC
容器。
02 基本概念
IOC
(inversion of control)就是控制反转的意思。控制反转依然让人一头雾水,到底什么反转了?
在我们没有用到Spring
生态的时候,我们自己需要控制对象的创建和管理。例如当我们需要使用一个对象的时候,我们自己通过new
关键字创建,然后调用其内部的方法。
这不有句调侃程序员的话:程序员不怕没有对象,没事就是new
一个,new
的对象太多了,导致现实的反噬,没有女朋友。哈哈......
IOC
时代的到来,代替了我们对对象的管理。所有对象的创建和管理交给了Spring
容器统一管理,使用的时候通过注入的方式直接获取就好了。这种对对象控制权由开发者转移到Spring
,发生了反转。这就是控制反转。
03 控制反转案例对比
我们通过代码,对比IOC
前后的变化。
3.1 非IOC管理
java
public class UserService {
public void test() {
System.out.println("test 方法执行了...");
}
}
// 方法调用
@Test
void contextLoads() {
// 通过new关键字创建对象
UserService userService = new UserService();
userService.test();
}

对象哪里需要哪里new
,我的对象我做主。
3.2 IOC容器管理
java
@Component
public class UserService {
public void test() {
System.out.println("test 方法执行了...");
}
}
// 方法调用
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.test();
}

其中@Component
注解将UserService
对象的创建和管理交给了Spring
,调用的时候,通过@Autowired
注解将对象注入进来,然后正常调用即可。
除了@Component
注解可以控制反转,还有@Bean
注解。还有一些复合注解@Controller
、@Service
、@Repository
、@RestController
,里面都集成了@Component
。但是使用的位置都不同。
3.3 对比
非IOC
管理的对象,使用更加灵活,不受框架的限制,可以用在任何地方。而IOC管理就要依赖Spring
框架。
非IOC
管理的对象,使用的越多,创建的对象就越多,因此占用的资源也就会越多,如果循环创建对象,可能造成内存溢出。虽然可以通过单例的方式解决,但是需要为每一个对象,设置单例。这时候IOC
容器的好处就来了。默认就是单例模式,容器只会创建一次对象。
对比非空参构造的对象,如果发生参数的修改,对于非IOC
管理的对象来说似灾难,每一处用到的地方都要手动修改。而IOC
管理的对象,只需要修改实例化的地方即可。
04 IOC实现原理
4.1 容器测试
java
@Test
void test() {
ApplicationContext context = new AnnotationConfigApplicationContext("com.simonking.boot.aop");
UserService contextBean = context.getBean(UserService.class);
contextBean.test();
}
ApplicationContext
就是我们所说的Spring
容器,他的主要实现有两个:
AnnotationConfigApplicationContext
:解析注解XmlWebApplicationContext
:解析Xml
案例中采用注解的方式,扫描com.simonking.boot.aop
,所有相关的注解@Component
、@Bean
,并加载到容器中。
从容器中通过getBean()
的方式获取容器中的Bean
对象。然后进行业务操作。
4.2 源码脉络
我们以AnnotationConfigApplicationContext
为例追踪。

上面的脉络图是执行的关键代码,简单总结一下总共有三步:
- 通过
AnnotationConfigApplicationContext
的构造函数,读取扫描路径的所有.class
文件。 - 将
.class
通过ScannedGenericBeanDefinition
读取BeanDefinition
信息,并交给Spring
容器。 Spring
容器通过getBean
的方式或者注入的方式,获取容器中的Bean
对象。
4.3 源码追踪
AnnotationConfigApplicationContext
构造函数:
java
public AnnotationConfigApplicationContext(String... basePackages) {
this();
// 扫描包路径
scan(basePackages);
// 启动Spring容器
refresh();
}
scan
的主要代码块如图:

其中①获取扫描包下的所有.class
的BeanDefinition
。

并通过ConfigurationClassUtils.isConfigurationCandidate
方法筛选目标候选的BeanDefinition
.

其中candidateIndicators
包括:

再通过②③步骤获取BeanName
并注册到org.springframework.beans.factory.support.BeanDefinitionRegistry
中,从此完成scan
操作。
refresh()
是Spring
的核心,也是Bean
初始化流程,而Bean
的所有信息,都保留在BeanDefinitionRegistry
里面。
05 小结
IOC
的思想很简单,但是里面的处理逻辑非常复杂,以兼容各种情况,这就是框架的魅力。里面的refresh()
涉及的知识点太多,无法详细说明。如果想了解其详细流程,可以在公众号输入关键字【Spring源码】,获取PDF流程图。下面是一张鸟瞰图:
