2-1 IoC 容器与依赖注入

2-1 IoC 容器与依赖注入

概念解析

什么是 IoC?

IoC(Inversion of Control)控制反转:将对象的创建和管理权交给容器,而不是在代码中 new

DI(Dependency Injection)依赖注入:容器在运行时自动将依赖注入到对象中

Bean 注册方式

方式 注解 适用场景
组件扫描 @Component, @Service, @Repository, @Controller 大部分业务 Bean
@Bean 注解 @Configuration + @Bean 第三方库、需要精细控制
@Import @Import(ConfigClass.class) 批量导入配置
FactoryBean 实现 FactoryBean 接口 复杂创建逻辑
动态注册 BeanDefinitionRegistry 动态注册Bean

Bean 作用域

作用域 说明 使用场景
singleton 单例(默认) 无状态 Bean
prototype 原型,每次新建 有状态的 Bean
request 一次请求 Web 请求相关
session 一次会话 用户会话信息
application 应用级别 全局共享
websocket WebSocket 生命周期 长连接

代码示例

1. 基本注入方式

构造器注入(推荐)

java 复制代码
@Service
public class UserService {

    private final UserRepository userRepository;

    // 构造器注入,final 不可变
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Setter 注入

java 复制代码
@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

字段注入

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;  // 不推荐
}

2. @Bean 方式注册

java 复制代码
@Configuration
public class AppConfig {

    @Bean
    // 默认方法名就是 bean 名称
    public UserService userService() {
        return new UserServiceImpl();
    }

    @Bean(name = "customUserService")  // 指定名称
    public UserService userService2() {
        return new UserServiceImpl();
    }
}

3. @Configuration 深度解析

java 复制代码
@Configuration
public class AppConfig {

    @Bean
    public UserService userService(UserRepository userRepository) {
        // 参数会自动注入
        return new UserServiceImpl(userRepository);
    }

    // @Scope 调整作用域
    @Bean
    @Scope("prototype")
    public UserService prototypeService() {
        return new UserServiceImpl();
    }

    // @Lazy 延迟初始化
    @Bean
    @Lazy
    public UserService lazyService() {
        return new UserServiceImpl();
    }

    // @Primary 优先使用
    @Bean
    @Primary
    public UserRepository primaryRepository() {
        return new PrimaryUserRepository();
    }
}

4. Bean 条件注册

java 复制代码
@Configuration
public class ConditionalConfig {

    // 类存在时才注册
    @Bean
    @ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
    public JsonService jsonService() {
        return new JacksonJsonService();
    }

    // 配置项存在时才注册
    @Bean
    @ConditionalOnProperty(prefix = "app.cache", name = "enabled", havingValue = "true")
    public CacheService cacheService() {
        return new RedisCacheService();
    }

    // Bean 不存在时才注册
    @Bean
    @ConditionalOnMissingBean(CacheService.class)
    public CacheService defaultCacheService() {
        return new SimpleCacheService();
    }

    // 容器中有特定类型时才注册
    @Bean
    @ConditionalOnBean(DataSource.class)
    public JdbcService jdbcService() {
        return new JdbcServiceImpl();
    }
}

5. Bean 生命周期回调

java 复制代码
@Configuration
public class LifecycleConfig {

    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    // 或使用注解
    @Component
    public class MyService {

        @PostConstruct
        public void init() {
            // 初始化操作
        }

        @PreDestroy
        public void destroy() {
            // 销毁操作
        }
    }
}

源码解读

Bean 创建流程

java 复制代码
// AbstractApplicationContext.refresh() 核心流程
public void refresh() throws BeansException {
    // 1. 准备 BeanFactory
    prepareBeanFactory(beanFactory);

    // 2. 执行 BeanFactoryPostProcessor
    invokeBeanFactoryPostProcessors(beanFactory);

    // 3. 注册 BeanPostProcessor
    registerBeanPostProcessors(beanFactory);

    // 4. 初始化消息源、事件广播器等

    // 5. 创建 Bean(实例化 + 依赖注入)
    finishBeanFactoryInitialization(beanFactory);

    // 6. 初始化所有 Bean
    finishRefresh();
}

Bean 实例化时机

类型 实例化时机
singleton 容器创建时(默认)
prototype 每次 getBean 时
lazy singleton 第一次使用时

常见坑点

⚠️ 坑 1:循环依赖

问题:A 依赖 B,B 依赖 A

java 复制代码
@Component
public class A {
    @Autowired
    private B b;
}

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

解决

java 复制代码
// 方案一:使用 @Lazy 延迟加载
@Autowired
private B b;

// 方案二:使用 Setter 注入(用于 break 循环)
@Component
public class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

// 方案三:使用 @PostConstruct
@Component
public class A {
    @Autowired
    private B b;

    @PostConstruct
    public void init() {
        b.setA(this);
    }
}

注意:Spring Boot 2.6+ 默认禁止循环依赖,需要配置开启

⚠️ 坑 2:多个同类型 Bean 注入

问题:有多个实现类,@Autowired 时不知道注入哪个

java 复制代码
// 有两个实现类
@Component
public class AlipayService implements PayService { }

@Component
public class WechatPayService implements PayService { }

// 注入时报错
@Autowired
private PayService payService;  // NoUniqueBeanDefinitionException

解决

java 复制代码
// 方案一:@Primary 指定默认实现
@Component
@Primary
public class AlipayService implements PayService { }

// 方案二:@Qualifier 指定名称
@Autowired
@Qualifier("alipayService")
private PayService payService;

// 方案三:注入 List
@Autowired
private List<PayService> payServices;  // 注入所有实现

⚠️ 坑 3:静态字段注入

问题:static 字段无法通过 @Autowired 注入

错误写法

java 复制代码
@Component
public class UserService {
    @Autowired
    private static UserRepository userRepository;  // 错误!
}

正确写法

java 复制代码
@Component
public class UserService {
    private static UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        UserService.userRepository = userRepository;
    }
}

面试题

Q1:Spring IoC 的实现原理?

参考答案

  1. 配置文件解析:读取 XML 或注解配置
  2. BeanDefinition:将 Bean 定义转为 BeanDefinition 对象
  3. BeanFactoryPostProcessor:允许在 Bean 创建前修改 BeanDefinition
  4. 实例化:通过反射(Constructor.newInstance)创建实例
  5. 依赖注入:通过反射设置属性值
  6. 初始化:执行 BeanPostProcessor 后置处理
  7. 生命周期管理:注册 DisposableBean 销毁回调

Q2:构造器注入为什么是推荐的?

参考答案

  1. 不可变性:构造器注入的对象是 final,无法被修改
  2. 强制依赖:不提供构造器参数会导致编译失败
  3. 易于测试:可以直接 new 传入 mock 对象
  4. 线程安全:不存在中途被修改的风险
  5. 避免 NPE:构造器参数必须非空

Q3:BeanFactory 和 FactoryBean 的区别?

参考答案

维度 BeanFactory FactoryBean
定位 IoC 容器接口 创建 Bean 的工厂
作用 管理 Bean 生命周期 封装复杂创建逻辑
获取方式 getBean("beanName") getBean("&beanName")
使用场景 框架内部 开发者自定义
java 复制代码
// FactoryBean 示例
@Component
public class MyFactoryBean implements FactoryBean<User> {

    @Override
    public User getObject() {
        // 复杂创建逻辑
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

// 获取 MyFactoryBean 本身:ctx.getBean("&myFactoryBean")
// 获取 MyFactoryBean 创建的对象:ctx.getBean("myFactoryBean")

Q4:Bean 的生命周期?

参考答案

sql 复制代码
1. 实例化 Bean
      ↓
2. 设置属性值(依赖注入)
      ↓
3. 检查 Aware 接口
      ↓
4. BeanPostProcessor.postProcessBeforeInitialization
      ↓
5. @PostConstruct 初始化方法
      ↓
6. InitializingBean.afterPropertiesSet
      ↓
7. 自定义 init-method
      ↓
8. BeanPostProcessor.postProcessAfterInitialization
      ↓
9. Bean 已就绪
      ↓
10. @PreDestroy 销毁方法
      ↓
11. DisposableBean.destroy
      ↓
12. 自定义 destroy-method

Q5:@Autowired 和 @Resource 的区别?

参考答案

特性 @Autowired @Resource
来源 Spring 注解 Java JSR-250 注解
注入方式 byType + byName byName 优先
required 属性 支持 不支持
位置 字段/构造器/Setter 字段/Setter
java 复制代码
// @Autowired 默认按类型匹配
@Autowired
private UserService userService;

// 可以配合 @Qualifier 指定名称
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;

// @Resource 默认按名称匹配,匹配失败再按类型
@Resource(name = "userServiceImpl")
private UserService userService;
相关推荐
是梦终空9 小时前
计算机源码273—基于SpringBoot+Vue3停车场管理系统带支沙箱支付(源代码+数据库)
数据库·spring boot·vue·mybatis·停车场管理系统·沙箱支付·毕设设计
Filwaod9 小时前
Java面试:AIGC场景下的技术深度拷问-谢飞机篇
spring boot·缓存·微服务·消息队列·aigc·java面试·ai技术
隐退山林10 小时前
JavaEE进阶:SpringBoot配置文件
java·spring boot·java-ee
霸道流氓气质12 小时前
SpringAI+Ollama本地模型实现快速对话入门实例
spring boot·ai
xun-ming13 小时前
SpringBoot和Vue3实战阿里百炼大模型极简版
spring boot·ai·vue3·智能体·百炼大模型
csdn2015_13 小时前
java springboot 文件导入,判断第一列的值是否有重复
java·windows·spring boot
阿丰资源13 小时前
基于Springboot+mysql的在线兼职平台(附源码)
spring boot·后端·mysql
怪祝浙13 小时前
从简单项目入手Java(学生系统)V6(Web版本 Spring Boot3 MySQL Vue3 MyBatis)
java·spring boot·mysql
蜡台14 小时前
Vue + SpringBoot 实现 WebSocket 基于 Sec-WebSocket-Protocol 传参鉴权(避坑指南)
vue.js·spring boot·websocket·sec
进阶的猿猴15 小时前
Rsa简单实现接口到期限制(springBoot)
java·spring boot·后端