当前位置: 首页 > news >正文

如何通过注解(@Component 等)声明一个 Bean?Spring 是如何找到这些注解的?

如何通过注解声明一个 Bean?

最核心的注解是 @Component。它是一个通用的、万能的标记。

import org.springframework.stereotype.Component;@Component // 嘿,Spring!这个类是一个组件,请帮我创建一个Bean。
public class MyGenericComponent {public void doSomething() {System.out.println("Doing something...");}
}

为了让代码的意图更清晰,Spring 提供了几个继承自 @Component 的、具有特定语义的注解。它们在功能上与 @Component 几乎完全相同,但能更好地表达该组件在分层架构中的角色。

  1. @Service: 用于标注业务逻辑层(Service Layer)的组件。

    import org.springframework.stereotype.Service;@Service // 这个组件是处理业务逻辑的
    public class UserServiceImpl implements UserService {// ...
    }
    
  2. @Repository: 用于标注数据访问层/持久层(Repository/DAO Layer)的组件。

    • 额外功能: 除了标记为 Bean,@Repository 还能将特定于数据访问技术的异常(如 JDBC 的 SQLException)转译为 Spring 统一的、与技术无关的 DataAccessException 异常体系。这使得你的业务层代码无需处理底层的特定异常。
    
    import org.springframework.stereotype.Repository;@Repository // 这个组件是用来访问数据库的
    public class MySqlUserRepository implements UserRepository {// ...
    }
    
  3. @Controller / @RestController: 用于标注表现层/控制层(Presentation Layer)的组件,通常用于 Spring MVC 或 Spring WebFlux。

    import org.springframework.web.bind.annotation.RestController;@RestController // 这个组件是处理Web请求的API控制器
    public class UserController {// ...
    }
    
  4. @Configuration: 用于标注配置类。配置类本身也是一个 Bean,它通常包含用 @Bean 注解的方法,用于以 Java 代码的方式定义其他的 Bean。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Bean;@Configuration // 这个类是专门用来定义Bean的
    public class AppConfig {@Bean // 这个方法返回的对象也要作为一个Bean来管理public MyCustomService myCustomService() {return new MyCustomService();}
    }
    

总结:你只需要根据组件的角色,在类上加上 @Component@Service@Repository@Controller 中的一个,就完成了 Bean 的声明。


Spring 是如何找到这些注解的?(Spring 做什么)

我们仅仅在类上加了注解,Spring 是如何知道要去扫描这些类并创建 Bean 的呢?答案是:组件扫描(Component Scanning)

这个过程就像雷达扫描一样,Spring 会在你指定的“空域”(包路径)中进行扫描,寻找带有特定注解的类。

这个过程由 @ComponentScan 注解触发。

工作流程详解:
  1. 启动指令:@ComponentScan
    你必须在一个配置类(一个标注了 @Configuration 的类)上使用 @ComponentScan 来告诉 Spring:“请开始扫描!”。

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;@Configuration
    @ComponentScan(basePackages = "com.myapp.service") // 告诉Spring:请扫描这个包以及它的所有子包
    public class AppConfig {// ...
    }
    
    • basePackages: 明确指定要扫描的包路径。可以指定多个。
    • 默认行为: 如果不指定 basePackages,Spring 会默认从当前配置类所在的包开始扫描。这是 Spring Boot 的核心约定,@SpringBootApplication 注解内部就包含了 @ComponentScan,所以它会自动扫描主启动类所在的包及其所有子包。
  2. 扫描执行:ClassPathBeanDefinitionScanner
    当 Spring IoC 容器启动并加载 AppConfig 时,它会解析到 @ComponentScan 注解。然后,它会内部创建一个名为 ClassPathBeanDefinitionScanner 的扫描器。

  3. 遍历类路径
    这个扫描器会像一个文件浏览器一样,根据你提供的 basePackages,去文件系统(或 JAR 包)的类路径下,递归地查找所有 .class 文件。

  4. 读取元数据
    对于找到的每一个 .class 文件,Spring 不会立即加载整个类到 JVM 中(这样效率太低)。它会使用一种称为 ASM 的字节码操作库,直接读取 .class 文件的二进制内容,从中解析出类的元数据,比如类名、接口、以及最重要的——类上的注解

  5. 识别候选者
    扫描器会检查每个类的元数据,看它是否被 @Component 或其衍生注解(@Service, @Repository 等)所标记。

  6. 创建 BeanDefinition
    如果一个类被识别为候选者,Spring 并不会立刻创建这个类的实例。相反,它会为这个类创建一个“Bean 的定义信息”对象,即 BeanDefinition
    BeanDefinition 就像一个菜谱,它描述了如何创建这个 Bean 的所有信息,包括:

    • Bean 的类名 (e.g., com.myapp.service.UserServiceImpl)。
    • Bean 的作用域(Scope),默认是 singleton(单例)。
    • 是否是懒加载(Lazy Init)。
    • 依赖关系(比如它需要注入哪些其他的 Bean)。
  7. 注册 BeanDefinition
    所有创建好的 BeanDefinition 都会被注册到一个名为 BeanDefinitionRegistry 的中央注册表中。至此,扫描阶段完成。容器现在已经“知道”了所有需要它管理的 Bean 的蓝图。

  8. 实例化 Bean
    最后,在容器生命周期的后续阶段(通常是容器刷新完成时,或者第一次请求该 Bean 时),容器会根据 BeanDefinitionRegistry 中的“菜谱”(BeanDefinition),通过反射来创建 Bean 的实例,执行依赖注入,并完成初始化,最终将一个功能完备的 Bean 放入容器的单例缓存池中,以备后用。

流程总结

你做什么 (声明)Spring 做什么 (发现和管理)
1. 在你的类上添加 @Component 或其衍生注解 (@Service, @Repository 等)。1. 启动时加载一个配置类 (@Configuration)。
2. 在配置类上添加 @ComponentScan,告诉 Spring 从哪里开始扫描。2. 发现 @ComponentScan 注解,获取要扫描的包路径。
3. 使用扫描器在指定的类路径下查找所有 .class 文件。
4. 高效读取每个 .class 文件的元数据(特别是注解)。
5. 识别出被 @Component 等注解标记的类。
6. 为每个识别出的类创建一个 BeanDefinition (Bean 的蓝图/菜谱)。
7. 将所有 BeanDefinition 注册到容器的注册表中。
8. 在需要时,根据 BeanDefinition 实例化、注入并初始化真正的 Bean 对象。

这个“注解声明 + 组件扫描”的机制是 Spring 框架实现自动化配置和依赖注入的基石,也是让 Spring Boot 能够实现“约定优于配置”的关键技术。

http://www.dtcms.com/a/266193.html

相关文章:

  • java微服务(Springboot篇)——————IDEA搭建第一个Springboot入门项目
  • 【基础算法】贪心 (二) :推公式
  • 封装一个png的编码解码操作
  • 译码器Multisim电路仿真汇总——硬件工程师笔记
  • 嵌入式系统中实现串口重定向
  • 【模糊集合】示例
  • 【MySQL\Oracle\PostgreSQL】迁移到openGauss数据出现的问题解决方案
  • Qt Creator自定义控件开发流程
  • redis缓存三大问题分析与解决方案
  • 车载以太网都有什么协议?
  • 创建 TransactionStatus
  • 【STM32实践篇】:I2C驱动编写
  • NumPy 安装使用教程
  • Debian-10-standard用`networking`服务的`/etc/network/interfaces`配置文件设置多网卡多IPv6
  • 【2.4 漫画SpringBoot实战】
  • CMake之CMakeLists.txt语法规则
  • 网安系列【1】:黑客思维、技术与案例解析
  • DDD实战:CQRS模式在电商报表系统中的高性能实践
  • RNN案例人名分类器(完整步骤)
  • MySQL 8.0 OCP 1Z0-908 题目解析(17)
  • POST请求url放参数场景-笔记
  • Spring SseEmitter 系统详细讲解
  • WPF学习笔记(16)树控件TreeView与数据模板
  • WPF学习笔记(22)项面板模板ltemsPanelTemplate与三种模板总结
  • spring-ai-alibaba 1.0.0.2 学习(八)——接入阿里云信息查询服务
  • 深度学习-逻辑回归
  • RJ45 连接器(水晶头)的引脚定义
  • 从0到1解锁Element-Plus组件二次封装El-Dialog动态调用
  • Gemini CLI初体验
  • 二叉树题解——二叉树的层序遍历【LeetCode】队列实现