本文收录于「Java 学习日记」专栏,聚焦 Spring 框架最核心的注解体系,从注解分类、使用场景到实战避坑,帮你彻底掌握 Spring 注解开发的精髓,告别繁琐的 XML 配置~
一、为什么要学 Spring 核心注解?
在上一篇 IOC/DI 的学习中,我们已经体验到注解开发的便捷性 ------ 用几行注解就能替代大量 XML 配置。但 Spring 的注解体系庞大,不同注解有不同的职责和使用场景:
- 有的注解用于定义 Bean (如
@Component),有的用于注入依赖 (如@Autowired); - 有的注解用于配置类 (如
@Configuration),有的用于限定作用域 (如@Scope); - 新手容易混淆注解的用法(比如
@Autowired和@Resource的区别),导致项目启动失败或依赖注入异常。
今天这篇日记,我们将 Spring 核心注解按 "功能分类" 拆解,结合实战案例讲解每个注解的使用场景、核心原理和避坑要点,让你对 Spring 注解体系形成清晰的认知。
二、Spring 核心注解分类与实战
Spring 核心注解可分为 6 大类,覆盖 Bean 定义、依赖注入、配置管理、作用域控制等核心场景,我们逐一讲解:
第一类:Bean 定义注解(将对象交给 Spring 容器管理)
核心作用:替代 XML 中的<bean>标签,告诉 Spring 容器 "这个类需要被管理"。
| 注解 | 作用 | 适用场景 |
|---|---|---|
@Component |
通用 Bean 定义注解 | 所有层的 Bean(通用,无明确分层) |
@Controller |
标识 Controller 层 Bean(Web 场景) | MVC 中的控制器层(接收请求) |
@Service |
标识 Service 层 Bean | 业务逻辑层 |
@Repository |
标识 Repository/DAO 层 Bean | 数据访问层(与数据库交互) |
- 基础使用示例
java
运行
java
// 1. DAO层(@Repository)
@Repository // 等价于<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
public class UserDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("DAO层:查询用户数据");
}
}
// 2. Service层(@Service)
@Service("userService") // 指定Bean id为userService(默认首字母小写userServiceImpl)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void business() {
userDao.query();
System.out.println("Service层:处理业务逻辑");
}
}
// 3. Controller层(@Controller)
@Controller // Bean id默认userController
public class UserController {
@Autowired
private UserService userService;
public void handleRequest() {
userService.business();
System.out.println("Controller层:处理请求");
}
}
// 4. 通用组件(@Component)
@Component // 非MVC分层的通用组件(如工具类)
public class CommonUtils {
public void print() {
System.out.println("通用组件:Spring管理的工具类");
}
}
- 核心细节
@Controller/@Service/@Repository本质是@Component的 "别名",仅用于语义化区分分层;- 不指定
value时,Bean id 默认是类名首字母小写 (如UserServiceImpl→userServiceImpl); - 必须配合
@ComponentScan(或 XML 的<context:component-scan>)扫描注解所在包,否则注解无效。
第二类:依赖注入注解(自动注入 Bean)
核心作用:替代 XML 中的<property>/<constructor-arg>,自动将依赖的 Bean 注入到目标对象中。
| 注解 | 作用 | 核心特点 |
|---|---|---|
@Autowired |
自动注入(Spring 注解) | 按类型匹配,支持 required 属性 |
@Resource |
自动注入(JDK 注解) | 按名称匹配(默认),可指定 name |
@Qualifier |
限定 Bean 名称 | 配合@Autowired解决同类型多 Bean 问题 |
@Value |
注入基本类型 / 配置文件属性 | 如@Value("${jdbc.url}") |
- @Autowired(最常用)
java
运行
java
@Service
public class UserServiceImpl implements UserService {
// 方式1:字段注入(最简洁,推荐)
@Autowired(required = false) // required=false:找不到Bean不报错(默认true)
private UserDao userDao;
// 方式2:setter注入(兼容老版本)
// @Autowired
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
// 方式3:构造器注入(推荐用于必须依赖)
// @Autowired
// public UserServiceImpl(UserDao userDao) {
// this.userDao = userDao;
// }
}
- @Autowired + @Qualifier(解决同类型多 Bean)
当同类型有多个 Bean 时,@Autowired按类型匹配会报错,需用@Qualifier指定 Bean 名称:
java
运行
java
// 两个同类型的DAO Bean
@Repository("userDaoImpl1")
public class UserDaoImpl1 implements UserDao { /* ... */ }
@Repository("userDaoImpl2")
public class UserDaoImpl2 implements UserDao { /* ... */ }
// 注入指定名称的Bean
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDaoImpl1") // 指定注入userDaoImpl1
private UserDao userDao;
}
- @Resource(JDK 注解)
java
运行
java
@Service
public class UserServiceImpl implements UserService {
// 方式1:按名称注入(name=Bean id)
@Resource(name = "userDaoImpl2")
private UserDao userDao;
// 方式2:默认按名称(字段名),找不到则按类型
// @Resource
// private UserDao userDaoImpl1; // 匹配id=userDaoImpl1的Bean
}
- @Value(注入基本类型 / 配置属性)
java
运行
java
@Service
public class UserServiceImpl implements UserService {
// 注入基本类型
@Value("18")
private Integer defaultAge;
// 注入配置文件属性(需先加载配置文件)
@Value("${app.name}") // 读取application.properties中的app.name
private String appName;
}
加载配置文件(在配置类上添加):
java
运行
java
@Configuration
@PropertySource("classpath:application.properties") // 加载配置文件
public class SpringConfig { /* ... */ }
第三类:配置类注解(替代 XML 配置文件)
核心作用:用 Java 类替代传统的applicationContext.xml,实现 "零 XML 配置"。
| 注解 | 作用 | 示例 |
|---|---|---|
@Configuration |
标记配置类(等价于 XML 配置文件) | @Configuration public class SpringConfig {} |
@ComponentScan |
扫描指定包下的注解 | @ComponentScan("com.example") |
@Bean |
手动注册 Bean(替代 XML 的<bean>) |
@Bean public UserDao userDao() {} |
@Import |
导入其他配置类 | @Import(DataSourceConfig.class) |
- 核心示例:纯 Java 配置
java
运行
java
// 1. 配置类(替代applicationContext.xml)
@Configuration // 标记为配置类
@ComponentScan("com.example") // 扫描注解(等价于<context:component-scan>)
@PropertySource("classpath:application.properties") // 加载配置文件
public class SpringConfig {
// 手动注册Bean(替代XML的<bean>,适用于第三方类)
@Bean("userDao") // Bean id默认方法名(userDao)
public UserDao userDao() {
return new UserDaoImpl(); // 手动创建对象,交给Spring管理
}
// 依赖注入:方法参数自动注入容器中的Bean
@Bean
public UserService userService(UserDao userDao) {
UserServiceImpl service = new UserServiceImpl();
service.setUserDao(userDao);
return service;
}
}
// 2. 测试:加载配置类创建容器
public class ConfigTest {
public static void main(String[] args) {
// 加载Java配置类,创建IOC容器
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserController controller = context.getBean(UserController.class);
controller.handleRequest();
}
}
- 核心细节
@Configuration修饰的类会被 Spring 代理,@Bean方法调用时会从容器获取 Bean(而非新建);@Bean方法的参数会自动从容器中注入(无需@Autowired);- 纯 Java 配置是 Spring Boot 的核心方式,彻底替代 XML。
第四类:作用域与生命周期注解
核心作用:控制 Bean 的作用域和生命周期(初始化 / 销毁)。
| 注解 | 作用 | 示例 |
|---|---|---|
@Scope |
指定 Bean 作用域 | @Scope("prototype") |
@PostConstruct |
Bean 初始化后执行(替代 init-method) | 方法上标注 |
@PreDestroy |
Bean 销毁前执行(替代 destroy-method) | 方法上标注 |
示例:作用域 + 生命周期
java
运行
java
@Service
@Scope("singleton") // 单例(默认),可选prototype/request/session
public class UserServiceImpl implements UserService {
// 初始化方法:Bean创建后执行(仅单例生效)
@PostConstruct
public void init() {
System.out.println("UserServiceImpl初始化完成");
}
// 销毁方法:容器关闭时执行(仅单例生效)
@PreDestroy
public void destroy() {
System.out.println("UserServiceImpl销毁");
}
}
第五类:条件注解(按需创建 Bean)
核心作用:根据条件动态创建 Bean(Spring Boot 自动配置的核心)。
| 注解 | 作用 | 示例 |
|---|---|---|
@Conditional |
自定义条件 | @Conditional(MyCondition.class) |
@ConditionalOnClass |
存在指定类时创建 Bean | @ConditionalOnClass(RedisTemplate.class) |
@ConditionalOnMissingBean |
不存在指定 Bean 时创建 | @ConditionalOnMissingBean(UserDao.class) |
示例:自定义条件
java
运行
java
// 1. 自定义条件类
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 自定义条件:存在"enable.userService"配置且值为true时创建Bean
String enable = context.getEnvironment().getProperty("enable.userService");
return "true".equals(enable);
}
}
// 2. 应用条件注解
@Service
@Conditional(MyCondition.class) // 满足条件才创建该Bean
public class UserServiceImpl implements UserService { /* ... */ }
第六类:AOP 相关注解(基础)
核心作用:实现面向切面编程(后续专题讲解,这里仅列基础注解)。
| 注解 | 作用 | 示例 |
|---|---|---|
@Aspect |
标记切面类 | @Aspect @Component public class LogAspect {} |
@Before |
前置通知 | @Before("execution(* com.example.service.*.*(..))") |
@After |
后置通知 | 同上 |
@Around |
环绕通知 | 同上 |
三、注解开发完整实战案例
1. 项目结构
plaintext
src/main/
├── java/
│ └── com/example/
│ ├── config/
│ │ └── SpringConfig.java // 配置类
│ ├── dao/
│ │ ├── UserDao.java
│ │ └── UserDaoImpl.java
│ ├── service/
│ │ ├── UserService.java
│ │ └── UserServiceImpl.java
│ ├── controller/
│ │ └── UserController.java
│ └── test/
│ └── AnnotationTest.java
└── resources/
└── application.properties // 配置文件
2. 核心代码
(1)配置文件:application.properties
properties
app.name=Spring注解实战
app.version=1.0
(2)配置类:SpringConfig.java
java
运行
java
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.example") // 扫描所有注解
@PropertySource("classpath:application.properties") // 加载配置文件
public class SpringConfig {
}
(3)DAO 层:UserDaoImpl.java
java
运行
java
package com.example.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void queryUser(String userId) {
System.out.println("查询用户ID:" + userId);
}
}
(4)Service 层:UserServiceImpl.java
java
运行
java
package com.example.service;
import com.example.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Value("${app.name}")
private String appName;
@Override
public void getUserInfo(String userId) {
System.out.println("应用名称:" + appName);
userDao.queryUser(userId);
}
}
(5)Controller 层:UserController.java
java
运行
java
package com.example.controller;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService userService;
public void handle(String userId) {
userService.getUserInfo(userId);
System.out.println("请求处理完成");
}
}
(6)测试类:AnnotationTest.java
java
运行
java
package com.example.test;
import com.example.config.SpringConfig;
import com.example.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationTest {
public static void main(String[] args) {
// 加载Java配置类,创建IOC容器
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取Controller并调用方法
UserController controller = context.getBean(UserController.class);
controller.handle("1001");
}
}
3. 运行结果
plaintext
应用名称:Spring注解实战
查询用户ID:1001
请求处理完成
四、核心避坑指南
1. 常见注解错误与解决方案
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
| NoSuchBeanDefinitionException | 1. 注解未扫描到;2. Bean 名称错误;3. 条件不满足 | 1. 检查 @ComponentScan 包路径;2. 核对 Bean id;3. 检查条件注解 |
| NoUniqueBeanDefinitionException | 同类型有多个 Bean,@Autowired 无法匹配 | 1. 用 @Qualifier 指定名称;2. 用 @Resource 按名称注入 |
| NullPointerException | @Autowired 注入的 Bean 为 null | 1. 确保类被注解标记(如 @Service);2. 确保被 Spring 容器管理(非手动 new) |
| @Value 注入 ${} 为 null | 未加载配置文件或配置项不存在 | 1. 添加 @PropertySource 加载配置;2. 核对配置项名称 |
2. 注解使用最佳实践
- 依赖注入 :优先使用字段注入 (简洁),必须依赖用构造器注入,避免循环依赖;
- Bean 命名:默认首字母小写,特殊场景(如类名前两个字母大写)手动指定 name;
- 配置方式 :新项目优先用纯 Java 配置(@Configuration),老项目兼容 XML;
- 作用域 :非线程安全的 Bean(如含成员变量)用
@Scope("prototype"),避免单例线程安全问题; - 注解扫描 :
@ComponentScan指定最小包路径(如com.example),避免扫描无关类,提升性能。
五、今日实战小任务
- 基于实战案例,新增
@Scope("prototype")到 UserService,测试每次 getBean () 是否创建新对象; - 新增两个同类型的 UserDao 实现类,用
@Autowired + @Qualifier和@Resource分别注入不同实现; - 自定义
@Conditional条件,实现 "只有当 JDK 版本大于 8 时,才创建 UserService Bean"。
总结
- Spring 核心注解按功能可分为 6 大类:Bean 定义(@Component/@Service 等)、依赖注入(@Autowired/@Resource 等)、配置类(@Configuration/@Bean 等)、作用域 / 生命周期(@Scope/@PostConstruct 等)、条件注解(@Conditional)、AOP 注解(@Aspect);
@Autowired按类型注入,配合@Qualifier解决同类型多 Bean 问题;@Resource按名称注入,是 JDK 注解,兼容性更好;@Configuration + @Bean可完全替代 XML 配置,是 Spring Boot 的核心配置方式;- 注解开发的核心是 "让 Spring 容器管理对象,自动注入依赖",需注意包扫描、Bean 命名、条件注解等细节,避免注入失败。
下一篇【Day40】预告:SpringMVC 全注解开发:请求映射、参数绑定、返回值处理,关注专栏从 Spring 核心过渡到 Web 实战~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!