控制反转(Inversion of Control)是一种设计原则,用于将传统程序中的控制流程反转。在传统编程中,我们的代码直接调用依赖对象,而在IoC中,这个控制权被反转了——由外部容器(在Spring中就是IoC容器)来负责对象的创建和依赖注入。
生活化比喻:
想象你去餐厅点餐:
组件 | 说明 | 类比 |
---|---|---|
BeanFactory |
IoC容器的基础接口,提供基本的DI功能 | 餐厅的基本厨房 |
ApplicationContext |
BeanFactory 的子接口,添加了更多企业级功能 |
高级餐厅,除了做菜还有音乐、装饰等服务 |
BeanDefinition |
描述bean的定义信息 | 菜谱,描述如何做一道菜 |
BeanPostProcessor |
对bean进行后处理 | 菜品装饰师,在上菜前进行最后装饰 |
让我们创建一个最简单的Spring Boot应用来演示IoC:
// 1. 定义一个简单的服务类
@Service // @Service注解告诉Spring这是一个服务层的Bean
public class GreetingService {
public String greet(String name) {
return "Hello, " + name + "!";
}
}
// 2. 创建一个控制器类来使用这个服务
@RestController
public class GreetingController {
private final GreetingService greetingService;
// 3. 通过构造器注入依赖 - IoC的核心体现
@Autowired // @Autowired告诉Spring自动注入依赖
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/greet")
public String greet(@RequestParam String name) {
return greetingService.greet(name);
}
}
// 4. 主应用类
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
代码解析:
@Service
注解将GreetingService
标记为Spring管理的Bean@RestController
将控制器类标记为Spring MVC组件@Autowired
实现了依赖的自动注入,这是IoC的核心体现Spring Bean支持多种作用域,以下是常用作用域的对比:
作用域 | 说明 | 适用场景 | 生命周期 |
---|---|---|---|
singleton | 默认作用域,每个容器只有一个实例 | 无状态服务、工具类 | 容器启动时创建,容器关闭时销毁 |
prototype | 每次请求都创建一个新实例 | 有状态对象、需要隔离的场景 | 每次获取时创建,不管理销毁 |
request | 每个HTTP请求创建一个实例 | Web应用中的请求相关数据 | 请求开始时创建,请求结束时销毁 |
session | 每个HTTP会话一个实例 | 用户会话数据 | 会话开始时创建,会话结束时销毁 |
application | ServletContext生命周期 | 全局应用数据 | Web应用启动时创建,应用关闭时销毁 |
// 作用域配置示例
@Configuration
public class ScopeConfig {
@Bean
@Scope("singleton") // 显式声明为单例,默认可以不写
public SingletonService singletonService() {
return new SingletonService();
}
@Bean
@Scope("prototype")
public PrototypeService prototypeService() {
return new PrototypeService();
}
}
Spring Bean的生命周期包含多个阶段,可以通过实现特定接口或使用注解来干预:
生命周期回调示例:
@Service
public class LifecycleService implements InitializingBean, DisposableBean {
public LifecycleService() {
System.out.println("1. 构造器调用");
}
@PostConstruct
public void postConstruct() {
System.out.println("3. @PostConstruct方法调用");
}
@Override
public void afterPropertiesSet() {
System.out.println("4. InitializingBean.afterPropertiesSet()调用");
}
@PreDestroy
public void preDestroy() {
System.out.println("6. @PreDestroy方法调用");
}
@Override
public void destroy() {
System.out.println("7. DisposableBean.destroy()调用");
}
}
// 输出顺序:
// 1. 构造器调用
// 2. 属性注入(如果有)
// 3. @PostConstruct方法调用
// 4. InitializingBean.afterPropertiesSet()调用
// 5. BeanPostProcessor处理
// ... (使用阶段)
// 6. @PreDestroy方法调用
// 7. DisposableBean.destroy()调用
Spring提供了多种依赖注入方式,各有优缺点:
注入方式 | 代码示例 | 优点 | 缺点 | 推荐场景 |
---|---|---|---|---|
构造器注入 | public A(B b) {this.b=b;} |
不可变、完全初始化、易于测试 | 参数多时代码冗长 | 推荐的主要方式 |
Setter注入 | public void setB(B b) |
灵活、可选依赖 | 对象可能部分初始化 | 可选依赖 |
字段注入 | @Autowired private B b; |
简洁 | 不易测试、隐藏依赖 | 不推荐,仅简单原型 |
方法注入 | @Autowired public void setup(B b) |
灵活 | 不常用 | 特殊场景 |
构造器注入最佳实践:
@Service
public class OrderService {
private final PaymentService paymentService;
private final InventoryService inventoryService;
// 单一构造器可省略@Autowired
public OrderService(PaymentService paymentService,
InventoryService inventoryService) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}
// 业务方法...
}
Spring提供了多种条件化注册Bean的方式:
@Configuration
public class ConditionalConfig {
// 只有当dev.properties存在时才注册
@Bean
@ConditionalOnResource(resources = "classpath:dev.properties")
public DevService devService() {
return new DevService();
}
// 只有当DataSource Bean存在时才注册
@Bean
@ConditionalOnBean(DataSource.class)
public DataSourceChecker dataSourceChecker() {
return new DataSourceChecker();
}
// 根据系统属性决定是否注册
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new SimpleCacheManager();
}
}
@Configuration
public class LazyConfig {
@Bean
@Lazy // 延迟初始化,只有第一次使用时才创建
public HeavyResource heavyResource() {
// 模拟一个初始化很重的资源
System.out.println("初始化HeavyResource...");
return new HeavyResource();
}
@Bean
public ConsumerService consumerService() {
// 此时heavyResource不会被初始化
return new ConsumerService();
}
}
// 测试类
@SpringBootTest
public class LazyTest {
@Autowired
private ApplicationContext context;
@Test
public void testLazy() {
System.out.println("应用上下文已启动");
// 只有在这里才会初始化HeavyResource
HeavyResource resource = context.getBean(HeavyResource.class);
}
}
当有多个同类型Bean时,可以使用@Primary指定首选Bean:
public interface MessageService {
String getMessage();
}
@Service
@Primary // 当有多个MessageService时优先选择这个
class EmailService implements MessageService {
@Override
public String getMessage() {
return "Email message";
}
}
@Service
class SmsService implements MessageService {
@Override
public String getMessage() {
return "SMS message";
}
}
@Service
public class NotificationService {
// 因为有@Primary,这里会自动注入EmailService
@Autowired
private MessageService messageService;
public void send() {
System.out.println(messageService.getMessage());
}
}
特性 | BeanFactory | ApplicationContext |
---|---|---|
Bean实例化/装配 | 是 | 是 |
自动BeanPostProcessor注册 | 否 | 是 |
自动BeanFactoryPostProcessor注册 | 否 | 是 |
便捷的MessageSource访问 | 否 | 是 |
内置ApplicationEvent发布机制 | 否 | 是 |
启动速度 | 快 | 较慢 |
内存占用 | 少 | 较多 |
适用场景 | 资源受限环境 | 大多数应用 |
Spring通过三级缓存解决构造器注入无法解决的循环依赖问题:
// 循环依赖示例
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
// Spring解决循环依赖的三级缓存:
// 1. singletonObjects:存放完全初始化好的Bean
// 2. earlySingletonObjects:存放早期引用(原始对象)
// 3. singletonFactories:存放ObjectFactory,用于生成早期引用
解决流程:
// 自定义BeanPostProcessor,用于监控Bean初始化时间
@Component
public class TimingBeanPostProcessor implements BeanPostProcessor, Ordered {
private static final Logger log = LoggerFactory.getLogger(TimingBeanPostProcessor.class);
private final Map<String, Long> startTimes = new ConcurrentHashMap<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
startTimes.put(beanName, System.currentTimeMillis());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
Long startTime = startTimes.remove(beanName);
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
log.info("Bean '{}' 初始化耗时 {} ms", beanName, duration);
}
return bean;
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE; // 最后执行
}
}
// 应用输出示例:
// Bean 'myController' 初始化耗时 12 ms
// Bean 'myService' 初始化耗时 8 ms
// 1. 实现自定义Scope(这里实现一个简单的线程作用域)
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadScope =
ThreadLocal.withInitial(() -> new HashMap<>());
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadScope.get();
Object object = scope.get(name);
if (object == null) {
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
@Override
public Object remove(String name) {
Map<String, Object> scope = threadScope.get();
return scope.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// 线程作用域通常不需要销毁回调
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
// 2. 注册自定义Scope
@Configuration
public class ThreadScopeConfig implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.registerScope("thread", new ThreadScope());
}
}
// 3. 使用自定义Scope
@Component
@Scope("thread")
public class ThreadScopedBean {
private final int value = new Random().nextInt(100);
public int getValue() {
return value;
}
}
// 4. 测试
@RestController
public class ScopeTestController {
@Autowired
private ThreadScopedBean threadScopedBean;
@GetMapping("/thread")
public String test() {
return "ThreadScopedBean value: " + threadScopedBean.getValue() +
", Thread: " + Thread.currentThread().getName();
}
}
Spring Boot的自动配置基于条件化Bean注册和@EnableAutoConfiguration
:
graph LR
A[启动类@SpringBootApplication] --> B[@EnableAutoConfiguration]
B --> C[spring.factories中查找AutoConfiguration类]
C --> D[过滤掉不满足@Conditional的配置]
D --> E[注册符合条件的Bean]
自定义自动配置示例:
// 1. 定义自动配置类
@Configuration
@ConditionalOnClass(MyService.class) // 当类路径下有MyService时生效
@EnableConfigurationProperties(MyServiceProperties.class) // 启用配置属性
@AutoConfigureAfter(DataSourceAutoConfiguration.class) // 在DataSource配置之后
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 当容器中没有MyService时创建
public MyService myService(MyServiceProperties properties) {
return new MyService(properties.getPrefix(), properties.getSuffix());
}
}
// 2. 定义配置属性类
@ConfigurationProperties("my.service")
public class MyServiceProperties {
private String prefix = "Default";
private String suffix = "!";
// getters and setters...
}
// 3. 在META-INF/spring.factories中注册
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyServiceAutoConfiguration
Spring Boot支持多种环境配置方式:
// 1. 主配置类
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
@Profile("dev") // 只在dev环境激活
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:dev-schema.sql")
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 生产环境数据源配置
}
}
// 2. 使用@Conditional根据环境变量配置
@Bean
@ConditionalOnExpression("'${spring.profiles.active}' == 'cloud'")
public CloudService cloudService() {
return new CloudService();
}
// 3. 属性文件组织:
// application.properties - 公共配置
// application-dev.properties - 开发环境
// application-prod.properties - 生产环境
Spring Boot提供了强大的测试支持:
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@MockBean // 替换真实Bean为Mock
private PaymentService paymentService;
@Test
void testOrderWithMockPayment() {
// 设置Mock行为
when(paymentService.process(any())).thenReturn(true);
Order order = new Order("test");
boolean result = orderService.placeOrder(order);
assertTrue(result);
verify(paymentService).process(any());
}
@Test
@ActiveProfiles("test") // 使用test环境配置
void testWithTestProfile() {
// 测试特定环境的配置
}
}
问题现象 | 可能原因 | 解决方案 |
---|---|---|
NoSuchBeanDefinitionException | 1. Bean未扫描到 2. 条件不满足 3. 名称错误 |
1. 检查@ComponentScan范围 2. 检查@Conditional条件 3. 检查Bean名称 |
BeanCurrentlyInCreationException | 循环依赖 | 1. 改为setter注入 2. 使用@Lazy延迟加载 |
UnsatisfiedDependencyException | 依赖注入失败 | 1. 检查依赖Bean是否存在 2. 检查@Primary/@Qualifier配置 |
BeanNotOfRequiredTypeException | 类型不匹配 | 1. 检查Bean实现类 2. 检查泛型类型 |
NoUniqueBeanDefinitionException | 多个同类型Bean | 1. 使用@Primary指定首选 2. 使用@Qualifier指定名称 |
合理使用作用域:
延迟初始化:
# application.properties中全局设置
spring.main.lazy-initialization=true
组件扫描优化:
@SpringBootApplication(scanBasePackages = "com.myapp")
// 替代默认的全包扫描
配置类分离:
@Configuration
@Profile("prod")
public class ProdConfig {
// 生产环境特有配置
}
BeanPostProcessor优化:
Spring IoC容器是Spring框架的核心,理解其工作原理和最佳实践对于构建高质量Spring应用至关重要。通过本文的系统学习,你应该已经掌握了从基础到高级的IoC知识,包括:
在实际开发中,建议:
希望这篇全面深入的指南能帮助你在Spring Boot开发中更好地运用IoC技术,构建更加灵活、可维护的应用程序。
关注不关注,你自己决定(但正确的决定只有一个)。
喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!