IoC容器与依赖注入
IoC概念与原理
什么是IoC
IoC(Inversion of Control,控制反转)是一种设计思想,将对象的创建和管理权交给容器。
传统方式 vs IoC方式
传统方式:
java
public class UserService {
// 自己创建依赖对象
private UserDao userDao = new UserDaoImpl();
public User getUser(Long id) {
return userDao.findById(id);
}
}IoC方式:
java
public class UserService {
private final UserDao userDao;
// 通过构造方法注入依赖
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public User getUser(Long id) {
return userDao.findById(id);
}
}IoC的优势
| 优势 | 说明 |
|---|---|
| 解耦 | 降低组件之间的耦合度 |
| 可测试 | 方便单元测试和Mock |
| 可维护 | 易于修改和扩展 |
| 可复用 | 组件可独立使用 |
IoC容器
BeanFactory
BeanFactory是最基本的IoC容器,提供基础的依赖注入功能。
java
// 创建BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 注册Bean定义
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(UserService.class);
beanFactory.registerBeanDefinition("userService", beanDefinition);
// 获取Bean
UserService userService = beanFactory.getBean(UserService.class);ApplicationContext
ApplicationContext是BeanFactory的子接口,提供更多企业级功能。
java
// 基于XML配置
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 基于Java配置
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 基于注解扫描
ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
// 获取Bean
UserService userService = context.getBean(UserService.class);ApplicationContext vs BeanFactory
| 特性 | ApplicationContext | BeanFactory |
|---|---|---|
| Bean实例化时机 | 容器启动时 | 首次获取时 |
| 国际化支持 | 支持 | 不支持 |
| 事件发布 | 支持 | 不支持 |
| AOP支持 | 自动支持 | 需手动配置 |
| 性能 | 启动较慢 | 启动较快 |
依赖注入方式
1. 构造器注入(推荐)
java
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// 构造器注入
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}优点:
- 保证依赖不可变(final)
- 保证依赖不为空
- 易于单元测试
- 清晰表达依赖关系
2. Setter注入
java
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
// Setter注入
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}优点:
- 可选依赖
- 灵活性高
- 支持循环依赖
3. 字段注入(不推荐)
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
}缺点:
- 无法使用final
- 难以单元测试
- 隐藏依赖关系
- 容易空指针
注入方式选择
| 场景 | 推荐方式 |
|---|---|
| 必需依赖 | 构造器注入 |
| 可选依赖 | Setter注入 |
| 多个构造器 | 构造器 + @Autowired |
自动装配
@Autowired
java
@Service
public class UserService {
private final UserRepository userRepository;
// 构造器注入可省略@Autowired(Spring 4.3+)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}@Autowired规则
- 按类型匹配:默认按类型查找Bean
- 按名称匹配:配合@Qualifier使用
- 可选注入:配合required=false
java
@Service
public class UserService {
// 按类型匹配
@Autowired
private UserRepository userRepository;
// 按名称匹配
@Autowired
@Qualifier("mysqlUserRepository")
private UserRepository mysqlRepository;
// 可选注入
@Autowired(required = false)
private OptionalService optionalService;
}@Primary
当有多个同类型Bean时,使用@Primary指定默认Bean:
java
@Repository
@Primary
public class MysqlUserRepository implements UserRepository {
// MySQL实现
}
@Repository
public class MongoUserRepository implements UserRepository {
// MongoDB实现
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 注入MysqlUserRepository
}@Qualifier
明确指定要注入的Bean:
java
@Service
public class UserService {
@Autowired
@Qualifier("mongoUserRepository")
private UserRepository userRepository; // 注入MongoUserRepository
}@Autowired、@Resource、@Inject对比
@Autowired
java
// Spring提供的注解
@Autowired
private UserRepository userRepository;
// 可配合@Qualifier使用
@Autowired
@Qualifier("mysqlUserRepository")
private UserRepository userRepository;
// 可选注入
@Autowired(required = false)
private OptionalService optionalService;@Resource
java
// Jakarta EE标准注解
@Resource
private UserRepository userRepository;
// 按名称注入
@Resource(name = "mysqlUserRepository")
private UserRepository userRepository;@Inject
java
// Jakarta Inject标准注解
@Inject
private UserRepository userRepository;
// 可配合@Named使用
@Inject
@Named("mysqlUserRepository")
private UserRepository userRepository;对比表
| 特性 | @Autowired | @Resource | @Inject |
|---|---|---|---|
| 来源 | Spring | Jakarta EE | Jakarta Inject |
| 默认匹配 | 类型 | 名称 | 类型 |
| 可选注入 | 支持 | 不支持 | 不支持 |
| 指定名称 | @Qualifier | name属性 | @Named |
Java配置
@Configuration
java
@Configuration
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new MysqlUserRepository();
}
@Bean
public UserService userService() {
return new UserService(userRepository());
}
}@Bean
java
@Configuration
public class AppConfig {
@Bean
@Primary
public UserRepository mysqlUserRepository() {
return new MysqlUserRepository();
}
@Bean
public UserRepository mongoUserRepository() {
return new MongoUserRepository();
}
@Bean
public UserService userService(UserRepository userRepository) {
return new UserService(userRepository);
}
}@ComponentScan
java
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
// 等价于
@Configuration
@ComponentScan({
"com.example.service",
"com.example.repository",
"com.example.controller"
})
public class AppConfig {
}@Import
java
@Configuration
@Import({DatabaseConfig.class, WebConfig.class})
public class AppConfig {
}注解扫描
组件注解
| 注解 | 说明 | 使用场景 |
|---|---|---|
| @Component | 通用组件 | 通用Bean |
| @Service | 服务层 | 业务逻辑 |
| @Repository | 数据访问层 | 数据访问 |
| @Controller | 控制器 | Web层 |
| @RestController | REST控制器 | REST API |
| @Configuration | 配置类 | Java配置 |
自定义组件注解
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface BusinessService {
String value() default "";
}
// 使用
@BusinessService
public class OrderService {
}条件化注入
@Conditional
java
@Configuration
public class AppConfig {
@Bean
@Conditional(OnLinuxCondition.class)
public UserService linuxUserService() {
return new LinuxUserService();
}
@Bean
@Conditional(OnWindowsCondition.class)
public UserService windowsUserService() {
return new WindowsUserService();
}
}
public class OnLinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}常用条件注解
java
@Configuration
public class AppConfig {
// 条件属性
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheService cacheService() {
return new RedisCacheService();
}
// 条件类存在
@Bean
@ConditionalOnClass(RedisTemplate.class)
public RedisService redisService() {
return new RedisService();
}
// 条件Bean存在
@Bean
@ConditionalOnBean(DataSource.class)
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 条件Bean不存在
@Bean
@ConditionalOnMissingBean(UserService.class)
public UserService defaultUserService() {
return new DefaultUserService();
}
}Profile环境切换
定义Profile
java
@Configuration
public class DatabaseConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new H2DataSource();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return new MySQLDataSource();
}
}激活Profile
yaml
# application.yml
spring:
profiles:
active: devbash
# 命令行参数
java -jar app.jar --spring.profiles.active=prod
# 环境变量
export SPRING_PROFILES_ACTIVE=prodProfile注解
java
@Service
@Profile("dev")
public class MockEmailService implements EmailService {
}
@Service
@Profile("prod")
public class SmtpEmailService implements EmailService {
}下一步
继续学习 Bean的配置与管理,了解Bean的详细配置。