Java 注解(Annotation):从原理到实战应用
注解是 Java 5 引入的代码级别的说明标记,它相当于给代码贴“标签”,本身不直接执行逻辑,但可以被编译器、框架、JVM读取并解析,实现自动化配置、代码生成、校验等功能。
从本质上讲,注解是一种特殊的接口,底层通过反射实现解析,是 Spring、MyBatis、Spring Boot 等主流框架的核心基础。
一、注解核心基础
1. 什么是注解?
- 定义:用于修饰类、方法、变量、参数、包的元数据(描述数据的数据)。
- 特点:
- 不直接影响代码逻辑;
- 可在编译、类加载、运行时被读取;
- 是框架实现「约定大于配置」的核心。
2. 注解的分类
(1)JDK 内置标准注解(直接用)
| 注解 | 作用 |
|---|---|
@Override | 标记方法重写,编译时校验 |
@Deprecated | 标记方法/类已废弃,提醒不推荐使用 |
@SuppressWarnings | 压制编译器警告 |
@FunctionalInterface | 标记函数式接口 |
(2)元注解(用来定义注解的注解,核心)
Java 提供了 4 个元注解,专门用于自定义注解:
-
@Target:指定注解能修饰的目标(类、方法、字段等) -
@Retention:指定注解的生命周期(源码、编译、运行时) -
@Documented:标记注解会被生成到 Javadoc 中 -
@Inherited:标记注解可被子类继承
重点:
@Retention 是注解生效的关键!
-
RetentionPolicy.SOURCE:仅源码阶段(如 @Override)-
RetentionPolicy.CLASS:编译阶段(默认)-
RetentionPolicy.RUNTIME:运行时(可通过反射解析,框架常用)
二、注解原理:底层是怎么工作的?
1. 本质原理
- 注解本质是 继承自
Annotation 接口的特殊接口; - 注解的属性 = 接口的抽象方法;
- 注解的解析 = Java 反射机制读取注解信息;
- 框架(如 Spring)通过解析注解,自动完成对象创建、依赖注入等操作。
2. 核心流程
定义注解 → 标记代码 → 反射解析注解 → 执行对应逻辑
三、实战一:自定义注解 + 反射解析(运行时注解)
我们手写一个自定义校验注解,实现:给实体类字段加注解,自动校验字段非空、长度限制。
步骤 1:定义自定义注解
import java.lang.annotation.*;
// 元注解:注解作用在字段上
@Target(ElementType.FIELD)
// 元注解:运行时生效(必须写,否则反射读取不到)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRule {
// 非空校验:默认true
boolean notNull() default false;
// 长度最小值
int minLength() default 0;
// 长度最大值
int maxLength() default Integer.MAX_VALUE;
// 校验失败提示信息
String message() default "参数校验失败";
}
步骤 2:编写实体类,使用注解
public class User {
// 非空校验 + 长度2-10
@CheckRule(notNull = true, minLength = 2, maxLength = 10, message = "用户名不能为空且长度2-10")
private String username;
// 仅非空校验
@CheckRule(notNull = true, message = "密码不能为空")
private String password;
// getter/setter
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public String getPassword() {return password;}
public void setPassword(String password) {this.password = password;}
}
步骤 3:反射解析注解(核心逻辑)
import java.lang.reflect.Field;
public class CheckUtils {
// 通用校验方法:传入对象,自动解析注解完成校验
public static String validate(Object obj) {
// 1. 获取对象的Class对象
Class<?> clazz = obj.getClass();
// 2. 获取所有字段
Field[] fields = clazz.getDeclaredFields();
// 3. 遍历字段,解析注解
for (Field field : fields) {
// 判断字段是否有我们的注解
if (field.isAnnotationPresent(CheckRule.class)) {
// 获取注解实例
CheckRule checkRule = field.getAnnotation(CheckRule.class);
// 暴力反射(打破private限制)
field.setAccessible(true);
try {
// 获取字段值
Object value = field.get(obj);
// 非空校验
if (checkRule.notNull()) {
if (value == null || value.toString().trim().isEmpty()) {
return checkRule.message();
}
}
// 长度校验(仅字符串)
if (value instanceof String str) {
int len = str.length();
if (len < checkRule.minLength() || len > checkRule.maxLength()) {
return checkRule.message();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// 校验通过
return null;
}
}
步骤 4:测试效果
public class Test {
public static void main(String[] args) {
User user = new User();
user.setUsername(""); // 空用户名
user.setPassword(null); // 空密码
String result = CheckUtils.validate(user);
if (result != null) {
System.out.println("校验失败:" + result);
} else {
System.out.println("校验通过");
}
}
}
输出结果:
校验失败:用户名不能为空且长度2-10
✅ 这就是注解的核心实战:自定义注解 + 反射解析 = 自动化逻辑。
四、实战二:模拟 Spring @Component 注解
Spring 中 @Component 作用:标记类为 Spring 管理的 Bean,我们手写简化版实现。
步骤 1:定义注解
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
// Bean名称
String value() default "";
}
步骤 2:标记业务类
@MyComponent("userService")
public class UserService {
public void sayHello() {
System.out.println("Hello, 注解实战!");
}
}
步骤 3:模拟 Spring 容器(反射扫描注解)
import java.util.HashMap;
import java.util.Map;
public class MySpringContext {
// 模拟Spring容器,存储Bean
private final Map<String, Object> beanMap = new HashMap<>();
// 初始化:扫描类上的注解,创建对象存入容器
public void init(Class<?>... classes) {
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(MyComponent.class)) {
MyComponent annotation = clazz.getAnnotation(MyComponent.class);
// 获取Bean名称
String beanName = annotation.value().isEmpty()
? clazz.getSimpleName()
: annotation.value();
try {
// 创建对象
Object bean = clazz.newInstance();
// 存入容器
beanMap.put(beanName, bean);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 根据名称获取Bean
public Object getBean(String beanName) {
return beanMap.get(beanName);
}
}
步骤 4:测试
public class TestSpring {
public static void main(String[] args) {
MySpringContext context = new MySpringContext();
// 初始化容器
context.init(UserService.class);
// 获取Bean
UserService userService = (UserService) context.getBean("userService");
userService.sayHello();
}
}
输出结果:
Hello, 注解实战!
五、注解的高级应用场景
- 框架核心:Spring
@Controller/@Service/@Autowired、MyBatis @Mapper、Spring Boot @SpringBootApplication; - 参数校验:Hibernate Validator
@NotBlank/@NotNull; - 代码生成:Lombok
@Data/@Getter(编译时生成代码); - 日志/监控:自定义注解 + AOP 实现自动日志、接口限流;
- ORM 映射:MyBatis-Plus
@TableName/@TableId。
六、核心总结
1. 原理一句话
注解是特殊接口,通过反射解析,是框架实现自动化配置的核心。
2. 自定义注解固定模板
- 用4个元注解修饰(必写
@Target + @Retention); - 定义注解属性(方法形式);
- 用反射解析注解,实现业务逻辑。
3. 关键知识点
-
@Retention(RetentionPolicy.RUNTIME):运行时解析必须加; - 解析注解核心 API:
isAnnotationPresent()、getAnnotation(); - 注解本身无逻辑,解析器才是核心。
掌握注解,你就掌握了 Java 框架的底层密码!