Spring(一):IoC/DI 与容器体系
Spring(一):IoC/DI 与容器体系
导语:Spring 面试的第一层是「容器是怎么起来的、Bean 是从哪来的」。本篇把 IoC/DI 的本质、
refresh()主线、BeanDefinition与两大容器的关系讲透,再补上注解驱动的底层机制——@ComponentScan、@Configuration的 full/lite 模式、@Import家族、@Conditional,最后收口到容器的扩展点全景(BFPP/BPP/Aware/事件/Environment)与设计模式,共 15 题。
一、IoC 与 DI
1. 什么是 IoC(控制反转)和 DI(依赖注入)?二者什么关系?
答:
- IoC(Inversion of Control):一种设计原则——把对象的创建、依赖装配、生命周期管理的控制权,从业务代码反转给容器。传统写法由开发者
new对象并自己管理依赖,Spring 中由容器负责"生产 + 装配"。 - DI(Dependency Injection):实现 IoC 的具体手段——容器在运行时把依赖(构造器、setter、字段)注入到对象中,对象自己不负责查找或创建依赖。
- 关系:IoC 是目标/思想,DI 是落地方式。除了 DI,还有依赖查找(DL)(如
beanFactory.getBean()、JNDI),但 Spring 以 DI 为主。
易错点:IoC 不是某项具体技术,而是一种"把控制权交给容器"的设计原则;DI 只是它的其中一种实现方式。
2. 为什么说 IoC 降低了耦合、提高了可维护性?
答:
- 面向接口 + 依赖倒置:业务类只依赖接口,不再硬编码具体实现类。
- 装配与实现解耦:换实现只需改配置/注解,不改调用方源码(符合开闭原则)。
- 统一的生命周期管理:容器统一创建、缓存、销毁,便于做 AOP 增强、事务、资源释放等横切处理。
- 可测试性:依赖可以注入 Mock/Stub,单元测试不再被
new死绑。
一句话:IoC 让"谁创建谁管理"这件事从业务代码里消失了——业务只关心"我要什么",不关心"它从哪来、怎么造、何时死"。
3. Spring IoC 容器的初始化流程(refresh() 主线)是怎样的?
答: 以 AbstractApplicationContext.refresh() 为主线,共十二步(面试记住关键的即可):
| 步骤 | 做什么 |
|---|---|
1. prepareRefresh() | 准备工作:记录启动时间、初始化 PropertySource、校验必需属性 |
2. obtainFreshBeanFactory() | 创建/刷新 BeanFactory,完成 BeanDefinition 的定位、加载、注册(Resource → BeanDefinition → beanDefinitionMap) |
3. prepareBeanFactory() | 配置类加载器、SpEL 解析器、PropertyEditor,注册内置依赖(BeanPostProcessor 等) |
4. postProcessBeanFactory() | 留给子类扩展(Web 容器在此注册 ServletContext 相关 Bean) |
5. invokeBeanFactoryPostProcessors() | 执行 BeanFactoryPostProcessor,可修改 BeanDefinition(ConfigurationClassPostProcessor 在此解析 @Configuration/@ComponentScan/@Import,即自动配置的解析入口) |
6. registerBeanPostProcessors() | 注册(不是执行)BeanPostProcessor |
7. initMessageSource() | 初始化国际化组件 |
8. initApplicationEventMulticaster() | 初始化事件广播器 |
9. onRefresh() | 子类钩子,Web 容器在此创建并启动内嵌 Tomcat |
10. registerListeners() | 注册事件监听器 |
11. finishBeanFactoryInitialization() | 实例化所有非懒加载单例 Bean(真正触发依赖注入与 AOP) |
12. finishRefresh() | 清除资源缓存、发布 ContextRefreshedEvent、启动 LifecycleProcessor |
关键点:BeanDefinition 的"注册"在第 2、5 步完成,而单例 Bean 的"实例化 + 注入"发生在第 11 步;AOP 代理的生成发生在 Bean 初始化之后的
BeanPostProcessor阶段(见《Spring(二)》)。
二、容器体系与 BeanDefinition
4. BeanFactory 和 ApplicationContext 有什么区别?
答:
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | 最基础的 IoC 容器接口 | BeanFactory 的子接口,功能更全 |
| 实例化时机 | 按需(getBean() 时)懒加载 | 启动时预实例化所有非懒加载单例 |
| 国际化 | 不支持 | MessageSource |
| 事件机制 | 不支持 | ApplicationEventPublisher |
| 资源加载 | 不支持 | ResourceLoader、ResourcePatternResolver |
| 环境抽象 | 不支持 | Environment(profile + PropertySource) |
| 自动注册 | 无 | 自动注册 BeanPostProcessor / BeanFactoryPostProcessor |
| 使用场景 | 资源受限、需极致懒加载 | 实际开发几乎都用它 |
实现上:
ApplicationContext内部委托一个DefaultListableBeanFactory来完成 Bean 的注册与创建(组合 + 委托,不是继承实现)。
5. 常见的 ApplicationContext 实现类有哪些?
答:
ClassPathXmlApplicationContext/FileSystemXmlApplicationContext:XML 配置(老项目)。AnnotationConfigApplicationContext:注解/JavaConfig,最常用。XmlWebApplicationContext/GenericWebApplicationContext:Web 环境。- Spring Boot 中默认创建
AnnotationConfigServletWebServerApplicationContext(响应式则为AnnotationConfigReactiveWebServerApplicationContext)。
6. 什么是 BeanDefinition?它在容器中如何存储?
答: BeanDefinition 是 Spring 对 Bean 元数据的内部抽象,包含:类名、作用域、是否懒加载、是否单例、构造参数、属性值、初始化/销毁方法、是否自动装配、是否为 @Primary 等信息。
- 解析阶段把 XML/注解/JavaConfig 统一转换成
BeanDefinition; - 注册到
DefaultListableBeanFactory.beanDefinitionMap(ConcurrentHashMap<String, BeanDefinition>)与beanDefinitionNames(保持注册顺序的 List); - 实例化阶段由
getBean()读取它来创建对象。
常见实现类:
RootBeanDefinition、GenericBeanDefinition、ScannedGenericBeanDefinition(注解扫描产物)、AnnotatedGenericBeanDefinition。
7. BeanFactory 和 FactoryBean 有什么区别?
答: 二者只是名字相似,本质完全不同:
| BeanFactory | FactoryBean | |
|---|---|---|
| 定位 | 容器本身(如 DefaultListableBeanFactory) | 一个特殊的 Bean,负责"生产对象" |
| 接口 | getBean() 等容器能力 | getObject() / getObjectType() / isSingleton() |
| 取值 | getBean("a") 返回 Bean | getBean("a") 返回 getObject() 的产物;要拿 FactoryBean 本身需加 &:getBean("&a") |
public interface FactoryBean<T> {
T getObject() throws Exception; // 返回由它创建的对象
Class<?> getObjectType();
boolean isSingleton();
}典型应用:
SqlSessionFactoryBean(MyBatis)、RocketMQ的DefaultMQProducer包装、TransactionProxyFactoryBean。用途:当对象创建过程很复杂(需要一堆前置步骤)时,把它封装进 FactoryBean。
8. @ComponentScan 与 @Autowired 是怎么工作的?@Component/@Service/@Controller/@Repository 有何区别?
答:
注解驱动的工作链:
ConfigurationClassPostProcessor(一个BeanFactoryPostProcessor)解析@Configuration、@ComponentScan、@Import;@ComponentScan按包路径扫描,把带@Component(及其派生注解)的类封装成ScannedGenericBeanDefinition注册进容器(可配合Filter精确控制);AutowiredAnnotationBeanPostProcessor在 Bean 实例化后解析@Autowired/@Value完成注入;CommonAnnotationBeanPostProcessor处理@PostConstruct/@PreDestroy/@Resource。
四个派生注解的区别:
| 注解 | 语义 | 特殊能力 |
|---|---|---|
@Component | 通用组件 | 无 |
@Service | 业务层 | 仅语义标识(早期有 AOP 特殊处理,现已无) |
@Controller | 控制层 | 与 Spring MVC 请求映射关联,DispatcherServlet 只认它(及其派生 @RestController) |
@Repository | 持久层 | 额外具备平台异常转换能力(PersistenceExceptionTranslationPostProcessor 把 SQLException 等转成 Spring 统一 DataAccessException) |
加分点:
@RestController=@Controller+@ResponseBody,因此返回值不再走视图解析,直接序列化为 JSON。
9. @Configuration 的 full 模式与 lite 模式有什么区别?(proxyBeanMethods)
答: 这是 @Configuration 一个高频且容易踩坑的点:
| 模式 | 触发条件 | 是否被 CGLIB 增强 | @Bean 方法互相调用 |
|---|---|---|---|
| Full(默认) | @Configuration(proxyBeanMethods = true) | 是(生成 CGLIB 子类) | 走代理 → 返回容器中的同一个单例 |
| Lite | proxyBeanMethods = false,或类上只有 @Component | 否 | 直接 Java 方法调用 → 每次 new 一个新对象 |
@Configuration // full:AppConfig 被 CGLIB 增强
public class AppConfig {
@Bean public A a() { return new A(b()); } // b() 被拦截,返回容器的单例 B
@Bean public B b() { return new B(); }
}
@Configuration(proxyBeanMethods = false) // lite:b() 就是普通方法调用,会 new 出第二个 B如何选择:
- 需要保证
@Bean方法间引用拿到同一个单例 → 用默认的 full; - 只想"注册 Bean"、不关心方法间调用语义(或减少启动期 CGLIB 开销)→ 用
proxyBeanMethods = false(Spring Boot 2.x 起大量自动配置类都改为 lite 模式以加快启动)。
原理:full 模式下,
ConfigurationClassPostProcessor会对配置类做 CGLIB 增强,@Bean方法被BeanMethodInterceptor拦截——若容器中已有该 Bean,直接返回容器实例,否则执行方法并注册。
10. @Import 有哪几种用法?
答: @Import 是把类/配置"导入"容器注册的核心手段,有三种形态:
| 用法 | 写法 | 特点 |
|---|---|---|
| 直接导入类 | @Import({A.class, B.class}) | 把普通类注册为 Bean(即使没有 @Component) |
ImportSelector | 实现 selectImports() 返回类名数组 | 按条件批量导入;DeferredImportSelector 会延迟到最后执行(自动配置就用它,保证用户配置优先) |
ImportBeanDefinitionRegistrar | 实现 registerBeanDefinitions() | 能直接操作 BeanDefinition,最灵活(MyBatis 的 @MapperScan 就是它) |
Why it matters:
@EnableXxx系列注解几乎都是@Import的组合封装——理解@Import,就能理解 Spring Boot 自动装配与各类@Enable注解的本质。
11. @Conditional 条件注解的原理是什么?
答: @Conditional 通过一组 Condition 决定"某个 BeanDefinition 是否注册":
@FunctionalInterface
public interface Condition {
// 返回 true 才注册;可读取 context(容器元数据) 与 metadata(注解元信息)
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}- 执行时机:
ConfigurationClassPostProcessor解析配置类时(conditionEvaluator.shouldSkip),因此条件不满足的类连 BeanDefinition 都不会注册。 - Spring Boot 的封装:
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty、@ConditionalOnBean等。 - 实现细节:
@ConditionalOnClass用OnClassCondition,通过 ASM 读取注解元数据(MetadataReader)判断类是否存在,而不会真正加载类——这是自动配置能在"类缺失"时不报错的关键。
与
@Profile的关系:@Profile本质也是@Conditional(ProfileCondition.class),只是在匹配 profile。
三、扩展点与解耦机制
12. Spring 容器的扩展点全景是怎样的?
答: 按执行时机串成一条线(面试常用来考察"你改过哪些扩展点"):
| 时机 | 扩展点 | 典型用途 |
|---|---|---|
| BeanDefinition 注册后、实例化前 | BeanFactoryPostProcessor | 修改 BeanDefinition、占位符替换、自动配置解析 |
| Bean 实例化前 | InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation | 提前返回代理对象(短路创建) |
| 属性填充时 | InstantiationAwareBeanPostProcessor#postProcessProperties | @Autowired/@Resource 注入 |
| 初始化前后 | BeanPostProcessor#postProcessBefore/AfterInitialization | AOP 代理生成、注解处理、指标埋点 |
| 初始化内部 | Aware 回调 → @PostConstruct → InitializingBean → init-method | 资源初始化 |
| 所有单例创建完 | SmartInitializingSingleton#afterSingletonsInstantiated | 依赖全部就绪后的收尾(如预热) |
| 容器启动完成 | ApplicationListener<ContextRefreshedEvent>、ApplicationRunner、CommandLineRunner | 启动后任务 |
| 容器关闭 | DisposableBean、@PreDestroy、destroy-method、SmartLifecycle#stop | 资源释放、优雅停机 |
记忆口诀:"BFPP 改定义、BPP 改实例、Aware 拿容器、事件做解耦"。
13. Spring 的事件机制是怎么实现的?如何自定义事件?
答: 基于观察者模式(ApplicationEventPublisher + ApplicationEventMulticaster + ApplicationListener):
// 定义事件
public class OrderCreatedEvent extends ApplicationEvent { ... }
// 发布
publisher.publishEvent(new OrderCreatedEvent(order));
// 订阅:注解式(推荐)
@EventListener
public void onCreated(OrderCreatedEvent e) { ... }
// 订阅:事务感知(事务提交后才执行,避免"事务回滚了但消息已发")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderCreatedEvent e) { ... }要点:
- 默认同步执行,异常会影响发布方;需要异步可加
@Async(记得配线程池,见《高性能(二)》)。 - 容器内置事件:
ContextRefreshedEvent、ContextClosedEvent、ApplicationStartedEvent/ApplicationReadyEvent(Boot)。 - 可自定义
ApplicationEventMulticaster实现异步/广播策略。
用途:模块解耦(下单成功后由监听器分别处理通知、积分、索引),比直接调用更松耦合,但要注意"事件是进程内的"——跨服务仍要用 MQ。
14. Spring 的 Environment 抽象解决了什么问题?配置优先级如何?
答:
Environment = Profiles(环境标识) + PropertyResolver(属性解析),把"配置从哪来"与"代码怎么读"解耦。
- Profiles:
spring.profiles.active激活一组 profile,配合@Profile决定哪些 Bean 注册、哪些配置文件生效。 - PropertySource:抽象各种配置来源(系统属性、环境变量、
application.yml、命令行参数、@PropertySource),按优先级形成一个有序列表,getProperty()时先查优先级高的。 - 占位符:
${...}由PropertySourcesPlaceholderConfigurer解析(配合@Value、@ConfigurationProperties)。
结论:"配置来源"可插拔、
profile可切换、优先级可覆盖——这就是多环境部署与外部化配置的基础(Boot 的配置加载顺序见《SpringBoot(三)》)。
15. Spring 中用到了哪些设计模式?
答: 这是"框架理解"的经典综合题,按模块归类回答最有条理:
| 模式 | Spring 中的体现 |
|---|---|
| 工厂 | BeanFactory/ApplicationContext 生产 Bean;FactoryBean 定制复杂对象创建 |
| 单例 | Bean 默认单例,由 SingletonBeanRegistry(三级缓存)保证容器内唯一 |
| 代理 | AOP 用 JDK 动态代理 / CGLIB 生成增强对象 |
| 模板方法 | AbstractApplicationContext.refresh()、JdbcTemplate、RestTemplate(父类定骨架、子类实现钩子) |
| 策略 | HandlerMapping/HandlerAdapter、InstantiationStrategy、资源加载策略、事务传播/隔离策略 |
| 适配器 | HandlerAdapter 适配各类 Controller;AdvisorAdapter 适配不同 Advice |
| 观察者 | ApplicationEvent + ApplicationListener 事件机制 |
| 装饰器 | BeanWrapper、HttpServletRequestWrapper、AOP 的 ProxyFactory 组合 |
| 建造者 | BeanDefinitionBuilder、SpringApplicationBuilder、UriComponentsBuilder |
| 委派/组合 | ApplicationContext 委托 DefaultListableBeanFactory;DelegatingFilterProxy |
答题建议:不要只罗列模式名,每个模式配一个具体的 Spring 类,并说明"为什么这里用它",这比背清单更能体现理解。
本章小结:IoC 是思想、DI 是手段,
refresh()是容器启动的主线,BeanDefinition是"Bean 的图纸",BeanFactory是"生产车间";而注解驱动的本质是ConfigurationClassPostProcessor(BFPP)+AutowiredAnnotationBeanPostProcessor(BPP) 这两类扩展点在干活。
