← 返回面试专题导航

🌱 Spring IoC 容器原理面试题

深入理解依赖注入、Bean 生命周期与容器核心机制

📚 IoC 容器核心概念

1. 什么是 IoC(控制反转)和 DI(依赖注入)?它们之间有什么关系?简单

📖 核心概念

IoC(Inversion of Control,控制反转)是一种设计思想,将对象的创建、配置和生命周期管理的控制权从应用程序代码转移到外部容器(如 Spring IoC 容器)。

DI(Dependency Injection,依赖注入)是实现 IoC 的一种具体方式,通过容器在运行时动态地将依赖关系注入到对象中。

🔗 关系说明

  • IoC 是思想:描述"谁控制谁,控制什么"的设计理念
  • DI 是实现:是 IoC 思想的一种具体技术实现方式
  • Spring IoC 容器:通过 DI 实现了 IoC 思想

💻 传统方式 vs IoC 方式

// 传统方式:对象自己创建依赖
public class UserService {
    private UserDao userDao = new UserDaoImpl(); // 紧耦合
    
    public void saveUser(User user) {
        userDao.save(user);
    }
}

// IoC 方式:依赖由容器注入
@Service
public class UserService {
    @Autowired
    private UserDao userDao; // 松耦合,由 Spring 容器注入
    
    public void saveUser(User user) {
        userDao.save(user);
    }
}

✨ IoC 的优势

  • 降低耦合度:对象之间的依赖关系由容器管理
  • 提高可测试性:可以轻松注入 Mock 对象进行单元测试
  • 提高可维护性:依赖关系集中管理,易于修改和扩展
  • 支持 AOP:容器可以在对象创建时织入切面逻辑
💡 记忆技巧:IoC 是"不要找我,我来找你"的好莱坞原则。传统方式是对象主动创建依赖(我找你),IoC 是容器主动注入依赖(我来找你)。
2. Spring IoC 容器有哪几种?BeanFactory 和 ApplicationContext 有什么区别?中等

📦 Spring IoC 容器类型

Spring 提供了两种主要的 IoC 容器:

  • BeanFactory:基础容器,提供基本的 IoC 功能
  • ApplicationContext:高级容器,继承 BeanFactory 并提供更多企业级功能

🔍 核心区别

对比维度 BeanFactory ApplicationContext
加载方式 延迟加载(Lazy) 立即加载(Eager)
Bean 创建时机 首次调用 getBean() 时 容器启动时(单例)
国际化支持 ❌ 不支持 ✅ 支持 MessageSource
事件发布 ❌ 不支持 ✅ 支持 ApplicationEvent
资源访问 ❌ 不支持 ✅ 支持 ResourceLoader
AOP 支持 需要手动配置 自动支持
内存占用 较小 较大

💻 代码示例

// 1. BeanFactory 使用示例
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
UserService userService = (UserService) factory.getBean("userService"); // 此时才创建 Bean

// 2. ApplicationContext 使用示例(推荐)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean(UserService.class); // Bean 已在容器启动时创建

// 3. ApplicationContext 的三种常见实现
// 3.1 从类路径加载配置文件
ApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml");

// 3.2 从文件系统加载配置文件
ApplicationContext context2 = new FileSystemXmlApplicationContext("C:/config/beans.xml");

// 3.3 基于注解的配置(最常用)
ApplicationContext context3 = new AnnotationConfigApplicationContext(AppConfig.class);

🎯 使用场景

  • BeanFactory:资源受限的环境(如移动设备、嵌入式系统)
  • ApplicationContext:企业级应用开发(推荐,99% 的场景)
💡 面试要点:重点说明 ApplicationContext 在容器启动时就创建所有单例 Bean,可以在启动阶段发现配置错误,而 BeanFactory 是延迟加载,问题可能在运行时才暴露。
3. Spring 中有哪几种依赖注入方式?各有什么优缺点?中等

💉 三种依赖注入方式

1️⃣ 构造器注入(Constructor Injection)

@Service
public class UserService {
    private final UserDao userDao;
    private final EmailService emailService;
    
    // 推荐:使用构造器注入
    @Autowired // Spring 4.3+ 单构造器可省略
    public UserService(UserDao userDao, EmailService emailService) {
        this.userDao = userDao;
        this.emailService = emailService;
    }
}

✅ 优点:

  • 依赖对象在构造时就确定,保证对象完整性
  • 依赖可以声明为 final,保证不可变性
  • 便于编写单元测试,无需反射
  • 避免循环依赖问题(启动时就会报错)

❌ 缺点:

  • 依赖过多时构造器参数列表会很长
  • 不支持可选依赖

2️⃣ Setter 注入(Setter Injection)

@Service
public class UserService {
    private UserDao userDao;
    private EmailService emailService;
    
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Autowired(required = false) // 可选依赖
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

✅ 优点:

  • 支持可选依赖(required = false)
  • 可以在对象创建后重新注入依赖
  • 适合有默认值的依赖

❌ 缺点:

  • 对象可能处于不完整状态(依赖未注入)
  • 无法声明为 final,不保证不可变性
  • 可能出现循环依赖

3️⃣ 字段注入(Field Injection)

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    
    @Autowired
    private EmailService emailService;
}

✅ 优点:

  • 代码简洁,使用方便
  • 减少样板代码

❌ 缺点:

  • 无法声明为 final,不保证不可变性
  • 难以编写单元测试(需要反射或 Spring 容器)
  • 违反了依赖注入的原则(依赖隐藏在类内部)
  • 容易导致过多依赖(违反单一职责原则)

🏆 最佳实践

@Service
public class UserService {
    // 1. 必需依赖使用构造器注入(推荐)
    private final UserDao userDao;
    private final CacheService cacheService;
    
    // 2. 可选依赖使用 Setter 注入
    private EmailService emailService;
    
    @Autowired
    public UserService(UserDao userDao, CacheService cacheService) {
        this.userDao = userDao;
        this.cacheService = cacheService;
    }
    
    @Autowired(required = false)
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void saveUser(User user) {
        userDao.save(user);
        cacheService.put(user.getId(), user);
        
        // 可选依赖需要判空
        if (emailService != null) {
            emailService.sendWelcomeEmail(user);
        }
    }
}
💡 Spring 官方推荐:强制依赖使用构造器注入,可选依赖使用 Setter 注入,避免使用字段注入。
⚠️ 注意:如果一个类有超过 5 个依赖,说明该类职责过重,违反了单一职责原则,应该考虑拆分。
4. 详细说明 Spring Bean 的完整生命周期?困难

🔄 Bean 生命周期完整流程

Spring Bean 的生命周期可以分为以下几个阶段:

1️⃣ 实例化阶段(Instantiation)

  • Spring 容器根据 Bean 定义创建 Bean 实例
  • 调用 Bean 的构造方法

2️⃣ 属性赋值阶段(Population)

  • Spring 容器将配置的属性值注入到 Bean 中
  • 通过 setter 方法或字段注入依赖

3️⃣ 初始化阶段(Initialization)

  • Aware 接口回调:如果 Bean 实现了 Aware 接口,调用相应方法
    • BeanNameAware.setBeanName()
    • BeanFactoryAware.setBeanFactory()
    • ApplicationContextAware.setApplicationContext()
  • BeanPostProcessor 前置处理:调用 postProcessBeforeInitialization()
  • InitializingBean 接口:调用 afterPropertiesSet()
  • 自定义初始化方法:调用 @PostConstruct 或 init-method 指定的方法
  • BeanPostProcessor 后置处理:调用 postProcessAfterInitialization()

4️⃣ 使用阶段(In Use)

  • Bean 已经准备就绪,可以被应用程序使用

5️⃣ 销毁阶段(Destruction)

  • DisposableBean 接口:调用 destroy()
  • 自定义销毁方法:调用 @PreDestroy 或 destroy-method 指定的方法

💻 完整代码示例

@Component
public class LifecycleBean implements BeanNameAware, BeanFactoryAware, 
        ApplicationContextAware, InitializingBean, DisposableBean {
    
    private String name;
    
    // 1. 构造器
    public LifecycleBean() {
        System.out.println("1. 构造器:Bean 实例化");
    }
    
    // 2. 属性注入
    @Autowired
    public void setName(String name) {
        this.name = name;
        System.out.println("2. 属性注入:setName()");
    }
    
    // 3. Aware 接口回调
    @Override
    public void setBeanName(String name) {
        System.out.println("3.1 BeanNameAware:setBeanName()");
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("3.2 BeanFactoryAware:setBeanFactory()");
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("3.3 ApplicationContextAware:setApplicationContext()");
    }
    
    // 4. 初始化前置处理(由 BeanPostProcessor 实现)
    // postProcessBeforeInitialization()
    
    // 5. InitializingBean 接口
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("5. InitializingBean:afterPropertiesSet()");
    }
    
    // 6. 自定义初始化方法
    @PostConstruct
    public void init() {
        System.out.println("6. @PostConstruct:自定义初始化方法");
    }
    
    // 7. 初始化后置处理(由 BeanPostProcessor 实现)
    // postProcessAfterInitialization()
    
    // 8. Bean 使用中...
    
    // 9. DisposableBean 接口
    @Override
    public void destroy() throws Exception {
        System.out.println("9. DisposableBean:destroy()");
    }
    
    // 10. 自定义销毁方法
    @PreDestroy
    public void cleanup() {
        System.out.println("10. @PreDestroy:自定义销毁方法");
    }
}

// BeanPostProcessor 示例
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof LifecycleBean) {
            System.out.println("4. BeanPostProcessor:postProcessBeforeInitialization()");
        }
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof LifecycleBean) {
            System.out.println("7. BeanPostProcessor:postProcessAfterInitialization()");
        }
        return bean;
    }
}

📊 生命周期流程图

容器启动
    ↓
实例化 Bean(构造器)
    ↓
属性注入(@Autowired / setter)
    ↓
BeanNameAware.setBeanName()
    ↓
BeanFactoryAware.setBeanFactory()
    ↓
ApplicationContextAware.setApplicationContext()
    ↓
BeanPostProcessor.postProcessBeforeInitialization()
    ↓
@PostConstruct 方法
    ↓
InitializingBean.afterPropertiesSet()
    ↓
自定义 init-method
    ↓
BeanPostProcessor.postProcessAfterInitialization()
    ↓
Bean 就绪,可以使用
    ↓
容器关闭
    ↓
@PreDestroy 方法
    ↓
DisposableBean.destroy()
    ↓
自定义 destroy-method
    ↓
Bean 销毁
💡 面试技巧:回答时按照"实例化 → 属性赋值 → 初始化 → 使用 → 销毁"五个阶段来说明,重点强调 Aware 接口、BeanPostProcessor 和初始化/销毁方法的执行顺序。
⚠️ 注意:BeanPostProcessor 是针对所有 Bean 的,而 InitializingBean 和 DisposableBean 是针对单个 Bean 的。
5. Spring Bean 的作用域有哪些?如何配置?中等

🎯 Spring Bean 的六种作用域

1️⃣ singleton(单例)- 默认

整个 Spring IoC 容器中只有一个 Bean 实例,所有请求共享同一个对象。

@Component
@Scope("singleton") // 默认,可省略
public class UserService {
    // 容器中只有一个 UserService 实例
}

特点:

  • 容器启动时创建(eager initialization)
  • 线程不安全(需要自行保证线程安全)
  • 适合无状态的 Bean

2️⃣ prototype(原型)

每次请求都会创建一个新的 Bean 实例。

@Component
@Scope("prototype")
public class ShoppingCart {
    // 每次 getBean() 都会创建新实例
}

特点:

  • 延迟创建(lazy initialization)
  • Spring 不管理 prototype Bean 的完整生命周期(不会调用销毁方法)
  • 适合有状态的 Bean

3️⃣ request(请求)- Web 环境

每个 HTTP 请求创建一个 Bean 实例,请求结束后销毁。

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class LoginInfo {
    // 每个 HTTP 请求都有独立的 LoginInfo 实例
}

4️⃣ session(会话)- Web 环境

每个 HTTP Session 创建一个 Bean 实例,Session 结束后销毁。

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
    // 每个用户会话都有独立的 UserSession 实例
}

5️⃣ application(应用)- Web 环境

整个 ServletContext 生命周期内只有一个 Bean 实例。

@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AppConfig {
    // 整个 Web 应用只有一个 AppConfig 实例
}

6️⃣ websocket - Web 环境

每个 WebSocket 连接创建一个 Bean 实例。

💻 作用域对比示例

// 测试 singleton vs prototype
@SpringBootTest
public class ScopeTest {
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void testSingleton() {
        UserService service1 = context.getBean(UserService.class);
        UserService service2 = context.getBean(UserService.class);
        
        System.out.println(service1 == service2); // true - 同一个实例
    }
    
    @Test
    public void testPrototype() {
        ShoppingCart cart1 = context.getBean(ShoppingCart.class);
        ShoppingCart cart2 = context.getBean(ShoppingCart.class);
        
        System.out.println(cart1 == cart2); // false - 不同实例
    }
}

// 在 singleton Bean 中注入 prototype Bean 的问题
@Component
public class OrderService {
    
    @Autowired
    private ShoppingCart cart; // 问题:cart 只会注入一次!
    
    // 解决方案1:使用 @Lookup 方法注入
    @Lookup
    public ShoppingCart getCart() {
        return null; // Spring 会动态实现这个方法
    }
    
    // 解决方案2:注入 ApplicationContext
    @Autowired
    private ApplicationContext context;
    
    public void processOrder() {
        ShoppingCart cart = context.getBean(ShoppingCart.class);
        // 使用 cart...
    }
}

📊 作用域对比表

作用域 创建时机 销毁时机 适用场景
singleton 容器启动 容器关闭 无状态 Bean
prototype 每次 getBean() 不管理 有状态 Bean
request HTTP 请求开始 HTTP 请求结束 请求级数据
session Session 创建 Session 销毁 用户会话数据
💡 最佳实践:默认使用 singleton,只有在需要保存状态或每次使用需要新实例时才使用 prototype。
⚠️ 常见陷阱:在 singleton Bean 中注入 prototype Bean 时,prototype Bean 只会被注入一次,失去了原型的意义。需要使用 @Lookup 或 ApplicationContext.getBean() 来解决。
6. Spring 如何解决循环依赖?中等

🔄 循环依赖解决方案

什么是循环依赖?

@Component
public class A {
    @Autowired
    private B b;  // A 依赖 B
}

@Component
public class B {
    @Autowired
    private A a;  // B 依赖 A
}

Spring 三级缓存机制

public class DefaultSingletonBeanRegistry {
    
    // 一级缓存:存放完全初始化好的单例 Bean
    private final Map singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:存放早期暴露的 Bean(已实例化但未初始化)
    private final Map earlySingletonObjects = new HashMap<>(16);
    
    // 三级缓存:存放 Bean 工厂对象
    private final Map> singletonFactories = new HashMap<>(16);
    
    protected Object getSingleton(String beanName) {
        // 1. 从一级缓存获取
        Object singletonObject = this.singletonObjects.get(beanName);
        
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                // 2. 从二级缓存获取
                singletonObject = this.earlySingletonObjects.get(beanName);
                
                if (singletonObject == null) {
                    // 3. 从三级缓存获取
                    ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        // 放入二级缓存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 从三级缓存移除
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
}

解决流程

  1. 创建 A:实例化 A,放入三级缓存
  2. 填充 A 的属性:发现需要 B
  3. 创建 B:实例化 B,放入三级缓存
  4. 填充 B 的属性:发现需要 A,从三级缓存获取 A(早期引用)
  5. B 初始化完成,放入一级缓存
  6. A 获得 B,完成初始化,放入一级缓存
⚠️ 注意:
  • 只能解决 setter 注入的循环依赖
  • 构造器注入的循环依赖无法解决
  • prototype 作用域的循环依赖无法解决
7. Spring Bean 的生命周期?困难

🔄 Bean 生命周期详解

完整生命周期

@Component
public class UserService implements BeanNameAware, BeanFactoryAware, 
                                    ApplicationContextAware, InitializingBean, DisposableBean {
    
    private String name;
    
    // 1. 构造器
    public UserService() {
        System.out.println("1. 构造器执行");
    }
    
    // 2. 属性注入
    @Autowired
    public void setUserDao(UserDao userDao) {
        System.out.println("2. 属性注入");
    }
    
    // 3. BeanNameAware
    @Override
    public void setBeanName(String name) {
        System.out.println("3. BeanNameAware.setBeanName()");
        this.name = name;
    }
    
    // 4. BeanFactoryAware
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        System.out.println("4. BeanFactoryAware.setBeanFactory()");
    }
    
    // 5. ApplicationContextAware
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        System.out.println("5. ApplicationContextAware.setApplicationContext()");
    }
    
    // 6. BeanPostProcessor.postProcessBeforeInitialization()
    // 由 Spring 容器调用
    
    // 7. @PostConstruct
    @PostConstruct
    public void postConstruct() {
        System.out.println("7. @PostConstruct");
    }
    
    // 8. InitializingBean.afterPropertiesSet()
    @Override
    public void afterPropertiesSet() {
        System.out.println("8. InitializingBean.afterPropertiesSet()");
    }
    
    // 9. init-method
    public void initMethod() {
        System.out.println("9. init-method");
    }
    
    // 10. BeanPostProcessor.postProcessAfterInitialization()
    // 由 Spring 容器调用(AOP 代理在这里创建)
    
    // Bean 使用中...
    
    // 11. @PreDestroy
    @PreDestroy
    public void preDestroy() {
        System.out.println("11. @PreDestroy");
    }
    
    // 12. DisposableBean.destroy()
    @Override
    public void destroy() {
        System.out.println("12. DisposableBean.destroy()");
    }
    
    // 13. destroy-method
    public void destroyMethod() {
        System.out.println("13. destroy-method");
    }
}

配置示例

@Configuration
public class BeanConfig {
    
    @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    public UserService userService() {
        return new UserService();
    }
}
💡 记忆口诀:构造 → 注入 → Aware → 前置处理 → 初始化 → 后置处理 → 使用 → 销毁
8. @Autowired 和 @Resource 的区别?中等

📊 @Autowired vs @Resource

对比表

特性 @Autowired @Resource
来源 Spring JDK (JSR-250)
装配方式 byType byName → byType
required 支持(默认 true) 不支持
作用位置 字段、构造器、方法 字段、方法

@Autowired 示例

@Service
public class UserService {
    
    // 1. 字段注入(不推荐)
    @Autowired
    private UserDao userDao;
    
    // 2. 构造器注入(推荐)
    private final UserDao userDao;
    
    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    
    // 3. Setter 注入
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    // 4. 多个实现时指定名称
    @Autowired
    @Qualifier("mysqlUserDao")
    private UserDao userDao;
    
    // 5. 可选注入
    @Autowired(required = false)
    private Optional userDao;
}

@Resource 示例

@Service
public class UserService {
    
    // 1. 默认按名称注入
    @Resource
    private UserDao userDao; // 查找名为 "userDao" 的 Bean
    
    // 2. 指定名称
    @Resource(name = "mysqlUserDao")
    private UserDao userDao;
    
    // 3. 按类型注入(找不到同名 Bean 时)
    @Resource
    private UserDao dao; // 按类型查找 UserDao
}
💡 推荐:
  • 优先使用 @Autowired + 构造器注入
  • 需要按名称注入时使用 @Resource
  • 避免字段注入,不利于测试
9. Spring 如何实现条件装配?中等

🎯 条件装配

常用条件注解

@Configuration
public class DataSourceConfig {
    
    // 1. @ConditionalOnProperty:根据配置属性
    @Bean
    @ConditionalOnProperty(name = "datasource.type", havingValue = "mysql")
    public DataSource mysqlDataSource() {
        return new MysqlDataSource();
    }
    
    // 2. @ConditionalOnClass:类存在时
    @Bean
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    public DataSource dataSource() {
        return new DataSource();
    }
    
    // 3. @ConditionalOnMissingBean:Bean 不存在时
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource defaultDataSource() {
        return new H2DataSource();
    }
    
    // 4. @ConditionalOnBean:Bean 存在时
    @Bean
    @ConditionalOnBean(DataSource.class)
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    // 5. @Profile:环境配置
    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return new H2DataSource();
    }
    
    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        return new MysqlDataSource();
    }
}

自定义条件

// 1. 实现 Condition 接口
public class LinuxCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String os = context.getEnvironment().getProperty("os.name");
        return os != null && os.toLowerCase().contains("linux");
    }
}

// 2. 使用自定义条件
@Configuration
public class AppConfig {
    
    @Bean
    @Conditional(LinuxCondition.class)
    public Service linuxService() {
        return new LinuxService();
    }
}
💡 应用场景:
  • 多环境配置(dev/test/prod)
  • 多数据源切换
  • 功能开关
  • 自动配置
10. Spring 的 @Component 和 @Bean 有什么区别?简单

📦 @Component vs @Bean

@Component 示例

// 用于自己编写的类
@Component
public class UserService {
    
    @Autowired
    private UserDao userDao;
    
    public void save(User user) {
        userDao.save(user);
    }
}

// 衍生注解
@Service        // 业务层
@Repository     // 数据访问层
@Controller     // 控制层
@RestController // REST 控制层

@Bean 示例

// 用于第三方类或需要复杂初始化的类
@Configuration
public class AppConfig {
    
    // 1. 第三方类
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/db");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    
    // 2. 需要参数的 Bean
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    // 3. 条件装配
    @Bean
    @ConditionalOnMissingBean
    public UserService userService() {
        return new UserService();
    }
}

主要区别

特性 @Component @Bean
作用位置 类上 方法上
适用场景 自己编写的类 第三方类
扫描方式 组件扫描 配置类
灵活性 较低 较高
💡 选择建议:
  • 自己的类:使用 @Component
  • 第三方类:使用 @Bean
  • 需要复杂初始化:使用 @Bean
  • 需要条件装配:使用 @Bean
11. Spring 的 @Configuration 和 @Component 有什么区别?中等

⚙️ @Configuration vs @Component

@Configuration(Full 模式)

@Configuration
public class AppConfig {
    
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
    
    @Bean
    public UserService userService() {
        // 多次调用 userDao() 返回同一个实例
        UserService service = new UserService();
        service.setUserDao(userDao()); // 第一次调用
        service.setUserDao(userDao()); // 第二次调用,返回同一个实例
        return service;
    }
}

// Spring 会为 @Configuration 类创建 CGLIB 代理
// 拦截 @Bean 方法调用,保证单例

@Component(Lite 模式)

@Component
public class AppConfig {
    
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
    
    @Bean
    public UserService userService() {
        // 多次调用 userDao() 返回不同实例
        UserService service = new UserService();
        service.setUserDao(userDao()); // 第一次调用,创建新实例
        service.setUserDao(userDao()); // 第二次调用,又创建新实例
        return service;
    }
}

// Spring 不会为 @Component 类创建代理
// 直接调用方法,每次都创建新实例

@Configuration(proxyBeanMethods = false)

// Lite 模式:不创建代理,提升性能
@Configuration(proxyBeanMethods = false)
public class AppConfig {
    
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
    
    @Bean
    public UserService userService(UserDao userDao) {
        // 通过参数注入,而不是方法调用
        return new UserService(userDao);
    }
}
💡 选择建议:
  • Bean 之间有依赖:使用 @Configuration(Full 模式)
  • Bean 之间无依赖:使用 @Configuration(proxyBeanMethods = false)(Lite 模式,性能更好)
  • Spring Boot 2.2+ 默认使用 Lite 模式
12. Spring 的 BeanFactory 和 ApplicationContext 有什么区别?困难

🏭 BeanFactory vs ApplicationContext

继承关系

// ApplicationContext 继承 BeanFactory
public interface ApplicationContext extends 
    EnvironmentCapable,      // 环境配置
    ListableBeanFactory,     // 列举 Bean
    HierarchicalBeanFactory, // 分层
    MessageSource,           // 国际化
    ApplicationEventPublisher, // 事件发布
    ResourcePatternResolver  // 资源加载
{
    // ...
}

主要区别

特性 BeanFactory ApplicationContext
加载方式 延迟加载 立即加载
国际化 不支持 支持
事件机制 不支持 支持
BeanPostProcessor 需手动注册 自动注册
资源加载 不支持 支持

BeanFactory 示例

// 延迟加载
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));

// 获取 Bean 时才创建
UserService service = factory.getBean(UserService.class);

ApplicationContext 示例

// 立即加载(容器启动时创建所有单例 Bean)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 或使用注解配置
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// Bean 已经创建好了
UserService service = context.getBean(UserService.class);

// 国际化
String message = context.getMessage("welcome", null, Locale.CHINA);

// 事件发布
context.publishEvent(new UserRegisteredEvent(user));

// 资源加载
Resource resource = context.getResource("classpath:config.properties");
💡 推荐:实际开发中都使用 ApplicationContext,功能更强大。
13. Spring 如何实现事件监听?中等

📡 Spring 事件机制

1. 定义事件

// 继承 ApplicationEvent
public class UserRegisteredEvent extends ApplicationEvent {
    
    private User user;
    
    public UserRegisteredEvent(Object source, User user) {
        super(source);
        this.user = user;
    }
    
    public User getUser() {
        return user;
    }
}

2. 发布事件

@Service
public class UserService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void register(User user) {
        // 保存用户
        userDao.save(user);
        
        // 发布事件
        eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
    }
}

3. 监听事件

// 方式1:实现 ApplicationListener 接口
@Component
public class EmailListener implements ApplicationListener {
    
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        User user = event.getUser();
        // 发送欢迎邮件
        emailService.sendWelcomeEmail(user.getEmail());
    }
}

// 方式2:使用 @EventListener 注解(推荐)
@Component
public class UserEventListener {
    
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        User user = event.getUser();
        // 发送欢迎邮件
        emailService.sendWelcomeEmail(user.getEmail());
    }
    
    // 异步监听
    @Async
    @EventListener
    public void handleUserRegisteredAsync(UserRegisteredEvent event) {
        // 异步处理
    }
    
    // 条件监听
    @EventListener(condition = "#event.user.vip == true")
    public void handleVipUserRegistered(UserRegisteredEvent event) {
        // 只处理 VIP 用户
    }
}

4. 启用异步事件

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("event-");
        executor.initialize();
        return executor;
    }
}
💡 应用场景:
  • 用户注册后发送邮件、短信
  • 订单创建后发送通知
  • 数据变更后刷新缓存
  • 解耦业务逻辑
14. Spring 的 @Lazy 注解有什么作用?简单

⏰ 延迟加载

默认行为

// 默认情况下,单例 Bean 在容器启动时创建
@Component
public class HeavyService {
    
    public HeavyService() {
        System.out.println("HeavyService 创建(容器启动时)");
        // 执行耗时的初始化操作...
    }
}

使用 @Lazy

// 延迟到第一次使用时才创建
@Component
@Lazy
public class HeavyService {
    
    public HeavyService() {
        System.out.println("HeavyService 创建(第一次使用时)");
    }
}

// 在注入时使用 @Lazy
@Service
public class UserService {
    
    @Autowired
    @Lazy
    private HeavyService heavyService; // 注入代理对象,延迟创建
    
    public void doSomething() {
        heavyService.process(); // 第一次调用时才创建 HeavyService
    }
}

全局配置

@Configuration
public class AppConfig {
    
    // 所有 Bean 都延迟加载
    @Bean
    public static LazyInitializationBeanFactoryPostProcessor lazyInit() {
        return new LazyInitializationBeanFactoryPostProcessor();
    }
}

// 或在 application.properties 中配置
// spring.main.lazy-initialization=true

解决循环依赖

@Component
public class A {
    
    @Autowired
    @Lazy // 延迟注入,解决循环依赖
    private B b;
}

@Component
public class B {
    
    @Autowired
    private A a;
}
💡 使用场景:
  • Bean 初始化耗时,影响启动速度
  • Bean 可能不会被使用
  • 解决循环依赖
  • 按需加载
15. Spring 如何实现国际化(i18n)?中等

🌍 国际化实现

1. 创建资源文件

# messages.properties(默认)
welcome=Welcome
user.name=Username
user.password=Password

# messages_zh_CN.properties(中文)
welcome=欢迎
user.name=用户名
user.password=密码

# messages_en_US.properties(英文)
welcome=Welcome
user.name=Username
user.password=Password

2. 配置 MessageSource

@Configuration
public class I18nConfig {
    
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
    
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        resolver.setDefaultLocale(Locale.CHINA);
        return resolver;
    }
    
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang"); // URL 参数名
        return interceptor;
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private LocaleChangeInterceptor localeChangeInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor);
    }
}

3. 使用国际化

@RestController
public class UserController {
    
    @Autowired
    private MessageSource messageSource;
    
    @GetMapping("/welcome")
    public String welcome(Locale locale) {
        // 根据 locale 获取对应语言的消息
        return messageSource.getMessage("welcome", null, locale);
    }
    
    @GetMapping("/user/error")
    public String error(Locale locale) {
        // 带参数的消息
        return messageSource.getMessage("user.not.found", 
                new Object[]{"张三"}, locale);
    }
}

// 访问示例:
// http://localhost:8080/welcome?lang=zh_CN  // 返回"欢迎"
// http://localhost:8080/welcome?lang=en_US  // 返回"Welcome"

4. 在模板中使用

<!-- Thymeleaf -->
<h1 th:text="#{welcome}"></h1>
<label th:text="#{user.name}"></label>

<!-- JSP -->
<spring:message code="welcome"/>
<spring:message code="user.name"/>
💡 LocaleResolver 类型:
  • SessionLocaleResolver:存储在 Session
  • CookieLocaleResolver:存储在 Cookie
  • AcceptHeaderLocaleResolver:从请求头获取
  • FixedLocaleResolver:固定语言