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

对象哪里需要哪里new,我的对象我做主。
3.2 IOC容器管理
@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 容器测试
@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构造函数:
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流程图。下面是一张鸟瞰图:

492

被折叠的 条评论
为什么被折叠?



