40岁开始学Java:如何实现依赖注入?
在 Java 中实现依赖注入(Dependency Injection, DI)的核心思想是将对象的依赖关系从代码内部转移到外部容器或框架管理。以下是常见的实现方式及示例:
一、手动实现依赖注入(不依赖框架)
1. 构造器注入
通过类的构造函数传递依赖对象:
// 接口定义
public interface Logger {
void log(String message);
}
// 具体实现
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console: " + message);
}
}
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("File: " + message);
}
}
// 使用依赖注入的类
public class UserService {
private final Logger logger;
// 构造器注入依赖
public UserService(Logger logger) {
this.logger = logger;
}
public void createUser(String name) {
logger.log("Creating user: " + name);
}
}
// 手动创建对象并注入依赖
public class Main {
public static void main(String[] args) {
// 选择具体实现
Logger logger = new ConsoleLogger();
UserService userService = new UserService(logger);
userService.createUser("Alice");
}
}
2. Setter 注入
通过 setter 方法设置依赖对象:
public class UserService {
private Logger logger;
public void setLogger(Logger logger) {
this.logger = logger;
}
public void createUser(String name) {
if (logger == null) {
throw new IllegalStateException("Logger not initialized");
}
logger.log("Creating user: " + name);
}
}
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
userService.setLogger(new FileLogger());
userService.createUser("Bob");
}
}
3. 工厂模式
通过工厂类动态创建对象并注入依赖:
public class LoggerFactory {
public static Logger getLogger(String type) {
switch (type) {
case "console":
return new ConsoleLogger();
case "file":
return new FileLogger();
default:
throw new IllegalArgumentException("Invalid logger type");
}
}
}
public class Main {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("console");
UserService userService = new UserService(logger);
userService.createUser("Charlie");
}
}
二、使用框架实现依赖注入(推荐)
1. Spring 框架
(1) 构造器注入
• 步骤:
- 定义接口和实现类。
- 使用
@Component
标记组件。 - 在目标类中使用
@Autowired
注解注入依赖。
• 示例:
// 定义接口
public interface DatabaseService {
String query(String sql);
}
// 实现类
@Component
public class MySQLService implements DatabaseService {
@Override
public String query(String sql) {
return "MySQL Result: " + sql;
}
}
// 目标类
@Component
public class UserService {
private final DatabaseService databaseService;
// 构造器注入
@Autowired
public UserService(DatabaseService databaseService) {
this.databaseService = databaseService;
}
public String getUserData() {
return databaseService.query("SELECT * FROM users");
}
}
// 启动类
@SpringBootApplication
public class App {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(App.class, args);
UserService userService = context.getBean(UserService.class);
System.out.println(userService.getUserData());
}
}
(2) Setter 注入
@Component
public class UserService {
private DatabaseService databaseService;
@Autowired
public void setDatabaseService(DatabaseService databaseService) {
this.databaseService = databaseService;
}
}
(3) 字段注入(不推荐)
@Component
public class UserService {
@Autowired
private DatabaseService databaseService;
}
2. Google Guice 框架
(1) 绑定依赖
通过模块(Module)绑定接口到实现类:
public class AppModule extends AbstractModule {
@Override
protected void configure() {
bind(DatabaseService.class).to(MySQLService.class);
}
}
(2) 注入依赖
public class UserService {
private final DatabaseService databaseService;
@Inject
public UserService(DatabaseService databaseService) {
this.databaseService = databaseService;
}
}
// 使用 Guice 创建对象
public class Main {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AppModule());
UserService userService = injector.getInstance(UserService.class);
System.out.println(userService.getUserData());
}
}
三、测试环境中的依赖注入
使用 Mockito 注入模拟对象进行单元测试:
public class UserServiceTest {
@Test
void testGetUser() {
// 创建模拟依赖
DatabaseService mockDb = Mockito.mock(DatabaseService.class);
when(mockDb.query(1)).thenReturn("Alice");
// 构造目标类并注入依赖
UserService userService = new UserService(mockDb);
// 执行方法并验证结果
assertEquals("Alice", userService.getUser(1).getName());
}
}
四、依赖注入的核心模式
模式 | 描述 |
---|---|
控制反转(IoC) | 由容器(如 Spring/IoC 容器)控制对象的创建和管理,而非代码自身控制。 |
服务定位器 | 通过统一接口(如 ServiceLocator )动态获取依赖对象,常见于手动实现。 |
依赖倒置 | 高层模块不依赖低层模块,二者都依赖抽象接口(如 Logger 接口)。 |
五、最佳实践
- 优先构造器注入:确保对象在创建时依赖已满足,避免
NullPointerException
。 - 避免字段注入:破坏封装性,难以追踪依赖来源。
- 使用接口抽象:隔离具体实现,提高代码灵活性。
- 结合单元测试:通过模拟依赖快速验证功能。
通过以上方式,可以灵活实现依赖注入,降低代码耦合度,提升可维护性和可测试性。