乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@Component 详解

本章定位:理解 Spring 组件扫描的根基注解。@Component 是所有受管 Bean 的"通用门票",@Service、@Repository、@Controller 均派生自它。


定义与作用

@Component 是 Spring 框架提供的通用组件注解,用于标记一个类为 Spring IoC 容器管理的 Bean。

解决的痛点:在没有 @Component 之前,开发者必须在 XML 配置中逐个声明 <bean> 标签:

<!-- 痛点:XML 配置冗长,与 Java 代码分离 -->
<bean id="studentDao" class="com.feixiang.student.dao.StudentDao"/>
<bean id="studentService" class="com.feixiang.student.service.StudentService"/>
<bean id="emailSender" class="com.feixiang.student.util.EmailSender"/>

@Component 让 Bean 的声明靠近代码,配合组件扫描实现自动注册,彻底告别 XML 的繁琐。

元注解地位

@Service、@Repository、@Controller 本质上都是 @Component 的特化(Specialization),它们在功能上完全等价,仅通过名称传达不同的语义意图:


适用位置与常用属性

适用位置

@Component 只能标注在类级别。

@Component
public class EmailSender {
    public void send(String to, String subject) {
        // 发送邮件逻辑
    }
}

常用属性

属性类型默认值说明
valueString""指定 Bean 的名称(id)。默认使用类名首字母小写,如 EmailSender → emailSender
// 显式指定 Bean 名称
@Component("mailService")
public class EmailSender {
}

// 等价写法
@Component(value = "mailService")
public class EmailSender {
}

核心原理:组件扫描流程

当容器遇到 @ComponentScan("com.feixiang.student") 时,会触发以下扫描流程:

关键实现细节:

  1. ASM 扫描:Spring 使用 ASM 库直接读取 .class 文件的二进制内容,而非加载类到 JVM。这意味着即使类有编译依赖缺失,扫描阶段也不会报错。
  2. 元注解递归:Spring 不仅检查类是否直接标注 @Component,还会递归检查元注解链。因此自定义注解只要标注了 @Component,也会被识别。
  3. Bean 名称生成:默认使用 Introspector.decapitalize() 将类名首字母小写。如果类名前两个字母都是大写(如 URLParser),则保持原样。

完整示例:飞翔科技公司的通知组件

场景简述

飞翔科技的学生管理系统需要多种通知渠道:邮件通知、短信通知、企业微信通知。架构师白歌要求这些通知工具都作为通用组件纳入 Spring 容器管理,由业务层按需注入。

操作前:手动实例化,无法统一管理

// 操作前:NotificationManager.java —— 手动 new,无法切换实现
public class NotificationManager {
    private EmailSender emailSender = new EmailSender();
    private SmsSender smsSender = new SmsSender();
    private WechatSender wechatSender = new WechatSender();

    public void notifyStudent(Student student, String message) {
        emailSender.send(student.getEmail(), "飞翔科技通知", message);
        smsSender.send(student.getPhone(), message);
        wechatSender.send(student.getWechatId(), message);
    }
}

痛点:

  1. 三个发送器都在 NotificationManager 内部硬编码创建
  2. 如果 EmailSender 需要连接池配置,配置信息无法外部化
  3. 无法在不修改源码的情况下替换为 MockSender 做测试

操作后:使用 @Component 纳入容器管理

// 操作后:EmailSender.java —— 通用组件
@Component
public class EmailSender {
    private static final Logger logger = LoggerFactory.getLogger(EmailSender.class);

    public void send(String to, String subject, String content) {
        logger.info("[邮件通知] 收件人:{},主题:{},内容:{}", to, subject, content);
    }
}
// 操作后:SmsSender.java —— 通用组件
@Component
public class SmsSender {
    private static final Logger logger = LoggerFactory.getLogger(SmsSender.class);

    public void send(String phone, String content) {
        logger.info("[短信通知] 手机号:{},内容:{}", phone, content);
    }
}
// 操作后:WechatSender.java —— 通用组件
@Component
public class WechatSender {
    private static final Logger logger = LoggerFactory.getLogger(WechatSender.class);

    public void send(String wechatId, String content) {
        logger.info("[企业微信通知] 用户:{},内容:{}", wechatId, content);
    }
}
// 操作后:NotificationManager.java —— 依赖注入使用
@Component
public class NotificationManager {
    private final EmailSender emailSender;
    private final SmsSender smsSender;
    private final WechatSender wechatSender;

    public NotificationManager(EmailSender emailSender,
                              SmsSender smsSender,
                              WechatSender wechatSender) {
        this.emailSender = emailSender;
        this.smsSender = smsSender;
        this.wechatSender = wechatSender;
    }

    public void notifyStudent(Student student, String message) {
        emailSender.send(student.getEmail(), "飞翔科技通知", message);
        smsSender.send(student.getPhone(), message);
        wechatSender.send(student.getWechatId(), message);
    }
}
// 操作后:AppConfig.java —— 启用组件扫描
@Configuration
@ComponentScan("com.feixiang.student")
public class AppConfig {
}
// 操作后:启动类
public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        NotificationManager manager = ctx.getBean(NotificationManager.class);

        Student student = new Student();
        student.setName("小崔");
        student.setEmail("xiaocui@learnto.cn");
        student.setPhone("13800138000");
        student.setWechatId("xiaocui_feixiang");

        manager.notifyStudent(student, "您的入学申请已通过,请按时报到。");
    }
}

运行结果及分析

运行输出:
[main] INFO  o.s.c.a.AnnotationConfigApplicationContext - 
    Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6d06d69c
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'emailSender'
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'smsSender'
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'wechatSender'
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'notificationManager'
[main] INFO  c.f.s.component.EmailSender - [邮件通知] 收件人:xiaocui@learnto.cn,主题:飞翔科技通知,内容:您的入学申请已通过,请按时报到。
[main] INFO  c.f.s.component.SmsSender - [短信通知] 手机号:13800138000,内容:您的入学申请已通过,请按时报到。
[main] INFO  c.f.s.component.WechatSender - [企业微信通知] 用户:xiaocui_feixiang,内容:您的入学申请已通过,请按时报到。

关键观察:

  1. 四个组件(emailSender、smsSender、wechatSender、notificationManager)都被自动注册为 Bean
  2. Bean 名称默认采用类名首字母小写(EmailSender → emailSender)
  3. NotificationManager 通过构造器注入获取三个发送器,无需手动 new

自定义派生注解

由于 @Component 是元注解,可以创建自定义的派生注解来表达特定语义:

// 自定义通知组件注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface NotificationChannel {
    String value() default "";
    int priority() default 0;
}
// 使用自定义注解
@NotificationChannel(priority = 1)
public class EmailSender {
    // 同样会被组件扫描识别
}

这在大型项目中非常有用:可以按自定义维度对组件进行分类和过滤。


易错场景与面试考点

反例一:忘记启用组件扫描

小崔在类上加了 @Component,但容器启动后 getBean() 报错:

// ❌ 错误:只加了 @Component,但没有 @ComponentScan
@Component
public class EmailSender {
}

@Configuration
// 缺少 @ComponentScan!
public class AppConfig {
}

运行报错:

NoSuchBeanDefinitionException: No qualifying bean of type 'com.feixiang.student.component.EmailSender' available

纠正:必须在配置类上添加 @ComponentScan 指定扫描路径:

// ✅ 正确:启用组件扫描
@Configuration
@ComponentScan("com.feixiang.student")
public class AppConfig {
}

反例二:Bean 名称冲突

小崔同时定义了两个 EmailSender:

// ❌ 错误:两个类默认 Bean 名都是 "emailSender"
@Component
public class EmailSender {
}

@Component
public class EmailSender {  // 包路径不同,但类名相同
}

启动报错:

ConflictingBeanDefinitionException: Annotation-specified bean name 'emailSender' for bean class [...EmailSender] conflicts with existing, non-compatible bean definition of same name and class [...EmailSender]

纠正方案一:显式指定不同的 Bean 名称:

@Component("smtpEmailSender")
public class EmailSender {
}

@Component("sendGridEmailSender")
public class EmailSender {
}

纠正方案二:使用包路径隔离,配合 @ComponentScan 的 basePackageClasses 精确控制扫描范围。

反例三:在接口上使用 @Component

// ❌ 错误:@Component 只能用于类,不能用于接口
@Component
public interface MessageSender {
    void send(String target, String content);
}

编译不报错,但扫描时会被忽略,因为接口无法直接实例化。

纠正:在接口的实现类上使用 @Component:

// ✅ 正确:在实现类上使用
public interface MessageSender {
    void send(String target, String content);
}

@Component
public class EmailSender implements MessageSender {
    @Override
    public void send(String target, String content) {
        // 实现
    }
}

面试高频题

Q1:@Component、@Service、@Repository、@Controller 有什么区别?

功能上完全等价,都是将类注册为 Spring Bean。区别在于语义意图:@Component 是通用组件;@Service 标记业务层;@Repository 标记数据访问层(额外提供异常转换);@Controller 标记 Web 控制器。后三者都是 @Component 的元注解派生。

Q2:Spring 如何识别 @Component 及其派生注解?

Spring 使用 ASM 库扫描类路径下的 .class 文件,读取注解元数据。对于每个类,递归检查其注解及元注解链,如果发现 @Component,则注册为 BeanDefinition。这一过程在容器启动时完成,不需要将类加载到 JVM。

Q3:@Component 标注的类,Bean 名称默认是什么?

默认使用 Introspector.decapitalize() 处理类名:首字母小写。特殊规则:如果类名前两个字母都是大写(如 URLParser),则保持原样不变(仍为 URLParser)。可以通过 @Component("customName") 显式指定。

Q4:为什么 @Component 不能用于方法或字段?

@Component 的设计目标是类级别的组件声明,它告诉 Spring"这个类需要被实例化并纳入容器管理"。方法级别的 Bean 声明使用 @Bean,字段级别的注入使用 @Autowired/@Value,它们属于不同的语义层级。

上一页
@Configuration 详解
下一页
@ComponentScan 详解