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;
相关推荐
NCIN EXPE2 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking
RATi GORI2 小时前
Spring Boot 整合 Keycloak
java·spring boot·后端
zs宝来了2 小时前
Spring Boot 内嵌 Tomcat 原理:Tomcat ServletWebServerFactory 源码解析
spring boot·tomcat·内嵌容器·webserverfactory
dd向上2 小时前
【计算机毕设/课设】在职全栈开发工程师接单:Java(SpringBoot+Vue)/小程序/C++(Qt/MFC) 定制与辅导
java·spring boot·课程设计
码农很忙3 小时前
Spring Boot 3.x 整合 Redis 实现高性能缓存的完整指南
java·spring boot·redis
DROm RAPS3 小时前
springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice
spring boot·后端·docker
快乐柠檬不快乐3 小时前
基于Java+SpringBoot+SSM攻防靶场实验室平台
java·开发语言·spring boot
爱丽_3 小时前
Spring Boot 启动链路:自动装配、条件注解与排错方法论
java·spring boot·后端
霸道流氓气质3 小时前
Springboot集成Kafka入门流程及示例代码
spring boot·kafka