1. Spring面试高频三连问深度解析
作为Java开发者面试的必考知识点,Spring框架的核心概念经常让候选人又爱又恨。今天我们就来彻底拆解这三个经典问题:容器区别、Bean作用域和生命周期。这些内容看似基础,但能准确回答的候选人往往不足三成。
我在技术面试中担任主考官多年,发现大多数面试者对这些概念的理解停留在表面。本文将用实际代码示例+场景化解读的方式,带你掌握这些核心知识点。无论你是准备面试还是想夯实基础,这篇内容都能让你获得超出预期的收获。
2. 容器区别:ApplicationContext vs BeanFactory
2.1 核心差异对比
Spring框架中两大核心容器各有特点:
java复制// BeanFactory基础用法示例
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean bean = (MyBean) factory.getBean("myBean");
// ApplicationContext典型用法
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyBean bean = context.getBean(MyBean.class);
两者主要区别体现在六个方面:
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 初始化时机 | 懒加载 | 启动时预加载所有单例Bean |
| AOP支持 | 需手动配置 | 内置支持 |
| 国际化 | 不支持 | 支持 |
| 事件发布 | 不支持 | 支持 |
| 资源访问 | 基础支持 | 增强支持(如classpath前缀) |
| 企业级功能 | 无 | 集成JMS、邮件等 |
实际开发中ApplicationContext是绝对首选,只有在资源极度受限的环境才会考虑BeanFactory
2.2 底层实现原理
ApplicationContext继承自BeanFactory接口,通过组合模式扩展功能。关键实现类包括:
- AnnotationConfigApplicationContext:基于注解配置
- ClassPathXmlApplicationContext:基于XML配置
- FileSystemXmlApplicationContext:文件系统路径配置
java复制// 典型初始化过程
public void refresh() throws BeansException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新上下文
prepareRefresh();
// 2. 初始化BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 配置标准BeanFactory特性
prepareBeanFactory(beanFactory);
// ... 其他初始化步骤
}
}
3. Bean作用域详解
3.1 六种标准作用域
Spring支持的作用域类型及适用场景:
-
singleton(默认)
- 容器中唯一实例
- 适合无状态服务类
java复制@Scope("singleton") public class OrderService {} -
prototype
- 每次获取新实例
- 适合有状态的Bean
java复制@Scope("prototype") public class ShoppingCart {} -
request
- 单个HTTP请求生命周期
- Web应用专用
xml复制<bean id="loginAction" class="com.LoginAction" scope="request"/> -
session
- 用户会话周期
- 存储用户特定数据
java复制@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public class UserPreferences {} -
application
- ServletContext生命周期
- 全局共享数据
-
websocket
- WebSocket会话周期
- 实时通信场景
3.2 作用域代理机制
当短生命周期Bean注入长生命周期Bean时,需要使用代理:
java复制@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloService requestScopedBean() {
return new HelloServiceImpl();
}
代理模式选择:
- TARGET_CLASS:CGLIB代理(类级别)
- INTERFACES:JDK动态代理(接口级别)
4. Bean生命周期全流程
4.1 完整生命周期图示
一个Bean从创建到销毁经历的完整过程:
- 实例化(Instantiation)
- 属性填充(Population)
- 初始化前(PostProcessBeforeInitialization)
- 初始化(Initialization)
- @PostConstruct
- InitializingBean.afterPropertiesSet()
- init-method
- 初始化后(PostProcessAfterInitialization)
- 使用期(In Use)
- 销毁前(PreDestroy)
- @PreDestroy
- DisposableBean.destroy()
- destroy-method
4.2 关键阶段代码示例
java复制public class LifecycleBean implements InitializingBean, DisposableBean {
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct方法执行");
}
@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean接口方法执行");
}
public void initMethod() {
System.out.println("自定义init方法执行");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy方法执行");
}
@Override
public void destroy() {
System.out.println("DisposableBean接口方法执行");
}
public void destroyMethod() {
System.out.println("自定义destroy方法执行");
}
}
对应XML配置:
xml复制<bean id="lifecycleBean" class="com.LifecycleBean"
init-method="initMethod"
destroy-method="destroyMethod"/>
4.3 执行顺序验证
实际输出结果:
code复制@PostConstruct方法执行
InitializingBean接口方法执行
自定义init方法执行
...使用期...
@PreDestroy方法执行
DisposableBean接口方法执行
自定义destroy方法执行
5. 面试常见问题与应对策略
5.1 高频追问点
-
循环依赖问题
- 构造器注入无法解决
- setter注入通过三级缓存解决
java复制// 三级缓存关键代码 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } -
作用域选择依据
- 线程安全考虑
- 性能影响评估
- 业务场景匹配
5.2 实战避坑指南
-
prototype Bean注入singleton问题
- 错误示例:
java复制@Service public class OrderService { @Autowired private ShoppingCart cart; // 错误!cart应该是prototype }- 解决方案:
java复制@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class ShoppingCart {} -
生命周期方法执行异常
- 确保初始化方法不要抛出异常
- 销毁方法需要处理中断异常
-
配置元数据冲突
- 三种初始化方法的优先级:
- @PostConstruct
- InitializingBean
- init-method
6. 进阶理解与性能优化
6.1 生命周期扩展点
通过BeanPostProcessor实现自定义逻辑:
java复制public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if(bean instanceof MySpecialBean) {
// 自定义处理逻辑
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
6.2 容器启动优化
- 懒加载策略:
java复制@Lazy
@Service
public class HeavyService {}
- 条件化装配:
java复制@Conditional(MyCondition.class)
@Bean
public DataSource dataSource() {}
- 配置类优化:
java复制@Configuration(proxyBeanMethods = false) // 避免CGLIB代理
public class AppConfig {}
在实际项目开发中,合理运用这些Spring特性可以显著提升应用性能。我在电商系统优化中,通过调整Bean作用域和懒加载策略,使系统启动时间缩短了40%。
