通关注解需四步:一、定义@LogExecutionTime注解并验证RUNTIME保留;二、用反射手动读取执行计时;三、对接Spring的@Aspect切面与@Conditional条件装配;四、避坑——确保RUNTIME、正确获取方法、理解AOP代理限制。

写一份实用的 Java 注解通关演练教程,核心是“从用到懂、边练边学”,不是罗列 API,而是围绕真实开发场景设计可动手的练习环节。重点讲清楚注解怎么写、怎么生效、怎么被框架或自己读取,避免陷入反射源码细节,但必须覆盖生命周期关键点。
一、先写一个能跑起来的自定义注解
别一上来就讲元注解,先做出一个能被识别的注解,建立手感。比如定义一个 @LogExecutionTime,用来标记需要记录执行时长的方法:
- 用 @interface 声明注解类型,加上 @Retention(RetentionPolicy.RUNTIME) 和 @Target(ElementType.METHOD)
- 添加一个 value() String 类型的属性,默认值设为 "",方便后续扩展用途
- 编译后用 javap -s 查看字节码,确认注解确实保留在运行时(这是后续反射读取的前提)
二、用反射手动读取并触发行为
不依赖 Spring,纯 Java 实现一次完整闭环:标注 → 发现 → 执行。例如写一个工具类 TimerUtils.executeWithTimeLogging(YourService.class, "doSomething"):
- 通过 Class.getDeclaredMethod() 获取目标方法
- 检查 method.isAnnotationPresent(LogExecutionTime.class),再 getAnnotation() 取出实例
- 用 System.nanoTime() 包裹 method.invoke(),打印耗时 —— 这就是注解“生效”的实质
- 注意处理 IllegalAccessException、InvocationTargetException 等异常,否则练习会卡住
三、对接 Spring 的常见模式
大多数人在项目里接触注解都是通过 Spring,所以必须练透两个典型路径:
- @Aspect 切面:用 @Around 拦截 @LogExecutionTime,获取 ProceedingJoinPoint,手动控制执行和计时,比反射更简洁
- @Bean + @Conditional:写一个 @EnableMyFeature + @MyFeatureCondition,模拟 starter 自动装配逻辑,理解条件化加载机制
- 强调一点:Spring 中注解本身不干活,真正干活的是它背后注册的 Bean 或 AOP Advisor,这点常被忽略
四、避坑清单:那些看似正常却失效的情况
很多同学写了注解但没反应,往往栽在这些细节上:
- @Retention 设成 CLASS 或 SOURCE → 反射读不到;必须是 RUNTIME
- 注解用在 private 方法上,而反射默认 getMethods() 只返回 public → 改用 getDeclaredMethod()
- Spring AOP 默认只代理 public 方法,private 或 package-private 方法加了 @Transactional 也没用
- 自定义注解加了 @Documented 或 @Inherited,但实际业务根本不需要,纯属干扰
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











