DEV Community

Liu yu
Liu yu

Posted on • Edited on

Spring Bean 创建与依赖注入教程

本文将详细讲解 Spring 框架中

  • Bean 的创建方式
  • Bean 之间依赖注入(wiring)

这两种不同维度问题。


1. Spring Bean 的创建方式

在 Spring 框架中,Bean 是由 Spring 容器管理的基本对象。可以通过以下三种主要方式创建 Bean:

1.1 使用 @Component 注解

通过在类上添加 @Component 注解,Spring 会自动扫描并将该类注册为一个 Bean。这种方式适用于直接定义一个类作为 Bean 的场景。

import org.springframework.stereotype.Component;

@Component
public class UserService {
    public void sayHello() {
        System.out.println("Hello from UserService!");
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 特点:简单直接,适合业务逻辑类。
  • 使用场景:当类是独立的、具有明确职责的组件时。
  • 注意:需要确保 @ComponentScan 开启,且类在扫描路径内。

1.2 使用 @Bean 注解在配置类中定义

@Configuration 注解的配置类中,通过 @Bean 注解定义方法返回值作为 Bean。这种方式适合需要自定义 Bean 实例化逻辑的场景。

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

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 特点:灵活,允许在方法中添加初始化逻辑。
  • 使用场景:需要控制 Bean 的创建过程或配置第三方库的 Bean。
  • 注意@Bean 方法通常在 @Configuration 类中定义,以确保 Spring 代理正确处理。

1.3 使用 XML 配置

通过 XML 配置文件定义 Bean,Spring 容器会根据 XML 配置创建 Bean。这种方式在早期 Spring 项目中常见,现在较少使用。

<bean id="userService" class="com.example.UserService"/>
Enter fullscreen mode Exit fullscreen mode
  • 特点:配置文件集中管理,适合传统项目。
  • 使用场景:遗留系统或需要与 XML 配置集成的场景。
  • 注意:XML 配置较为繁琐,维护成本较高。

2. Bean 之间的依赖注入(Wiring)

Bean 的创建解决了单个 Bean 的定义问题,而依赖注入(Dependency Injection, DI)则解决了 Bean 之间的联动问题。Spring 提供了多种方式实现 Bean 之间的依赖注入,下面介绍四种主要方式。

2.1 使用 @Autowired 注解

@Autowired 是 Spring 提供的自动注入注解,支持以下三种注入方式:

2.1.1 构造器注入

通过构造器注入依赖,推荐在需要强制依赖的场景中使用。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderService {
    private final UserService userService;

    @Autowired
    public OrderService(UserService userService) {
        this.userService = userService;
    }

    public void processOrder() {
        userService.sayHello();
        System.out.println("Order processed!");
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 优点:依赖不可变,强制注入,适合关键依赖。
  • 缺点:构造器参数过多时代码显得冗长。

2.1.2 Setter 方法注入

通过 Setter 方法注入依赖,适合可选依赖或需要动态更换依赖的场景。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderService {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 优点:灵活,允许运行时更改依赖。
  • 缺点:依赖可能未初始化,需额外检查。

2.1.3 字段注入

直接在字段上使用 @Autowired 进行注入,代码简洁但不推荐。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderService {
    @Autowired
    private UserService userService;
}
Enter fullscreen mode Exit fullscreen mode
  • 优点:代码简洁,开发效率高。
  • 缺点:难以测试,依赖隐藏,容易导致空指针问题。
  • 建议:避免使用字段注入,优先选择构造器注入。

2.2 使用 XML 配置进行注入

通过 XML 文件配置 Bean 之间的依赖关系,适合传统项目或需要集中管理配置的场景。

<bean id="userService" class="com.example.UserService"/>
<bean id="orderService" class="com.example.OrderService">
    <property name="userService" ref="userService"/>
</bean>
Enter fullscreen mode Exit fullscreen mode
  • 特点:集中式配置,适合复杂依赖关系。
  • 使用场景:遗留系统或需要与 XML 集成的项目。
  • 注意:XML 配置维护成本高,建议优先使用注解。

2.3 在 @Bean 方法中手动注入

@Configuration 类中,通过 @Bean 方法手动指定依赖关系,适合需要自定义注入逻辑的场景。

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

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }

    @Bean
    public OrderService orderService() {
        OrderService orderService = new OrderService();
        orderService.setUserService(userService());
        return orderService;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 特点:高度灵活,允许复杂的初始化逻辑。
  • 使用场景:需要控制依赖注入过程或整合第三方库。
  • 注意:手动注入需要开发者确保依赖正确性。

2.4 使用 @Resource@Inject(补充)

除了 @Autowired,Spring 还支持 JSR-250 的 @Resource 和 JSR-330 的 @Inject 注解,用于依赖注入。它们与 @Autowired 类似,但匹配规则和优先级略有不同。

  • @Resource:按名称匹配,优先于类型。
  • @Inject:与 @Autowired 类似,但需要额外的依赖(如 javax.inject)。

3. 创建与注入的维度区别

Bean 的创建和依赖注入属于 Spring 容器管理的两个不同维度:

  • Bean 创建:关注如何定义和实例化单个 Bean,解决“Bean 是什么”的问题。包括:
    • @Component:自动扫描和注册。
    • @Bean:手动定义和配置。
    • XML:通过配置文件声明。
  • 依赖注入:关注 Bean 之间的关系,解决“Bean 如何协作”的问题。包括:
    • @Autowired:自动注入(构造器、Setter、字段)。
    • XML:通过配置文件指定依赖。
    • @Bean 方法:手动注入依赖。

类比

  • 创建 Bean 就像生产零件(定义单个组件)。
  • 依赖注入就像组装零件(将组件连接成一个整体)。

4. 综合示例

以下是一个完整的示例,展示如何结合不同的创建和注入方式。

4.1 代码示例

// UserService.java
import org.springframework.stereotype.Component;

@Component
public class UserService {
    public void sayHello() {
        System.out.println("Hello from UserService!");
    }
}

// OrderService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderService {
    private final UserService userService;

    @Autowired
    public OrderService(UserService userService) {
        this.userService = userService;
    }

    public void processOrder() {
        userService.sayHello();
        System.out.println("Order processed!");
    }
}

// AppConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }

    @Bean
    public OrderService orderService() {
        OrderService orderService = new OrderService(userService());
        return orderService;
    }
}
Enter fullscreen mode Exit fullscreen mode

4.2 XML 配置示例

<beans>
    <bean id="userService" class="com.example.UserService"/>
    <bean id="orderService" class="com.example.OrderService">
        <constructor-arg ref="userService"/>
    </bean>
</beans>
Enter fullscreen mode Exit fullscreen mode

4.3 测试代码

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

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        OrderService orderService = context.getBean(OrderService.class);
        orderService.processOrder();
    }
}
Enter fullscreen mode Exit fullscreen mode

5. 总结与建议

  • Bean 创建
    • 优先使用 @Component@Bean,因为它们更符合现代 Spring 开发习惯。
    • XML 配置适合遗留系统或特殊场景。
  • 依赖注入
    • 推荐使用构造器注入(@Autowired),确保依赖不可变且易于测试。
    • 避免字段注入,减少潜在的空指针风险。
    • 对于复杂依赖关系,可使用 @Bean 方法手动注入。
  • 选择依据
    • 小型项目:使用 @Component@Autowired(构造器注入)。
    • 复杂项目:结合 @Bean@Configuration 实现灵活配置。
    • 遗留项目:保留 XML 配置,逐步迁移到注解方式。

🧭 配置方式的演进:从 XML 到注解,到最终的组合实践

回顾我们上面讲到的各种配置方式与注入方式,它们并不是孤立存在的,而是随着 Spring 框架本身的发展不断演化出来的解决方案。

🔍 为什么会出现这么多种配置方式?

  1. 早期阶段(Spring 1.x - XML 配置)
    Spring 最初强调“配置即一切”,所以 XML 是唯一入口,所有对象、依赖、AOP 都写在 XML 中。好处是解耦、灵活,但冗长、易出错

  2. 中期转型(Spring 2.5 - 注解驱动)
    为减少 XML 配置,Spring 引入 @Component@Autowired 等注解,推崇“约定优于配置”,降低了上手门槛,但也增加了控制权下放的风险(开发者必须清楚依赖图)。

  3. 成熟阶段(Spring 3.0+ - JavaConfig)
    Spring 推出 @Configuration + @Bean,让你用 Java 编程的方式管理 Bean 定义,既保留了注解的灵活性,也重新获得了对配置的可控性与清晰度。

  4. 现代推荐(Spring Boot)
    Spring Boot 在此基础上加入自动配置(AutoConfiguration),让大多数常见组件都能“开箱即用”。你几乎不用配置就能跑起来,但在复杂场景下,还是要理解底层配置机制。


💡 对实际项目的几点建议

  1. 新项目尽量用构造器注入 + JavaConfig(@Configuration)组合。
    它语义清晰、IDE 友好、适合重构和测试。

  2. 字段注入虽然简单,但慎用在核心业务 Bean 上。
    它让依赖关系隐形,调试、测试都更麻烦,Spring 团队也不推荐。

  3. 理解 XML 配置的原理,但别再用它开发新项目。
    有时读旧项目源码、集成老系统还是会遇到它,但没必要继续写它。

  4. 注解方式适合配合框架做扫描注入,但别滥用。
    比如你想注册一个第三方 SDK Bean,最好在 @Configuration 中用 @Bean 明确声明,而不是用 @Component 暴力扫入。


🧩 最终的理解:不是“哪种最好”,而是“在什么场景用什么最合适”

Spring 提供了这么多方式,并不是让你“全都会用”,而是希望你能根据项目规模、团队习惯、架构复杂度来做出权衡。

而掌握它们的前提,是理解每一种配置/注入机制的本质和设计动因——这也正是这篇文章的初衷 😊


Top comments (0)