springboot

依赖注入与 Bean 管理

By AI-Writer 9 min read

依赖注入与 Bean 管理

依赖注入(Dependency Injection,DI)是 Spring Framework 最核心的概念,也是 Spring Boot 一切魔法的基础。理解 DI,你就能理解为什么 Spring Boot 能「约定优于配置」——框架自动发现、管理和组装组件,你只需声明依赖,容器负责注入。

IoC 容器核心概念

什么是 IoC

控制反转(Inversion of Control,IoC)是一种设计原则,将对象的创建和依赖关系的管理从应用代码中转移到框架容器。在传统模式中:

java
// ❌ 传统方式:对象在代码内部创建,高耦合
public class UserService {
    private UserRepository repository = new UserRepositoryImpl(); // 硬编码依赖
    private EmailService email = new EmailService(); // 难以替换

    public void register(User user) {
        repository.save(user);
        email.sendWelcome(user);
    }
}
java
// ✅ IoC 方式:依赖由外部注入,低耦合,可测试
public class UserService {
    private final UserRepository repository;
    private final EmailService email;

    // 依赖通过构造函数从外部传入
    public UserService(UserRepository repository, EmailService email) {
        this.repository = repository;
        this.email = email;
    }

    public void register(User user) {
        repository.save(user);
        email.sendWelcome(user);
    }
}

Bean 是什么

在 Spring 中,Bean 是由 Spring IoC 容器创建、组装和管理的对象。所有被 @Component@Service@Repository@Controller@Configuration + @Bean 标注的类,或者用 Java @Bean 方法返回的对象,都是 Spring 容器中的 Bean。

Bean 的注册方式

1. @Component 及其派生注解

这是最常用的方式。@Component 是最通用的组件标注,@Service@Repository@Controller 都是 @Component 的特殊化:

java
// @Service:标注业务逻辑组件(语义更明确)
@Service
public class UserServiceImpl implements UserService {
    // ...
}

// @Repository:标注数据访问组件(额外支持异常转换)
@Repository
public class JpaUserRepository implements UserRepository {
    // ...
}

// @Controller:标注 Spring MVC 控制器
@Controller
public class UserController {
    // ...
}

// @RestController:@Controller + @ResponseBody 的组合
@RestController
@RequestMapping("/api/users")
public class UserApiController {
    // ...
}

2. @Configuration + @Bean

当需要引入第三方库或需要更细粒度控制 Bean 的创建逻辑时,使用 Java 配置类:

java
@Configuration
public class AppConfig {

    // @Bean 标注的方法返回值会被注册为 Bean,方法名即为 Bean 名称
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate template = new RestTemplate();
        // 自定义配置
        template.getMessageConverters().add(new Jackson2HttpMessageConverter());
        return template;
    }

    // 同一个配置类中,Bean 方法可以相互调用(Spring 会处理依赖)
    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserServiceImpl(userRepository);
    }

    // 使用 @Qualifier 精确指定要注入的 Bean
    @Bean
    public PaymentService paymentService(
            @Qualifier("alipay") PaymentGateway alipayGateway) {
        return new AlipayPaymentService(alipayGateway);
    }

    @Bean("alipay")  // 指定 Bean 名称为 "alipay"
    public PaymentGateway alipay() {
        return new AlipayGateway();
    }

    @Bean("wechatpay")
    public PaymentGateway wechatpay() {
        return new WechatPayGateway();
    }
}

3. @Import 导入配置

将多个配置类聚合在一起,或导入第三方配置:

java
// 主配置类
@SpringBootApplication
@Import({ DataConfig.class, SecurityConfig.class })
public class AppConfig {
    // ...
}

// DataConfig.java
@Configuration
public class DataConfig {
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

依赖注入的方式

构造器注入(推荐)

Spring Boot 2.6+ 推荐使用构造器注入,优势在于:

  • 不可变性:final 字段必须在构造时初始化
  • 强制依赖声明:缺少必要依赖时编译期即报错
  • 天然可测试:无需 Spring 容器即可实例化测试
java
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;
    private final MessagePublisher messagePublisher;

    // ✅ 构造器注入(推荐)
    public OrderService(
            OrderRepository orderRepository,
            PaymentGateway paymentGateway,
            MessagePublisher messagePublisher) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
        this.messagePublisher = messagePublisher;
    }

    // 只有一个构造器时,@Autowired 可省略(Spring 自动注入)
    public void placeOrder(Order order) {
        orderRepository.save(order);
        paymentGateway.process(order.getPayment());
        messagePublisher.publish("order.placed", order);
    }
}

Spring Framework 4.x 就引入了构造器注入的隐式支持,当类只有一个构造器时,@Autowired 可以省略。

Setter 注入

通过 setXxx() 方法注入,字段可选:

java
@Service
public class NotificationService {

    private EmailService emailService;
    private SmsService smsService;

    // Setter 注入(可选依赖)
    @Autowired(required = false)
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    @Autowired
    public void setSmsService(SmsService smsService) {
        this.smsService = smsService;
    }
}

字段注入(不推荐)

java
@Service
public class BadExample {

    // ❌ 字段注入:难以测试、不支持不可变字段
    @Autowired
    private UserRepository userRepository;

    // ❌ @Inject 是 Java 标准 CDI 注解,与 @Autowired 等效
    @Inject
    private CacheService cacheService;
}

@Autowired 与 @Qualifier 精确注入

当存在多个同类型 Bean 时,需要精确指定注入哪一个:

java
// 定义两个实现了同一接口的类
@Component
public class InMemoryUserRepository implements UserRepository {
    // 内存实现
}

@Component
public class JpaUserRepository implements UserRepository {
    // JPA 实现
}

// 方式1:@Qualifier 指定 Bean 名称
@Service
public class UserServiceImpl {
    @Autowired
    @Qualifier("jpaUserRepository")  // 指定注入 JpaUserRepository
    private UserRepository userRepository;
}

// 方式2:构造器参数 + @Qualifier
@Service
public class UserServiceImpl {
    private final UserRepository userRepository;

    public UserServiceImpl(
            @Qualifier("jpaUserRepository") UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// 方式3:@Primary 指定默认实现
@Component
@Primary  // 标注为默认实现,@Autowired 未指定时使用此 Bean
public class JpaUserRepository implements UserRepository {
    // ...
}

@Resource 与 @Inject

Java EE 标准注解也可用于注入:

java
// @Resource:默认按字段名匹配 Bean(JSK 330 标准)
@Resource
private UserRepository userRepository;

// @Inject:功能同 @Autowired(Java CDI 标准)
@Inject
private UserRepository userRepository;

Bean 作用域

Spring 默认创建的 Bean 是单例(singleton)的,整个容器只有一个实例:

java
@Component
@Scope("singleton")  // 默认作用域,可省略
public class Counter {
    private int count = 0;
}

// @Configuration 类默认也是 singleton
@Configuration
public class AppConfig {
    // ...
}

Spring 支持的 Bean 作用域:

作用域说明使用场景
singleton容器中只有一个实例默认,适用于无状态 Bean
prototype每次注入都创建新实例有状态 Bean
request每次 HTTP 请求创建一个实例Spring MVC Web 请求作用域
session每次 HTTP Session 创建一个实例Web 会话作用域
applicationServletContext 生命周期内唯一Web 应用级全局状态
websocketWebSocket 生命周期内唯一WebSocket 作用域
java
// 原型作用域:每次注入/获取都创建新实例
@Component
@Scope("prototype")
public class ShoppingCart {
    private final List<Item> items = new ArrayList<>();
    // ...
}

// 使用 ObjectFactory / ObjectProvider 按需获取原型 Bean
@Service
public class OrderService {
    @Autowired
    private ObjectProvider<ShoppingCart> cartProvider;

    public void checkout() {
        // 每次调用 getObject() 都创建新的 ShoppingCart 实例
        ShoppingCart cart = cartProvider.getObject();
        // ...
    }
}

@Primary 与 @Order 优先级

@Primary 指定默认 Bean

java
@Component
@Primary  // 同类型 Bean 中优先级最高
public class DefaultCacheService implements CacheService {
    // ...
}

@Order 控制 Bean 注册顺序

@Order 控制同一类型的多个 Bean 被 List<Interface> 注入时的顺序(数值越小优先级越高):

java
@Configuration
public class ValidatorConfig {

    @Bean
    @Order(1)
    public Validator emailValidator() {
        return new EmailValidator();
    }

    @Bean
    @Order(2)
    public Validator phoneValidator() {
        return new PhoneValidator();
    }

    @Bean
    @Order(3)
    public Validator passwordValidator() {
        return new PasswordValidator();
    }
}

// 注入所有 Validator,按 @Order 顺序排列
@Service
public class RegistrationService {
    @Autowired
    private List<Validator> validators;  // [email, phone, password]

    public void validate(User user) {
        validators.forEach(v -> v.validate(user));
    }
}

@Configuration 的代理机制

@Configuration 类默认被 CGLIB 代理,以保证 @Bean 方法之间的调用也经过容器代理:

java
@Configuration
public class AppConfig {

    @Bean
    public ServiceA serviceA() {
        // 调用 serviceB() 时返回的是容器中的 Bean,不是 new ServiceB()
        return new ServiceA(serviceB());
    }

    @Bean
    public ServiceB serviceB() {
        return new ServiceB();
    }
}

如果将 @Configuration 改为 @Component,CGLIB 代理会被禁用,@Bean 方法调用会创建新的实例,这通常不是你想要的行为。

小结

  • 构造器注入是 Spring Boot 3.x 推荐的方式,支持不可变性,强制声明依赖
  • @Component 体系适合业务组件,@Configuration + @Bean 适合第三方库配置
  • 多个同类型 Bean 使用 @Qualifier 按名称精确注入,@Primary 标记默认实现
  • Bean 作用域中 singleton 是默认且最常用的,prototype 适合有状态对象
  • @Order 控制同类型多个 Bean 的注册和应用顺序

下一篇文章我们将学习 RESTful API 与 Web 开发,掌握控制器层开发的全部核心技能。

#springboot #spring #ioc #dependency-injection #bean

评论

A

Written by

AI-Writer

Related Articles

springboot
#3

依赖注入与 Bean 管理

深入理解 Spring IoC 容器核心概念、@Bean/@Component/@Configuration 注解使用、构造器注入与 Setter 注入、@Autowired 与 @Qualifier 精确注入、Bean 作用域与优先级控制

Read More
springboot
#8

单元测试与集成测试

@SpringBootTest 集成测试、@MockBean 模拟依赖、@WebMvcTest 切片测试 REST 接口、AssertJ 断言库、测试数据库配置与 Spring Boot Test 新特性

Read More
springboot
#7

Spring Security 安全认证

Spring Security 6.x 核心架构、认证与授权概念、基于 JWT 的无状态登录实现、OAuth 2.0 资源服务器入门、@PreAuthorize 方法级安全注解

Read More