谈谈 Spring 的 IOC?

#Java#面试八股

谈谈 Spring 的 IOC?

结论先行:IoC(Inversion of Control,控制反转)是一种设计思想——对象的创建与依赖装配的控制权,从业务代码"反转"给容器;DI(Dependency Injection,依赖注入)是它的主要实现方式。Spring 的 IoC 容器读取配置(注解/XML/Java Config)得到 BeanDefinition,再按需实例化、装配、缓存、销毁。收益是解耦、可替换、可测试、生命周期统一管理;代价是"对象从哪来"变得隐式,排查问题需要理解容器。

一、反转了什么

传统写法(控制权在业务代码):
  OrderService ──new──▶ UserDaoImpl ──new──▶ DataSource
  依赖写死、无法替换、单测要连库

IoC 写法(控制权在容器):
  容器 ──创建──▶ DataSource ──注入──▶ UserDaoImpl ──注入──▶ OrderService
  业务代码只声明"我需要什么",不关心"谁给我、怎么造"

二、容器家族

类型特点何时用
BeanFactory最底层容器,懒加载,只提供 getBean 等基础能力资源极度受限、按需取 Bean
ApplicationContext继承 BeanFactory,启动时预实例化单例,增加事件、国际化、资源加载、AOP 集成绝大多数应用
WebApplicationContext增加 Servlet 上下文与 Web 作用域Web 应用
AnnotationConfigApplicationContext基于注解配置类纯注解项目、单测
GenericApplicationContext可编程式注册 BeanDefinition框架集成、动态注册

三、依赖注入的三种方式

方式写法优点缺点
构造器注入构造器参数 + final 字段依赖不可变、必填、循环依赖即报错、易单测参数多时构造器臃肿
Setter 注入@Autowired 标在 setter可选择性注入、可重配置对象可能处于半初始化状态
字段注入@Autowired 标在字段代码最短无法 final、难单测、隐藏依赖、易滥用

四、代码:同一份依赖的三种写法

@Service
public class OrderService {

    // 推荐:构造器注入(Spring 4.3+ 单构造器可省略 @Autowired)
    private final UserDao userDao;
    private final PayClient payClient;

    public OrderService(UserDao userDao, PayClient payClient) {
        this.userDao = userDao;
        this.payClient = payClient;
    }
}
@Configuration
@ComponentScan("com.demo")
public class AppConfig {

    @Bean
    public PayClient payClient(@Value("${pay.endpoint}") String endpoint) {
        return new PayClient(endpoint);
    }

    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext ctx =
                     new AnnotationConfigApplicationContext(AppConfig.class)) {
            OrderService svc = ctx.getBean(OrderService.class);
            // 容器负责关闭时调用销毁回调
        }
    }
}

五、容器启动时做了什么

AbstractApplicationContext#refresh() 是 IoC 的主线,可概括为:

prepareRefresh          准备上下文、校验必需属性
obtainFreshBeanFactory  创建 DefaultListableBeanFactory
prepareBeanFactory      注册内建依赖(环境、资源解析器、事件发布器)
invokeBeanFactoryPostProcessors   解析配置类、执行 @Configuration 增强、自动配置
registerBeanPostProcessors        注册 BPP(AOP、@Autowired 的处理者)
initMessageSource / initApplicationEventMulticaster
onRefresh                子类扩展:Web 容器在此启动
registerListeners
finishBeanFactoryInitialization   实例化所有非懒加载单例(生命周期主线)
finishRefresh             发布 ContextRefreshedEvent、启动 Lifecycle 组件

六、常见追问

  • 问:IoC 和 DI 是一回事吗? 不是。IoC 是思想(控制权反转),DI 是实现手段;还有依赖查找(Dependency Lookup,主动 getBean)也是 IoC 的一种实现。
  • 问:BeanDefinition 是什么? Bean 的"图纸":类名、作用域、是否懒加载、构造参数、属性值、初始化方法等,容器据此生产实例。
  • 问:为什么 ApplicationContext 启动时就把单例都创建好? 为了尽早暴露配置错误(缺依赖、循环依赖、切点错误),代价是启动变慢;可用 spring.main.lazy-initialization=true 改为懒加载。
  • 问:依赖注入能注入基本类型吗? 用 @Value 注入配置值,或 @ConfigurationProperties 批量绑定,本质是类型转换后的属性填充。

小结

讲 IoC 抓住三句话:控制权反转给容器、DI 是主要实现、BeanDefinition 是中间产物。再补"容器有 BeanFactory 与 ApplicationContext 两档、注入优先构造器、refresh 是启动主线",就是一条完整的答题链。

笔记加载中…