代码之术-剑指 Java 注解
注解本质上就是一种标记,用于给代码添加说明信息。
可以标记到类、方法、属性上等。标记自身也可以设置一些值。
注解本身并不影响程序的执行逻辑,但可以通过工具或框架来利用这些信息进行特定的处理,如代码生成、编译时检查、运行时处理等。
1 自定义一个注解
管它三七二十一,先用起来再说!
主要包含以下步骤:
- 定义注解
- 使用注解
- 解析注解
- 测试注解
1、定义注解
先尝试定义一个,能够在方法输出前后打上标记的注解: @MyAdvice。

2、使用注解
在 DriveImpl 类中使用:

需要注意,因为用 Java 原生的动态代理进行解析,所以该类需要实现一个父接口。
即 IDriver:

3、解析注解
此处使用了传统 java 的动态代理来实现,没有使用 SpringBoot 的 AOP,所以代码上会有点复杂。
因本文目的在于注解,而非动态代理,所以不多做赘述。后续如果发布了动态代理的博客,会补充链接。
代码会贴在最后,这里先梳理核心脉络。

4、测试注解
在 main 方法中使用注解:

因为使用了动态代理,解析注解,所以第一行的内容看不懂没关系,可以不关心。
总之,现在就是实现了通过注解对方法进行增强。
整体流程如下:

2 定义方式
- 以
@interface关键字来定义注解。 - 可以定义变量,以及默认值。
- 需要通过元注解修饰

3 解析注解
本文使用动态代理的方式,自动解析注解,让使用方对注解的解析无感知。
当然也可以通过使用方显性解析,但是这样很不灵活。
本文使用动态代理,只是自动化解析注解的一种方式。
因为注解的本质是打上一个标记,同时可以传递一些变量,所以解析的核心不在于动态代理,而在于动态代理中的解析方式。

当然,除了动态代理解析,也可以在使用处显性解析,如下:

这样就太不优雅了。
无论是动态代理,还是显性解析,本质上决定的都是解析的位置,而不是解析行为本身。
而且动态代理更适用于对方法注解的解析,其它类型注解的解析又要通过其它方式。
对照着看,也能够对“注解只是一种标记”这个定义有了更深的理解。
4 元注解
元注解,即注解的注解,如:@Retention、@Target、@Inherited(表示注解能否被继承)
4.1 @Retention
定义注解的保留策略,即注解的有效范围。
注解的三大保留策略:
RetentionPolicy.SOURCE:仅在源码中存在,编译时被丢弃RetentionPolicy.CLASS:注解存在于编译后的 .class 文件中,但运行时不可用RetentionPolicy.RUNTIME:注解在运行时可用,可以通过反射访问
三大策略定义如下图:

4.2 @Target
指定注解可以应用于哪些代码元素。
ElementType.TYPE:类、接口(包括注解类型)或枚举ElementType.FIELD:字段(包括枚举常量)ElementType.METHOD:方法ElementType.PARAMETER:方法参数ElementType.CONSTRUCTOR:构造方法ElementType.LOCAL_VARIABLE:局部变量ElementType.ANNOTATION_TYPE:注解类型ElementType.PACKAGE:包ElementType.TYPE_PARAMETER:允许注解出现在泛型类型参数上ElementType.TYPE_USE:类型使用的地方
5 常见注解
@Override

@Autowared

6 附录
相关代码
MyAdvice 自定义注解:
/**
* 自定义方法增强注解
* 在方法执行前后打印信息
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyAdvice {
/**
* 前置打印信息
* @return 打印信息
*/
String beforeValue() default " ";
/**
* 后置打印信息
* @return 打印信息
*/
String afterValue() default " ";
}
MyAdviceProxyFactory 注解解析工厂
/**
* @Author gs_huang
* @Date 2025/11/30 11:59
*/
public class MyAdviceProxyFactory {
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // 注意:只能代理接口
new MyAdviceInvocationHandler(target)
);
}
private static class MyAdviceInvocationHandler implements InvocationHandler {
private final Object target;
public MyAdviceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
MyAdvice myAdvice = targetMethod.getAnnotation(MyAdvice.class);
if (myAdvice != null) {
// 前置处理
if (!myAdvice.beforeValue().trim().isEmpty()) {
System.out.println(myAdvice.beforeValue());
}
Object result = method.invoke(target, args);
// 后置处理
if (!myAdvice.afterValue().trim().isEmpty()) {
System.out.println(myAdvice.afterValue());
}
return result;
} else {
// 没有注解的方法直接执行
return method.invoke(target, args);
}
}
}
}
IDrive 接口类
/**
* @Author gs_huang
* @Date 2025/11/30 11:53
*/
public interface IDrive {
void driveCar();
}
DriveImpl 实现类
/**
* @Author gs_huang
* @Date 2025/11/30 11:12
*/
public class DriveImpl implements IDrive {
@MyAdvice(beforeValue = "车子插上钥匙了...", afterValue = "车子跑起来了...")
public void driveCar(){
System.out.println("车子启动了...");
}
}
AnnoApp 测试类
/**
* @Author gs_huang
* @Date 2025/11/30 11:12
*/
public class AnnoApp {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 动态代理解析
IDrive drive = MyAdviceProxyFactory.createProxy(new DriveImpl());
drive.driveCar();
// 显性解析
Method method = DriveImpl.class.getMethod("driveCar");
if (method.isAnnotationPresent(MyAdvice.class)){
MyAdvice myAdvice = method.getAnnotation(MyAdvice.class);
String beforeValue = myAdvice.beforeValue();
String afterValue = myAdvice.afterValue();
System.out.println(beforeValue);
method.invoke(new DriveImpl());
System.out.println(afterValue);
}
}
}

浙公网安备 33010602011771号