Spring 声明 Bean 的注解详解
一、概述
Spring 声明 Bean 的注解分为两大类:类级别注解和方法级别注解。类级别注解标注在类上,Spring 通过组件扫描自动注册;方法级别注解标注在@Configuration类的方法上,手动注册返回值。
| 类别 | 注解 | 标注位置 | 注册方式 |
|---|---|---|---|
| 类级别 | @Component | 类 | 组件扫描 |
| 类级别 | @Service | 类 | 组件扫描 |
| 类级别 | @Repository | 类 | 组件扫描 |
| 类级别 | @Controller | 类 | 组件扫描 |
| 类级别 | @RestController | 类 | 组件扫描 |
| 类级别 | @Configuration | 类 | 组件扫描 |
| 方法级别 | @Bean | 方法 | 配置类中手动注册 |
二、@Component:基础组件注解
@Component是最基础的 Bean 声明注解,其他类级别注解都是它的派生注解。
@ComponentpublicclassUserDao{publicvoidsave(){System.out.println("保存用户");}}Bean 名称规则:默认是类名首字母小写,如UserDao→userDao。可以显式指定名称:
@Component("myUserDao")publicclassUserDao{}特点:
- 通用性最强,适合不属于任何特定层次的组件
- 是
@Service、@Repository、@Controller的元注解 - 被
@ComponentScan扫描后注册为 Bean
三、@Service:业务层组件
@Service是@Component的派生注解,语义上表示业务逻辑层。
@ServicepublicclassUserService{@AutowiredprivateUserDaouserDao;publicvoidregister(Useruser){userDao.save(user);}}为什么用 @Service 而不是 @Component?
功能上完全等价,但语义更清晰。在代码审查时,看到@Service就知道这是业务层组件,看到@Repository就知道这是数据访问层组件。这种语义分层让项目结构更清晰。
四、@Repository:数据访问层组件
@Repository是@Component的派生注解,专门用于数据访问层。它有一个额外功能:异常转换。
@RepositorypublicclassUserDaoImplimplementsUserDao{@AutowiredprivateJdbcTemplatejdbcTemplate;@Overridepublicvoidsave(Useruser){jdbcTemplate.update("INSERT INTO user (name) VALUES (?)",user.getName());}}当@Repository类抛出数据库异常时,Spring 会自动将其转换为 Spring 的统一数据访问异常体系(DataAccessException)。这样上层代码不需要处理特定数据库厂商的异常类型,切换数据库时业务代码不受影响。
五、@Controller 和 @RestController
@Controller标记 Web 控制层组件,@RestController是@Controller和@ResponseBody的组合。
@ControllerpublicclassPageController{@GetMapping("/index")publicStringindex(){return"index";// 返回视图名,渲染模板}}@RestControllerpublicclassUserController{@AutowiredprivateUserServiceuserService;@GetMapping("/user/{id}")publicUsergetUser(@PathVariableLongid){returnuserService.getById(id);// 直接返回 JSON}}@Controller返回视图名,由视图解析器渲染页面。@RestController返回的对象直接作为 HTTP 响应体输出,适合前后端分离的 RESTful API。
六、@Configuration:配置类
@Configuration标记一个类为配置类,可以替代 XML 配置文件。它本身也是一个@Component,会被组件扫描注册。
@ConfigurationpublicclassAppConfig{@BeanpublicDataSourcedataSource(){HikariDataSourceds=newHikariDataSource();ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");returnds;}}@Configuration与@Component的区别:@Configuration类会被 CGLIB 代理,保证@Bean方法之间互相调用时返回容器中的单例。@Component不会被代理,方法间调用直接执行方法体,创建新实例。
@ConfigurationpublicclassAppConfig{@BeanpublicUserDaouserDao(){returnnewUserDao();}@BeanpublicUserServiceuserService(){// 由于 CGLIB 代理,userDao() 返回的是容器中的单例returnnewUserService(userDao());}}七、@Bean:方法级别声明
@Bean标注在@Configuration类的方法上,把方法返回值注册为 Bean。
@ConfigurationpublicclassAppConfig{@BeanpublicDataSourcedataSource(){returnnewHikariDataSource();}@BeanpublicJdbcTemplatejdbcTemplate(DataSourcedataSource){returnnewJdbcTemplate(dataSource);}}适用场景:
- 第三方类无法添加
@Component注解时 - 需要复杂初始化逻辑的对象
- 需要根据条件动态创建 Bean 时
@Bean方法参数:Spring 会自动从容器中注入匹配的 Bean。
@BeanpublicJdbcTemplatejdbcTemplate(DataSourcedataSource){// dataSource 自动从容器注入returnnewJdbcTemplate(dataSource);}Bean 名称:默认是方法名,可以通过name或value指定:
@Bean("myDataSource")publicDataSourcedataSource(){...}初始化和销毁方法:
@Bean(initMethod="init",destroyMethod="cleanup")publicMyBeanmyBean(){returnnewMyBean();}八、@ComponentScan:组件扫描
@ComponentScan告诉 Spring 扫描哪些包下的组件。
@Configuration@ComponentScan("com.example")publicclassAppConfig{}常用属性:
| 属性 | 作用 |
|---|---|
basePackages | 指定扫描的包路径 |
basePackageClasses | 指定类所在包为扫描根路径 |
includeFilters | 包含过滤器 |
excludeFilters | 排除过滤器 |
@ComponentScan(basePackages="com.example",excludeFilters=@ComponentScan.Filter(type=FilterType.ANNOTATION,classes=Service.class))@SpringBootApplication已经包含了@ComponentScan,默认扫描启动类所在包及其子包。
九、@Import:导入配置
@Import用于导入其他配置类或普通类,被导入的类会注册为 Bean。
@Configuration@Import({DatabaseConfig.class,RedisConfig.class})publicclassAppConfig{}@Import支持三种类型:
- 普通配置类(
@Configuration类) ImportSelector实现类ImportBeanDefinitionRegistrar实现类
十、条件化声明 Bean
@Conditional
根据条件决定是否注册 Bean:
@ConfigurationpublicclassAppConfig{@Bean@Conditional(WindowsCondition.class)publicFileServicewindowsFileService(){returnnewWindowsFileService();}@Bean@Conditional(LinuxCondition.class)publicFileServicelinuxFileService(){returnnewLinuxFileService();}}Spring Boot 的条件注解
| 注解 | 条件 |
|---|---|
@ConditionalOnClass | classpath 中存在指定类 |
@ConditionalOnMissingClass | classpath 中不存在指定类 |
@ConditionalOnBean | 容器中存在指定 Bean |
@ConditionalOnMissingBean | 容器中不存在指定 Bean |
@ConditionalOnProperty | 配置属性满足条件 |
@ConditionalOnWebApplication | 是 Web 应用 |
@Bean@ConditionalOnMissingBeanpublicObjectMapperobjectMapper(){returnnewObjectMapper();}@ConditionalOnMissingBean常用于提供默认实现,允许用户通过自定义 Bean 覆盖。
十一、@Profile:环境化声明 Bean
根据激活的 Profile 决定是否注册 Bean:
@Configuration@Profile("dev")publicclassDevConfig{@BeanpublicDataSourcedevDataSource(){// 开发环境数据源}}@Configuration@Profile("prod")publicclassProdConfig{@BeanpublicDataSourceprodDataSource(){// 生产环境数据源}}也可以通过启动参数激活:
java-jarmyapp.jar--spring.profiles.active=prod十二、各注解对比总结
| 注解 | 标注位置 | 语义 | 特殊功能 |
|---|---|---|---|
@Component | 类 | 通用组件 | 无 |
@Service | 类 | 业务层 | 无(语义清晰) |
@Repository | 类 | 数据访问层 | 异常转换 |
@Controller | 类 | Web 控制层 | 视图解析 |
@RestController | 类 | REST 控制器 | 直接返回 JSON |
@Configuration | 类 | 配置类 | CGLIB 代理 |
@Bean | 方法 | 手动注册 | 灵活创建 |
@ComponentScan | 类 | 组件扫描 | 指定扫描范围 |
@Import | 类 | 导入配置 | 导入其他配置类 |
@Conditional | 类/方法 | 条件注册 | 按条件决定 |
@Profile | 类/方法 | 环境注册 | 按 Profile 决定 |
十三、最佳实践
按层选择注解:Controller 层用@Controller或@RestController,Service 层用@Service,Dao 层用@Repository。语义清晰,便于维护。
第三方类用 @Bean:无法修改源码的第三方类,通过@Configuration+@Bean注册。
条件化 Bean 用 @ConditionalOnMissingBean:提供默认实现,允许用户覆盖。
多环境用 @Profile:不同环境的 Bean 分开配置,避免运行时判断。
组件扫描范围控制在合理范围:启动类放在根包下,避免扫描到不需要的包,影响启动速度。
十四、总结
| 维度 | 核心要点 |
|---|---|
| 类级别注解 | @Component、@Service、@Repository、@Controller、@RestController、@Configuration |
| 方法级别注解 | @Bean |
| 扫描注解 | @ComponentScan |
| 导入注解 | @Import |
| 条件注解 | @Conditional、@ConditionalOnXxx |
| 环境注解 | @Profile |
| 选择原则 | 自己的类用类级别注解,第三方类用@Bean |
Spring 声明 Bean 的注解体系围绕“类级别自动扫描”和“方法级别手动注册”两条线展开。@Component及其派生注解负责前者,@Bean负责后者。配合@ComponentScan、@Import、@Conditional、@Profile,可以灵活地控制 Bean 的注册范围和条件。理解这些注解的语义和适用场景,是掌握 Spring IoC 注解式开发的基础。