Java 注解(Annotation)与反射(Reflection) 是 Java 高级编程的核心技能之一,尤其在框架开发(如 Spring、MyBatis)、AOP、参数校验、权限控制、序列化、测试等场景中大量使用。自定义注解 + 反射 是实现“元编程”和“声明式编程”的最常见组合。
这份手册从零基础到实战精通,结构清晰、代码密集、层层递进,适合快速上手和面试/项目复习。
1. 注解核心概念(必背)
注解本质上是元数据(关于程序的额外信息),不直接影响代码执行,但可以通过反射在运行时读取并产生行为。
四大元注解(定义注解时必须掌握):
| 元注解 | 作用 | 常用值 / 默认值 | 说明 |
|---|---|---|---|
@Target | 指定注解能用在什么地方 | ElementType.*(TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, RECORD_COMPONENT) | 不写默认所有位置;最常用:METHOD + FIELD + TYPE |
@Retention | 指定注解的生命周期(保留到什么时候) | RetentionPolicy.*(SOURCE, CLASS, RUNTIME) | SOURCE:只在源码(Lombok) CLASS:字节码(默认) RUNTIME:运行时可反射读取(最常用) |
@Documented | 是否将注解包含进javadoc | — | 一般用于公开API的注解(如 @Deprecated) |
@Inherited | 子类是否能继承父类的注解(只对类注解有效) | — | 常用于配置类注解(如 Spring 的 @Component 系列) |
@Repeatable(JDK8+) | 允许同一个位置重复使用同一个注解 | 指定容器注解 | 如 @RolesAllowed({“admin”, “user”}) |
一句话记忆:
@Target 管位置,@Retention 管存活,@Inherited 管遗传,@Documented 管文档。
2. 自定义注解入门(最基础写法)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 最简单的自定义注解
@Target({ElementType.METHOD, ElementType.TYPE}) // 可用于方法和类
@Retention(RetentionPolicy.RUNTIME) // 运行时可读
public @interface MyFirstAnnotation {
// 注解可以有属性(类似接口的抽象方法)
String value() default "default"; // 单值时常用 value
int priority() default 1;
String[] roles() default {}; // 数组属性
}
使用方式:
@MyFirstAnnotation(value = "重要方法", priority = 3, roles = {"admin", "manager"})
public class UserService {
@MyFirstAnnotation("必须登录")
public void getUserInfo() {
// ...
}
}
3. 反射读取注解(核心操作)
反射获取注解 是自定义注解产生实际效果的关键。
import java.lang.reflect.Method;
public class AnnotationParser {
public static void main(String[] args) throws Exception {
Class<?> clazz = UserService.class;
// 1. 获取类上的注解
if (clazz.isAnnotationPresent(MyFirstAnnotation.class)) {
MyFirstAnnotation ann = clazz.getAnnotation(MyFirstAnnotation.class);
System.out.println("类注解: " + ann.value() + ", priority=" + ann.priority());
}
// 2. 获取方法上的注解
Method method = clazz.getMethod("getUserInfo");
if (method.isAnnotationPresent(MyFirstAnnotation.class)) {
MyFirstAnnotation ann = method.getAnnotation(MyFirstAnnotation.class);
System.out.println("方法注解: " + ann.value());
System.out.println("所需角色: " + String.join(", ", ann.roles()));
}
// 3. 获取所有注解(包含继承的)
for (java.lang.annotation.Annotation a : method.getAnnotations()) {
System.out.println(a);
}
}
}
4. 实战案例1:自定义 @NotNull 参数校验(方法参数级别)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String message() default "参数不能为空";
}
使用 + 反射校验:
public class Validator {
public static void validate(Object obj, Method method, Object[] args) throws Exception {
java.lang.reflect.Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].isAnnotationPresent(NotNull.class)) {
if (args[i] == null) {
NotNull notNull = parameters[i].getAnnotation(NotNull.class);
throw new IllegalArgumentException(notNull.message());
}
}
}
}
// 示例调用
public void saveUser(@NotNull(message = "用户不能为空") User user) {
// ...
}
}
5. 实战案例2:自定义 @Log 方法耗时日志(AOP思想前置)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default ""; // 日志描述
boolean showParams() default true;
boolean showResult() default true;
}
实现(使用反射 + 动态代理思想):
public class LogHandler {
public static Object invokeWithLog(Object target, Method method, Object[] args) throws Throwable {
if (!method.isAnnotationPresent(Log.class)) {
return method.invoke(target, args);
}
Log log = method.getAnnotation(Log.class);
long start = System.currentTimeMillis();
System.out.println("开始执行: " + log.value());
if (log.showParams()) {
System.out.println("参数: " + Arrays.toString(args));
}
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("执行完成: " + log.value() + ", 耗时: " + (end - start) + "ms");
if (log.showResult()) {
System.out.println("返回: " + result);
}
return result;
}
}
6. 实战案例3:自定义 @Auth 权限控制(最常见企业需求)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Auth {
String[] roles() default {}; // 允许的角色
String[] permissions() default {}; // 允许的权限码
}
拦截器 / AOP 风格实现(伪代码):
public void checkAuth(Method method, User currentUser) {
if (!method.isAnnotationPresent(Auth.class)) return;
Auth auth = method.getAnnotation(Auth.class);
String[] requiredRoles = auth.roles();
if (requiredRoles.length > 0) {
boolean hasRole = Arrays.stream(requiredRoles)
.anyMatch(role -> currentUser.getRoles().contains(role));
if (!hasRole) {
throw new SecurityException("无权限,缺少角色: " + Arrays.toString(requiredRoles));
}
}
// 类似处理 permissions
}
7. 进阶要点 & 面试高频
| 问题 | 核心回答要点 |
|---|---|
| 运行时注解 vs 编译时注解 | RUNTIME → 反射读取(Spring @Autowired) SOURCE → 编译期处理(Lombok) |
| 注解属性支持的类型 | 基本类型、String、Class、enum、Annotation、以上类型的数组 |
| 注解可以没有属性吗? | 可以,称为标记注解(Marker Annotation),如 @Override、@Test |
| @Repeatable 怎么用? | 定义容器注解(如 @Roles),然后在 @Roles 上用 @Repeatable(容器.class) |
| Spring 中注解是怎么工作的? | 启动时扫描 → BeanDefinition → 反射读取注解 → AOP/BeanPostProcessor 处理 |
| 注解继承关系? | 只有类注解 + @Inherited 才会被子类继承 |
8. 总结:从入门到精通的进阶路线
- 掌握四大元注解 + 基本语法(1天)
- 能写出标记注解 + 单属性注解 + 数组属性注解(1天)
- 熟练用反射读取类/方法/字段/参数上的注解(2天)
- 实现 2-3 个实战场景:校验、日志、权限、配置映射(3-5天)
- 理解 Spring AOP + 自定义注解的原理(AspectJ / JDK动态代理)
- 尝试写类似 @Cacheable、@Transactional 的简易版(进阶)
一句话口诀:
注解写元数据,反射读行为,元注解定规则,RUNTIME 才生效。
有具体场景想深入实现(比如参数校验完整版、AOP方式、@Repeatable实战、注解处理器APT等),随时告诉我,我可以继续给出更详细的代码和原理!