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;
相关推荐
独泪了无痕3 小时前
Spring Data Redis 快速上手
spring boot·redis·后端
Thomas.Sir6 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
VimorphTech10 小时前
一段字幕太长,怎样拆句并安排显示时间?
java·spring boot
万联WANFLOW10 小时前
Kimi K3.1 Pro 接口接入指南:模型选型、费用计算与零数据保留
java·spring boot·python·安全·spring·大模型
aiks_yuan11 小时前
Spring AI 2.1 ChatMemory 已过时用什么替代:迁移到 Session Memory 实战(JDBC 存储与会话压缩)
spring boot
博风13 小时前
Springboot4.0.9,添加Filter(拦截器)
spring boot
Java内核笔记15 小时前
同一份知识库,4 种切分 × 3 种召回,纯关键词比纯向量高 13 个点
java·人工智能·spring boot
学长毕设16 小时前
基于springboot的社会志愿者管理系统-附源码
java·spring boot·后端
计算机编程-吉哥17 小时前
计算机毕业设计 基于 SpringBoot + Vue 的剧本杀系统 Java毕业设计【任务书、开题报告、中期报告、论文、源码】
java·vue.js·spring boot·毕业设计·课程设计·计算机毕业设计选题
这里是杨杨吖17 小时前
SpringCloud+Vue民宿房屋租赁系统 附带详细运行指导视频
vue.js·spring boot·spring cloud·房屋·民宿租赁