Spring 通过类路径扫描自动发现并注册 Bean,这彻底告别了 XML 时代逐个声明 <bean> 的繁琐。而扫描的核心机制,就是这四个看似相似却各有分工的注解:@Component、@Repository、@Service 和 @Controller。
2.3.1 它们都是“让 Spring 管理这个类”的标记
无论你使用哪一个,最终目的都是告诉 Spring:“请为这个类创建 Bean,并加入到容器中”。在底层,@Repository、@Service 和 @Controller 全部由 @Component 派生而来:
// Spring 源码中的定义
@Component
public @interface Service { }
@Component
public @interface Repository { }
@Component
public @interface Controller { }
所以,从纯技术角度看,将 @Service 换成 @Component,你的应用依然可以正常运行,Spring 都会将其识别为候选 Bean。但若止步于此,就严重低估了这三枚特化注解的实际价值。
2.3.2 分层标记:让代码“自说明”
项目规模一大,成百上千的 Bean 会让维护变得困难。这套注解首先充当了架构边界的标识:
- @Controller:明确宣告这是一个 Web 层组件,用于处理 HTTP 请求。通常与
@RequestMapping等 MVC 注解配合,解析请求参数,调用业务层,返回响应数据或视图。 - @Service:标记 业务逻辑层。这是领域逻辑的居所,负责事务编排、业务校验、流程控制,通常注入
@Repository并对外暴露高层次的用例方法。 - @Repository:标记 数据访问层。它封装了对数据库、缓存或任何外部存储的访问,负责数据的持久化与查询。
- @Component:通用的“组件”标记。当一个类不属于典型三层,但又需要被容器管理时(如工具类、转换器、消息监听器),就用它。
这种语义约定极大提升了可读性。任何一个有经验的开发者看到 @Repository,无需查看代码就知道它承担着数据访问的角色,应该没有业务逻辑混入;看到 @Controller,就知道它是 API 入口,不能包含事务性操作。通过注解进行分层治理,比注释或文档更加可靠。
2.3.3 @Repository 的额外福利:异常转换
@Repository 并非只有标签意义,它实实在在地增强了框架行为。Spring 利用它做了一层关键的“异常翻译”。
JDBC、JPA、MyBatis 等持久化技术各自抛出的原生异常差异巨大:底层可能是 SQLException、HibernateException、PersistenceException。如果直接暴露给业务层,Service 就不得不与具体数据访问技术耦合。
Spring 通过 @Repository 激活 PersistenceExceptionTranslationPostProcessor,会自动将特定持久化异常转译为 Spring 统一的 DataAccessException 子类。例如:无论底层是 MySQL 还是 Oracle,主键冲突都会转换为 DataIntegrityViolationException。这样,Service 层只需面向 Spring 的异常体系编程,完全屏蔽了持久化技术的差异,而且这些异常都是非受检的,不必强制编写 catch 块。
@Repository
public class JpaOrderRepository implements OrderRepository {
@PersistenceContext
private EntityManager em;
@Override
public void save(Order order) {
em.persist(order); // 抛出的各种异样会被自动转换为 Spring 数据异常
}
}
若无此转换,Service 可能会直接触碰 javax.persistence.PersistenceException,更换持久化框架就成为灾难。
2.3.4 @Service 的增值:事务切面的理想锚点
@Service 本身并没有添加额外的技术行为,但它作为业务服务的明确标识,为 AOP 切面的切入点定义提供了最自然的锚点。例如声明式事务管理:
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable { ... }
}
更常见的是,Spring 事务管理器默认通过代理模式为标注了 @Transactional 的 Bean 增加事务行为,而这些 Bean 几乎总是位于 @Service 层。将 @Transactional 放在 Controller 或 Repository 上都是架构上的坏味道,@Service 正好划定了事务应该作用的粒度。
除此之外,分层标记还是安全框架、监控平台、日志管道的共同基座。你可以在一个地方定义规则:“捕获所有 @Service 层方法抛出的异常并发送报警”,或“对所有 @Controller 进行请求日志记录”,而无需考虑具体类名。
2.3.5 组件扫描如何找到它们
要让这些注解生效,必须在某个 @Configuration 类上启用组件扫描:
@Configuration
@ComponentScan(basePackages = "com.example.myapp")
public class AppConfig { }
Spring Boot 则更进一步:@SpringBootApplication 内部已经组合了 @ComponentScan,默认扫描启动类所在包及其子包。这是“零配置”感觉得以成真的基础。
在扫描阶段,Spring 遍历类路径,发现带有 @Component(及其衍生注解)的类,便将其注册为 Bean Definition,并根据注解属性(如 @Service("orderService"))确定 Bean 名称。之后,容器通过依赖注入完成组装。
2.3.6 实践建议
- 坚持分层标记,不要偷懒统一使用
@Component。虽然能跑,但失去了自说明性和异常转换等增值能力,且使架构边界模糊。 @Controller不要滥用为 Service。Controller 应尽可能薄,只做参数校验与结果封装,避免业务逻辑泄漏到 Web 层。@Repository在手动实现 DAO 时尤其重要,如果使用 Spring Data JPA 则它的代理会自动具备异常转换功能,但仍要保留该注解标明身份。- 当某个组件确实不属于三层,再使用
@Component,例如自定义的 Spring 配置处理器、通用工具 Bean 等。 - 统一使用构造器注入配合这些注解,通过
final字段与@RequiredArgsConstructor(Lombok)可获得最整洁的代码。
这四个注解是 Spring 应用的骨骼。它们让类路径扫描得以有序运转,让开发者一眼看懂代码的职责归属,并通过微妙的框架增强将枯燥的技术细节悄然屏蔽。用好它们,是写出清晰、健壮的 Spring 应用的第一步。