构造函数注入使得依赖关系在对象创建时就被严格初始化。这符合面向对象编程中对象的不变性原则,一旦对象创建完成,其依赖关系就不会再改变。这对于一些需要保证数据一致性和安全性的场景非常重要。例如,在金融系统中,账户服务类AccountService
依赖于账户存储库AccountRepository
,使用构造函数注入可以确保在AccountService
实例创建时就拥有有效的AccountRepository
实例,避免在后续操作中出现空指针异常。
java
public class AccountService {
private final AccountRepository accountRepository;
public AccountService(AccountRepository accountRepository) {
if (accountRepository == null) {
throw new IllegalArgumentException("AccountRepository cannot be null");
}
this.accountRepository = accountRepository;
}
public void transferMoney(Account from, Account to, double amount) {
// 业务逻辑
accountRepository.updateBalance(from, from.getBalance() - amount);
accountRepository.updateBalance(to, to.getBalance() + amount);
}
}
当一个类有多个依赖或者有不同的初始化方式时,可以使用构造函数重载。例如,一个日志服务类LoggingService
可能依赖于日志存储库LogRepository
和日志配置LogConfig
,同时也可以提供一个只依赖LogRepository
的构造函数,以支持不同的使用场景。
java
public class LoggingService {
private final LogRepository logRepository;
private final LogConfig logConfig;
public LoggingService(LogRepository logRepository) {
this(logRepository, new LogConfig());
}
public LoggingService(LogRepository logRepository, LogConfig logConfig) {
this.logRepository = logRepository;
this.logConfig = logConfig;
}
public void logMessage(String message) {
// 根据配置记录日志
if (logConfig.isEnabled()) {
logRepository.saveLog(message);
}
}
}
构造函数注入使得单元测试变得更加简单和直观。在编写单元测试时,可以很方便地通过构造函数传入模拟对象(Mock Object)。例如,使用 Mockito 框架为UserService
编写单元测试:
java
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
public class UserServiceTest {
@Test
public void testSaveUser() {
UserRepository mockUserRepository = mock(UserRepository.class);
UserService userService = new UserService(mockUserRepository);
User user = new User();
userService.saveUser(user);
verify(mockUserRepository, times(1)).save(user);
}
}
Setter 方法注入允许在对象创建后动态地更新依赖关系。这在一些需要根据不同环境或运行时条件进行配置的场景中非常有用。例如,一个缓存服务类CacheService
可能依赖于不同的缓存实现(如内存缓存、Redis 缓存等),可以通过 Setter 方法在运行时动态切换缓存实现。
java
public class CacheService {
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
}
Setter 方法注入可以方便地处理可选依赖。如果某个依赖不是必需的,可以在类中为其提供默认值。例如,一个消息通知服务类NotificationService
可能依赖于邮件发送器EmailSender
,但如果没有配置邮件发送器,也可以使用默认的消息通知方式。
java
public class NotificationService {
private EmailSender emailSender;
public void setEmailSender(EmailSender emailSender) {
this.emailSender = emailSender;
}
public void sendNotification(String message) {
if (emailSender != null) {
emailSender.sendEmail(message);
} else {
// 使用默认通知方式
System.out.println("Sending default notification: " + message);
}
}
}
Setter 方法可以实现链式调用,从而创建一个流畅的接口。例如,一个配置类AppConfig
可以通过链式调用的方式设置多个配置项。
java
public class AppConfig {
private String databaseUrl;
private String username;
private String password;
public AppConfig setDatabaseUrl(String databaseUrl) {
this.databaseUrl = databaseUrl;
return this;
}
public AppConfig setUsername(String username) {
this.username = username;
return this;
}
public AppConfig setPassword(String password) {
this.password = password;
return this;
}
// 其他方法
}
使用时可以这样调用:
java
AppConfig config = new AppConfig()
.setDatabaseUrl("jdbc:mysql://localhost:3306/mydb")
.setUsername("root")
.setPassword("password");
@Autowired
与@Primary
注解当存在多个相同类型的 bean 时,除了使用@Qualifier
注解指定具体的 bean,还可以使用@Primary
注解指定一个默认的 bean。例如,有两个数据源DataSource
的实现MySqlDataSource
和OracleDataSource
,可以将MySqlDataSource
标记为@Primary
。
java
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class MySqlDataSource implements DataSource {
// 实现方法
}
@Component
public class OracleDataSource implements DataSource {
// 实现方法
}
然后在需要注入DataSource
的地方,直接使用@Autowired
即可:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DatabaseService {
@Autowired
private DataSource dataSource;
// 业务方法
}
@Resource
与@Inject
对比@Resource
是 Java EE 规范中的注解,@Inject
是 JSR - 330 规范中的注解。它们都可以用于依赖注入,功能上有一些相似之处,但也有一些细微的差别。@Resource
默认按名称匹配,而@Inject
按类型匹配。例如:
java
import javax.annotation.Resource;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Resource(name = "productRepository")
private ProductRepository productRepository1;
@Inject
private ProductRepository productRepository2;
// 业务方法
}
@Conditional
注解Spring 提供了@Conditional
注解,可以根据条件来决定是否进行依赖注入。例如,根据不同的操作系统来注入不同的文件服务实现。
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
@Conditional(WindowsCondition.class)
public FileService windowsFileService() {
return new WindowsFileService();
}
@Bean
@Conditional(LinuxCondition.class)
public FileService linuxFileService() {
return new LinuxFileService();
}
}
其中WindowsCondition
和LinuxCondition
是自定义的条件类,实现Condition
接口:
java
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
}
在实际的 Spring 项目中,通常会综合运用多种依赖注入方式。例如,对于核心业务类,使用构造函数注入保证依赖的稳定性;对于一些可配置的组件,使用 Setter 方法注入实现动态配置;对于一些辅助类或工具类,使用基于注解的自动装配简化配置。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
private final CoreRepository coreRepository;
private AuxiliaryService auxiliaryService;
@Autowired
public BusinessService(CoreRepository coreRepository) {
this.coreRepository = coreRepository;
}
@Autowired
public void setAuxiliaryService(AuxiliaryService auxiliaryService) {
this.auxiliaryService = auxiliaryService;
}
public void performBusinessLogic() {
// 使用核心仓库
coreRepository.saveData();
// 使用辅助服务
auxiliaryService.doSomething();
}
}
通过综合运用不同的依赖注入方式,可以充分发挥它们各自的优势,构建出更加灵活、可维护和可测试的 Spring 应用程序。
欢迎点赞关注哦,本专栏深入剖析 30 个关键面试点,通过趣味解读、源码分析、实战技巧分享,
将晦涩的知识变得通俗易懂。无论是 Spring 事务管理的复杂机制,还是 Spring MVC 请求处理的高效流程,在这里都能找到清晰且深入的讲解。无论你是正在为面试发愁,渴望系统梳理 Spring 知识;还是想在工作中更好地运用 Spring 提升开发效率,本专栏都将是你的不二之选。跟随我们,踏上 Spring 面试通关之旅,让 Spring 知识成为你职场进阶的有力武器吧。【每周不定期更新喔】