吊打面试官系列:深入理解Spring的IOC容器

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

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的主要代码块如图:

其中①获取扫描包下的所有.classBeanDefinition

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

其中candidateIndicators包括:

再通过②③步骤获取BeanName并注册到org.springframework.beans.factory.support.BeanDefinitionRegistry中,从此完成scan操作。

refresh()Spring的核心,也是Bean初始化流程,而Bean的所有信息,都保留在BeanDefinitionRegistry里面。

05 小结

IOC的思想很简单,但是里面的处理逻辑非常复杂,以兼容各种情况,这就是框架的魅力。里面的refresh()涉及的知识点太多,无法详细说明。如果想了解其详细流程,可以在公众号输入关键字【Spring源码】,获取PDF流程图。下面是一张鸟瞰图:

相关推荐
爱吃牛肉的大老虎4 分钟前
Rust对象之结构体,枚举,特性
开发语言·后端·rust
石榴10 分钟前
NestJS 的请求到底经过了什么:装饰器、守卫、拦截器、管道与中间件如何配合
后端
bbq粉刷匠16 分钟前
HashMap 底层原理深度拆解(二):putVal 完整链路解析(懒加载 · 链表遍历 · 尾插法)
java·开发语言·哈希算法
Gopher_HBo18 分钟前
moby-client客户端
后端
Sam_Deep_Thinking24 分钟前
一个靠谱的C端网关服务应该包含什么内容
java·程序员·系统架构
杨运交42 分钟前
[055][调度模块]Spring动态任务调度框架的设计与实现
java·后端·spring
白狐_7981 小时前
408 数据结构算法题 01:线性表暴力求解保分指南
java·数据结构·算法
流云鹤1 小时前
03Java学习day(3)
java·学习
tqs_123451 小时前
Agent智能体+Skill插件引擎+Milvus混合检索 技术沉淀
java·python·milvus
小码哥哥2 小时前
如何评价“构建企业级 AI 知识库“这一趋势?从技术架构到落地实践的完整分析
人工智能·架构