Spring框架作为JavaEE开发的事实标准,其核心思想可以用两个关键词概括:IOC(控制反转)和DI(依赖注入)。这两个概念看似简单,但真正理解其设计哲学需要从实际开发痛点说起。
在传统Java开发中,对象创建和依赖管理通常由开发者手动完成。比如我们需要一个UserService时,往往会直接new UserServiceImpl()。这种硬编码方式带来的直接问题是:
Spring的解决方案是将对象的创建、装配和管理权从程序员手中转移到容器(Container)中。这种控制权的转移就是所谓的"控制反转"。举个例子:
java复制// 传统方式
UserService userService = new UserServiceImpl();
// Spring方式
@Autowired
UserService userService;
关键理解:IOC是一种设计思想,DI是实现这种思想的具体技术手段。Spring通过DI机制实现了IOC理念。
Spring容器的初始化过程可以概括为以下几个关键步骤:
这个流程中容易出问题的环节是循环依赖。Spring通过三级缓存机制解决setter注入的循环依赖问题:
java复制// 三级缓存结构示例
Map<String, Object> singletonObjects = ... // 一级缓存(完整Bean)
Map<String, Object> earlySingletonObjects = ... // 二级缓存(早期引用)
Map<String, ObjectFactory<?>> singletonFactories = ... // 三级缓存(工厂对象)
Spring支持多种Bean作用域,最常用的是singleton和prototype:
| 作用域类型 | 说明 | 适用场景 |
|---|---|---|
| singleton | 容器中只存在一个实例(默认) | 无状态服务、工具类 |
| prototype | 每次获取都创建新实例 | 有状态对象、线程不安全类 |
| request | 每个HTTP请求一个实例 | Web层对象 |
| session | 每个会话一个实例 | 用户会话数据 |
Bean生命周期的关键节点可以通过以下方式干预:
java复制public class ExampleBean implements InitializingBean, DisposableBean {
// 初始化回调
public void afterPropertiesSet() {...}
// 销毁回调
public void destroy() {...}
// 替代方案(更推荐)
@PostConstruct
public void init() {...}
@PreDestroy
public void cleanup() {...}
}
这是Spring官方推荐的首选方式,具有以下优势:
典型实现:
java复制@Service
public class OrderService {
private final PaymentService paymentService;
@Autowired // Spring4.3+可以省略
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
适用于可选依赖或需要重新配置的场景:
java复制@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
虽然写法最简洁,但存在明显缺点(不易测试、隐藏依赖关系),应谨慎使用:
java复制@Repository
public class UserDao {
@Autowired
private DataSource dataSource;
}
最佳实践:优先使用构造器注入,必须使用setter注入时确保方法可见性不是private。字段注入仅限测试代码使用。
Spring提供了灵活的Conditional机制,可以根据不同环境动态注册Bean:
java复制@Configuration
public class DataSourceConfig {
@Bean
@Conditional(ProdEnvCondition.class)
public DataSource prodDataSource() {...}
@Bean
@Conditional(DevEnvCondition.class)
public DataSource devDataSource() {...}
}
对于启动时不急需的Bean,可以配置延迟加载提升启动速度:
xml复制<bean id="lazyBean" class="com.example.LazyBean" lazy-init="true"/>
或使用注解方式:
java复制@Lazy
@Service
public class HeavyService {...}
解决prototype Bean注入singleton Bean时的作用域不匹配问题:
java复制@Scope("prototype")
public class PrototypeBean {...}
@Service
public class SingletonService {
@Lookup
public PrototypeBean getPrototypeBean() {
return null; // 实际由Spring实现
}
}
现象:NoSuchBeanDefinitionException
排查步骤:
现象:BeanCreationException
典型原因:
当存在多个同类型Bean时,解决方案包括:
java复制@Autowired
private List<Validator> validators;
随着Spring Boot的普及,一些传统配置方式已经发生变化:
配置方式迁移:
依赖管理进化:
测试支持增强:
一个现代Spring Boot应用的典型分层结构:
code复制com.example
├── config // 配置类
├── controller // 表现层
├── service // 业务层
├── repository // 数据层
└── model // 领域对象
在实际项目中,我通常会建立明确的包访问规则: