java依赖注入

一、依赖注入的基本概念

依赖注入(Dependency Injection,简称 DI)是一种设计模式,它让对象之间的依赖关系由外部容器来管理,而不是由对象自己创建或管理依赖。这种模式可以降低代码的耦合度,提高可测试性和可维护性。

二、为什么需要依赖注入

假设我们有一个UserService类,它需要使用UserRepository类来访问数据库:

public class UserService {
    private UserRepository userRepository;

    public UserService() {
        // 在类内部创建依赖对象,导致紧耦合
        this.userRepository = new UserRepository();
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

这种实现方式存在以下问题:

  1. UserServiceUserRepository紧耦合,难以替换UserRepository的实现。
  2. 难以进行单元测试,因为无法注入模拟的UserRepository

三、依赖注入的三种实现方式

1. 构造函数注入

依赖通过构造函数传递:

public class UserService {
    private final UserRepository userRepository;

    // 通过构造函数注入依赖
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

使用示例

// 创建依赖对象
UserRepository repository = new UserRepository();
// 通过构造函数注入依赖
UserService service = new UserService(repository);
service.createUser("test");
2. Setter 方法注入

依赖通过 Setter 方法传递:

public class UserService {
    private UserRepository userRepository;

    // 通过Setter方法注入依赖
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

使用示例

UserService service = new UserService();
// 创建依赖对象
UserRepository repository = new UserRepository();
// 通过Setter方法注入依赖
service.setUserRepository(repository);
service.createUser("test");
3. 接口注入

依赖通过实现特定接口来注入:

// 定义注入接口
public interface UserRepositoryAware {
    void setUserRepository(UserRepository userRepository);
}

public class UserService implements UserRepositoryAware {
    private UserRepository userRepository;

    @Override
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

四、依赖注入的优势

  1. 降低耦合度:对象不负责创建自己的依赖,依赖关系由外部管理。
  2. 提高可测试性:可以轻松注入模拟对象进行单元测试。
  3. 遵循单一职责原则:对象只需关注自己的业务逻辑,而不是依赖的创建和管理。

五、依赖注入容器

手动管理依赖关系在大型项目中会变得非常繁琐,因此出现了依赖注入容器,如 Spring 和 Google Guice。

Spring 框架示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// 使用Spring注解标记为服务组件
@Service
public class UserService {
    private final UserRepository userRepository;

    // 使用@Autowired注解进行构造函数注入
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

// 使用Spring注解标记为数据访问组件
@Repository
public class UserRepository {
    public void save(String username) {
        System.out.println("Saving user: " + username);
    }
}

配置和使用

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

// 配置类,启用组件扫描
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    public static void main(String[] args) {
        // 创建Spring应用上下文
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        // 从容器中获取Bean
        UserService service = context.getBean(UserService.class);
        service.createUser("test");
    }
}

六、进阶概念:依赖查找与依赖注入的对比

依赖查找(Dependency Lookup)是另一种管理依赖的方式,它让对象主动从容器中查找依赖:

public class UserService {
    private UserRepository userRepository;

    public UserService() {
        // 主动从容器中查找依赖
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        this.userRepository = context.getBean(UserRepository.class);
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

依赖查找的缺点

  • 代码与容器紧密耦合。
  • 难以进行单元测试。

七、Java 中的依赖注入规范

Java EE 6 引入了 JSR 330(Dependency Injection for Java)规范,定义了标准的依赖注入注解,如@Inject@Named@Provider

import javax.inject.Inject;
import javax.inject.Named;

@Named("userService")
public class UserService {
    private final UserRepository userRepository;

    @Inject
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username) {
        userRepository.save(username);
    }
}

八、依赖注入的高级应用场景

1. 作用域管理

Spring 框架支持多种 Bean 作用域,如单例(Singleton)、原型(Prototype)、请求(Request)和会话(Session):

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype") // 每次请求都创建新实例
public class UserService {
    // ...
}
2. 条件注入

根据条件决定注入哪个实现:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    @Conditional(DevEnvironmentCondition.class)
    public UserRepository devUserRepository() {
        return new DevUserRepository();
    }

    @Bean
    @Conditional(ProdEnvironmentCondition.class)
    public UserRepository prodUserRepository() {
        return new ProdUserRepository();
    }
}
3. 限定符(Qualifiers)

当有多个相同类型的 Bean 时,使用限定符来区分:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(@Qualifier("mysqlRepository") UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

九、依赖注入的最佳实践

  1. 优先使用构造函数注入:确保对象创建后即处于可用状态,避免 NullPointerException。
  2. 使用接口或抽象类定义依赖:提高代码的灵活性和可替换性。
  3. 避免循环依赖:循环依赖会导致依赖注入容器无法正确解析依赖关系。
  4. 合理使用作用域:根据对象的生命周期选择合适的作用域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值