IOC 与 DI

IOC(Inversion of Control,控制反转) 是 Spring 的核心思想:对象的创建和依赖关系的组装,由开发者自己 new 反转为由 Spring 容器负责。DI(Dependency Injection,依赖注入) 是 IOC 的具体实现方式——容器在创建对象时,把它所需的依赖”注入”进来。


核心容器

Spring 提供两种容器接口:

接口说明典型实现
BeanFactory基础容器,懒加载,生产环境几乎不直接使用DefaultListableBeanFactory
ApplicationContext扩展 BeanFactory,支持事件、国际化、AOP 等AnnotationConfigApplicationContextSpringApplication 内部

Spring Boot 应用中,SpringApplication.run() 返回的就是 ConfigurableApplicationContextApplicationContext 的子接口)。


一、注册 Bean 的方式

1. 组件扫描注解

@Component          // 通用组件
@Service            // 业务层(语义化,本质同 @Component)
@Repository         // 数据访问层(额外提供异常转换)
@Controller         // MVC 控制器
@RestController     // @Controller + @ResponseBody
@Service
public class OrderService {
    // Spring 自动扫描并注册为 Bean
}

2. @Bean 方法

适合注册第三方库的类或需要复杂初始化逻辑的对象:

@Configuration
public class AppConfig {
 
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(10))
            .build();
    }
 
    @Bean("primaryDataSource")
    public DataSource dataSource(
            @Value("${spring.datasource.url}") String url) {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(url);
        return ds;
    }
}

3. @Import

手动引入配置类或 ImportSelector(自动配置的核心机制):

@Configuration
@Import({SecurityConfig.class, CacheConfig.class})
public class ApplicationConfig { }

4. 条件注册

详见 条件注解


二、依赖注入方式

构造器注入(推荐)

@Service
public class OrderService {
 
    private final OrderRepository orderRepo;
    private final InventoryService inventoryService;
    private final EventPublisher eventPublisher;
 
    // 单构造器无需 @Autowired(Spring 4.3+)
    public OrderService(OrderRepository orderRepo,
                        InventoryService inventoryService,
                        EventPublisher eventPublisher) {
        this.orderRepo = orderRepo;
        this.inventoryService = inventoryService;
        this.eventPublisher = eventPublisher;
    }
}

优势:

  • 依赖不可变(final 字段),线程安全
  • 强制显式声明依赖,依赖过多时构造器参数爆炸,天然提示重构
  • 便于单元测试(直接 new,无需反射)
  • 在 Spring 中唯一能够彻底解决循环依赖的注入方式(配合 @Lazy 或重构)

实际项目推荐配合 Lombok:

@Service
@RequiredArgsConstructor   // 自动生成包含所有 final 字段的构造器
public class OrderService {
 
    private final OrderRepository orderRepo;
    private final InventoryService inventoryService;
}

Setter 注入

适合可选依赖或需要在注入后重新设置的场景:

@Service
public class NotificationService {
 
    private MailSender mailSender;
    private SmsSender smsSender;
 
    @Autowired(required = false)   // 可选依赖,Bean 不存在时不报错
    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }
 
    @Autowired(required = false)
    public void setSmsSender(SmsSender smsSender) {
        this.smsSender = smsSender;
    }
}

字段注入(不推荐)

@Service
public class UserService {
 
    @Autowired          // 不推荐:隐藏依赖、难以测试、无法 final
    private UserRepository userRepo;
}

字段注入使依赖关系隐式,且无法在测试中直接 new 来替换依赖,只能靠反射或 Spring 容器。


三、@Autowired 解析规则

注入时 Spring 的查找顺序:

1. 按类型(Type)匹配 —— 若唯一则直接注入
2. 若有多个同类型 Bean:
   a. 按 @Primary 标记的优先
   b. 按字段/参数名称匹配 Bean 名称
   c. 均不满足则报 NoUniqueBeanDefinitionException

@Primary

@Configuration
public class CacheConfig {
 
    @Bean
    @Primary    // 存在多个 CacheManager 时优先选择此 Bean
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.create(factory);
    }
 
    @Bean
    public CacheManager caffeineCacheManager() {
        return new CaffeineCacheManager();
    }
}

@Qualifier

精确指定 Bean 名称,优先级高于 @Primary

@Service
public class PaymentService {
 
    private final PaymentGateway gateway;
 
    public PaymentService(
            @Qualifier("stripeGateway") PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

注入集合 / Map

@Service
public class ExportService {
 
    // 注入所有实现了 Exporter 接口的 Bean
    private final List<Exporter> exporters;
 
    // Key = Bean 名称,Value = Bean 实例
    private final Map<String, Exporter> exporterMap;
 
    public ExportService(List<Exporter> exporters,
                         Map<String, Exporter> exporterMap) {
        this.exporters = exporters;
        this.exporterMap = exporterMap;
    }
 
    public void export(String format, Object data) {
        Exporter exporter = exporterMap.get(format + "Exporter");
        if (exporter == null) {
            throw new IllegalArgumentException("不支持的导出格式: " + format);
        }
        exporter.export(data);
    }
}

四、@Value 注入配置值

@Component
public class AppProperties {
 
    // 注入简单值(带默认值)
    @Value("${app.timeout:30}")
    private int timeout;
 
    // 注入列表
    @Value("${app.allowed-origins:http://localhost:3000}")
    private String[] allowedOrigins;
 
    // SpEL 表达式
    @Value("#{systemProperties['user.timezone'] ?: 'Asia/Shanghai'}")
    private String timezone;
 
    // 注入其他 Bean 的属性(SpEL)
    @Value("#{appConfig.maxRetries * 2}")
    private int maxRetries;
}

复杂配置属性推荐改用 @ConfigurationProperties,详见 属性绑定


五、作用域与注入陷阱

不同作用域 Bean 互相注入时需注意生命周期匹配:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    private String traceId = UUID.randomUUID().toString();
    // 每次 HTTP 请求创建新实例
}
 
@Service
public class TraceService {
 
    // RequestContext 是 request 作用域,TraceService 是 singleton
    // 必须使用 proxyMode,否则注入的是同一个实例(请求 1 的实例)
    @Autowired
    private RequestContext requestContext;
}

作用域详见 Bean作用域


六、延迟注入

@Service
public class HeavyService {
 
    // 延迟初始化:首次调用 get() 时才创建 Bean
    @Autowired
    private ObjectProvider<SlowDependency> slowDependencyProvider;
 
    @Autowired
    @Lazy
    private AnotherHeavyService heavyService;
 
    public void process() {
        SlowDependency dep = slowDependencyProvider.getIfAvailable();
        if (dep != null) {
            dep.doWork();
        }
    }
}

ObjectProvider 是处理可选依赖延迟注入的推荐方式,同时支持 getIfUnique() 处理多 Bean 情况。


七、编程式获取 Bean

非注入场景(工具类、静态方法)中可通过 ApplicationContext 获取:

@Component
public class BeanLocator implements ApplicationContextAware {
 
    private static ApplicationContext context;
 
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        context = ctx;
    }
 
    public static <T> T getBean(Class<T> type) {
        return context.getBean(type);
    }
 
    public static <T> T getBean(String name, Class<T> type) {
        return context.getBean(name, type);
    }
}

尽量通过注入使用 Bean;仅在无法注入时(如静态工具类、框架回调)才使用编程式获取。


常见问题

问题原因解决方案
NoSuchBeanDefinitionExceptionBean 未注册或包未扫描到检查 @ComponentScan 路径或添加 @Bean
NoUniqueBeanDefinitionException多个同类型 Bean@Qualifier@Primary
UnsatisfiedDependencyException依赖无法满足检查依赖是否已注册,或加 @Autowired(required=false)
字段注入为 null在构造器中访问 @Autowired 字段改用构造器注入
循环依赖A 依赖 B,B 依赖 A循环依赖

相关链接