Spring 组件:一场关于“对象如何被组织起来”

举报
yd_236998400 发表于 2026/10/24 23:41:46 2026/10/24
【摘要】 Spring 组件:一场关于“对象如何被组织起来”节目类型:技术播客逐字稿主题:Spring Framework 与 Spring Boot 中的核心组件建议时长:15~20 分钟适合听众:Java 初学者、正在使用 Spring Boot 的开发者、想弄清 @Component 背后机制的工程师 节目简介很多人第一次接触 Spring,记住的是一串注解:@Component、@Servi...

Spring 组件:一场关于“对象如何被组织起来”

节目类型:技术播客逐字稿
主题:Spring Framework 与 Spring Boot 中的核心组件
建议时长:15~20 分钟
适合听众:Java 初学者、正在使用 Spring Boot 的开发者、想弄清 @Component 背后机制的工程师


节目简介

很多人第一次接触 Spring,记住的是一串注解:@Component、@Service、@Repository、@Autowired、@Bean。注解记了不少,可一到实际项目里,还是会问:这个对象到底是谁创建的?为什么能自动注入?@Service 和 @Component 究竟差在哪儿?

这期节目不把 Spring 讲成一张注解背诵表,而是从一个核心问题出发:Spring 是如何把一堆普通 Java 对象,组织成一个可配置、可扩展、可维护的应用系统的?


片头:Spring 到底解决了什么问题?

主播:

欢迎收听本期节目。今天我们聊一个看起来老生常谈、实际上经常被用错的话题:Spring 组件。

先想象一个没有 Spring 的业务系统。控制器需要服务,服务需要仓储,仓储需要数据库客户端。于是代码里到处都是这样的写法:

public class OrderController {
    private final OrderService orderService;

    public OrderController() {
        this.orderService = new OrderService(
            new OrderRepository(new DatabaseClient())
        );
    }
}

这段代码并不是不能运行,问题在于 OrderController 不仅负责处理请求,还负责决定依赖怎么创建、怎么组合、什么时候释放。业务代码和对象组装逻辑搅在一起,项目一大,维护就开始变得像拆一团耳机线。

Spring 的核心思路是:把对象的创建、组装、生命周期和部分横切能力交给容器管理,业务类本身尽量保持普通 Java 对象的形态。

所以,Spring 组件不是某种神秘的“特殊对象”,它本质上仍然是一个 Java 对象,只是它被 Spring 容器发现、注册、创建并管理了。


第一部分:什么叫 Spring 组件?

1. 组件的本质是 Bean

在 Spring 语境里,最重要的词不是注解,而是 Bean。

Bean 可以理解为:交给 Spring ApplicationContext 管理的对象。容器会保存它的定义,并根据配置完成实例化、依赖注入、初始化和销毁。

一个最简单的组件是这样:

@Component
public class PriceCalculator {
    public BigDecimal calculate(BigDecimal price) {
        return price.multiply(new BigDecimal("0.90"));
    }
}

当 Spring 扫描到这个类时,会把它注册为 Bean。之后其他组件可以直接依赖它:

@Service
public class OrderService {
    private final PriceCalculator priceCalculator;

    public OrderService(PriceCalculator priceCalculator) {
        this.priceCalculator = priceCalculator;
    }
}

这里没有手动调用 new PriceCalculator(),也没有在 OrderService 里编写查找逻辑。对象之间的关系由容器完成,这就是控制反转,也就是 IoC,Inversion of Control。

2. @Component 和几个常见派生注解

Spring 提供了一组语义化注解,它们大多都可以被组件扫描识别:

注解 典型用途 关键含义
@Component 通用组件 这是一个由 Spring 管理的组件
@Service 业务服务层 表达业务逻辑和用例编排
@Repository 数据访问层 表达持久化边界,并支持异常转换
@Controller MVC 控制器 处理 Web 请求并返回视图
@RestController REST 接口 控制器方法默认返回响应体
@Configuration 配置类 声明 Bean 和应用配置

从继承关系上看,@Service、@Repository 和 @Controller 都是 @Component 的特殊形式。它们并没有把 Java 类变成不同的语言结构,主要价值是表达架构意图。

不过 @Repository 有一点额外价值:Spring 可以把底层持久化异常转换成统一的数据访问异常体系。注解不是装饰品,语义和框架行为要一起看。


第二部分:@Component 和 @Bean,到底怎么选?

这是 Spring 初学者最容易混淆的一组概念。

@Component:标记自己的业务类

当一个类由我们自己编写,并且适合通过组件扫描自动发现,可以使用 @Component 或它的语义化派生注解:

@Service
public class UserService {
    // 业务逻辑
}

@Bean:在配置中显式声明对象

当对象来自第三方库,不能直接修改源码,或者创建过程需要明确配置时,使用 @Bean:

@Configuration
public class ClientConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.findAndRegisterModules();
        return mapper;
    }
}

简单来说:能给自己写的类加组件注解,就让它被扫描;需要精确控制构造过程,就用 @Bean。

还有一个经常被忽略的事实:@Configuration 类默认会被 Spring 增强,确保同一个配置方法在容器语境下返回的是同一个 Bean,而不是每调用一次就重新创建一个对象。这也是为什么配置类不能只当成普通工具类来理解。


第三部分:依赖注入,Spring 是怎么把对象接上的?

依赖注入就是 DI,Dependency Injection。它的含义很直白:对象需要什么依赖,不自己创建,由外部注入进来。

推荐构造器注入

@Service
public class PaymentService {
    private final PaymentGateway paymentGateway;
    private final PaymentRepository paymentRepository;

    public PaymentService(
            PaymentGateway paymentGateway,
            PaymentRepository paymentRepository) {
        this.paymentGateway = paymentGateway;
        this.paymentRepository = paymentRepository;
    }
}

构造器注入有三个优点:

  1. 依赖是显式的,类需要什么一眼就能看出来。
  2. final 字段保证对象创建完成后依赖不会被替换。
  3. 单元测试时可以直接传入 fake 或 mock,不需要启动 Spring 容器。

如果类只有一个构造器,现代 Spring 通常不要求再额外写 @Autowired。这让代码更接近普通 Java,也减少了注解噪音。

出现多个候选 Bean 怎么办?

比如系统里有两个支付网关:

@Component("wechatGateway")
public class WechatPaymentGateway implements PaymentGateway {
}

@Component("alipayGateway")
public class AlipayPaymentGateway implements PaymentGateway {
}

此时直接注入 PaymentGateway 会出现歧义。可以用 @Qualifier 指定名称:

public PaymentService(
        @Qualifier("wechatGateway") PaymentGateway paymentGateway) {
    this.paymentGateway = paymentGateway;
}

也可以用 @Primary 指定默认实现。工程里更推荐使用有意义的接口和限定名称,别把 Bean 名称写成一串看不出业务含义的字母。


第四部分:组件生命周期与作用域

Spring 不只负责创建 Bean,还会管理它的生命周期。

默认情况下,Bean 是 singleton 作用域,也就是在一个 Spring 容器中只创建一个实例:

@Component
public class TokenCache {
}

常见作用域包括:

  • singleton:容器内单例,默认值。
  • prototype:每次获取时创建新实例。
  • request:每个 HTTP 请求一个实例,适用于 Web 场景。
  • session:每个 HTTP Session 一个实例。

这里有个非常实用的工程判断:无状态服务适合使用默认 singleton;有可变共享状态的组件必须认真考虑线程安全。

单例不等于“所有线程都安全”。如果一个 singleton 里放了普通可变字段,它可能被多个请求线程同时访问,问题就不是 Spring 注解能替你解决的了。

生命周期回调也可以通过注解表达:

@Component
public class SearchIndex {

    @PostConstruct
    public void start() {
        // 加载索引或建立连接
    }

    @PreDestroy
    public void stop() {
        // 释放资源
    }
}

资源型组件要特别注意关闭逻辑。连接池、线程池、文件句柄,打开之后不关,最后都得由线上告警来提醒你,代价通常比较贵。


第五部分:Spring Boot 把这些组件串了起来

如果说 Spring Framework 提供了容器和组件模型,那么 Spring Boot 做的事情,是把大量常见配置自动化,让应用可以更快启动。

1. 自动配置

@SpringBootApplication 通常组合了三个能力:

  • @SpringBootConfiguration:表明这是应用配置入口。
  • @EnableAutoConfiguration:根据 classpath 和配置自动装配框架能力。
  • @ComponentScan:扫描并注册应用组件。

也就是说,我们写下一个启动类之后,Spring Boot 会根据依赖、配置和条件判断,自动准备 Web、JSON、数据库等基础设施。

2. Starter

Starter 是一组经过整理的依赖集合。例如 Web 应用常见的 spring-boot-starter-web,会带上 Spring MVC、内嵌服务器和 JSON 相关能力。

它解决的不是“有没有依赖”的问题,而是“哪些依赖应该一起使用、版本怎么保持兼容”的问题。

3. 自动配置不是魔法

自动配置通常基于条件注解,例如:

  • classpath 中存在某个类;
  • 容器中还没有用户自定义的某个 Bean;
  • 配置文件中打开了某个开关;
  • 当前应用处于特定环境。

当自动配置和预期不一致时,正确的排查路径是查看条件评估报告、启动日志和最终的 Bean 定义,而不是盲目地重复添加注解。注解叠得越多,问题通常越像一锅被搅乱的粥。


第六部分:AOP 和代理,为什么 @Transactional 有时“不生效”?

Spring 组件体系里还有一类非常关键的能力:AOP,面向切面编程。

事务、缓存、权限校验、日志、重试,这些能力都属于横切关注点。Spring 常通过代理对象,在目标方法前后织入额外行为。

例如:

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        // 写入订单
        // 扣减库存
    }
}

调用方拿到的往往不是原始 OrderService,而是 Spring 创建的代理对象。代理先开启事务,再调用目标方法,最后根据执行结果提交或回滚。

这也解释了一个经典问题:同一个类内部通过 this.createOrder() 调用方法时,可能绕过代理,导致事务、缓存或切面不生效。

排查这类问题时,要问的不是“注解写没写”,而是:

  1. 这个 Bean 是否真的被 Spring 管理?
  2. 调用方拿到的是不是代理对象?
  3. 调用是否经过了代理边界?
  4. 事务管理器和数据库配置是否匹配?

这比盯着注解颜色看半天靠谱得多。


第七部分:一套小型组件架构示例

把前面的内容串起来,可以得到这样的分层结构:

@RestController
@RequestMapping("/orders")
public class OrderController {
    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }
}
@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final PricingService pricingService;

    public OrderService(
            OrderRepository orderRepository,
            PricingService pricingService) {
        this.orderRepository = orderRepository;
        this.pricingService = pricingService;
    }
}
@Repository
public class OrderRepository {
    private final JdbcTemplate jdbcTemplate;

    public OrderRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

在这套结构中:

  • Controller 负责协议适配,不承载核心业务判断;
  • Service 负责编排业务用例和事务边界;
  • Repository 负责数据访问;
  • JdbcTemplate 由 Spring Boot 自动配置或显式配置提供;
  • 构造器注入把组件关系表达得清清楚楚。

这就是 Spring 组件的价值:它不是让代码少写几个 new,而是让系统的依赖关系变得显式、可替换、可测试。


常见误区速记

误区一:加了 @Component 就一定能注入

不一定。类必须位于组件扫描范围内,而且不能存在 Bean 定义冲突。包结构、扫描配置、条件注解都可能影响最终结果。

误区二:@Autowired 越多越专业

不是。字段注入隐藏依赖,也让测试和对象不变性变差。优先使用构造器注入。

误区三:Spring Bean 都是全局单例

默认是 singleton,但作用域可以改变,Web 场景也有 request 和 session 作用域。更重要的是,单例对象里的共享状态可能带来并发问题。

误区四:@Service 会自动让代码变成事务服务

不会。@Service 主要表达层次语义;事务需要 @Transactional、事务管理器、代理机制和正确的数据源配置共同参与。

误区五:启动成功就说明组件配置正确

启动成功只代表容器完成了初始化。真正的组件问题可能出现在运行时:事务未生效、代理丢失、循环依赖、配置环境错误,以及某个 singleton 的共享字段发生竞态。


结尾:别背注解,先看对象关系

主播:

今天我们从 Bean、组件扫描、依赖注入,一路聊到生命周期、Spring Boot 自动配置和 AOP 代理。

如果只记住一句话,那就是:Spring 组件的核心不是注解,而是对象管理和依赖关系。

@Component 解决的是“这个类可以被容器发现”;@Bean 解决的是“这个对象应该如何被显式创建”;构造器注入解决的是“组件需要哪些依赖”;作用域和生命周期解决的是“对象应该活多久”;AOP 代理解决的是“事务、缓存和日志如何在不侵入业务代码的情况下织入”。

当你理解了这些关系,Spring 就不再是一堆凭记忆试出来的注解,而是一套有清晰边界的对象组织系统。

感谢收听本期节目。下一次再看到 @Service、@Repository 或 @Transactional,别只问“这个注解怎么用”,顺手再问一句:这个对象是谁创建的?调用有没有经过代理?依赖关系是否清楚?

这几个问题问明白,Spring 的大半功夫也就被你啃干净了——咳,技术播客差点串台。我们下期见。


节目配套:一句话总结

Spring 组件 = 普通 Java 对象 + 容器管理 + 依赖注入 + 生命周期控制 + 可选的代理增强。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。