代码之术-剑指 Java 注解

注解本质上就是一种标记,用于给代码添加说明信息。

可以标记到类、方法、属性上等。标记自身也可以设置一些值。

注解本身并不影响程序的执行逻辑,但可以通过工具或框架来利用这些信息进行特定的处理,如代码生成、编译时检查、运行时处理等。

1 自定义一个注解

管它三七二十一,先用起来再说!

主要包含以下步骤:

  1. 定义注解
  2. 使用注解
  3. 解析注解
  4. 测试注解

1、定义注解

先尝试定义一个,能够在方法输出前后打上标记的注解: @MyAdvice

1764475515305-caa16c5b-4449-4b83-b857-6c099cf42025

2、使用注解

在 DriveImpl 类中使用:

1764475567869-c0d823e9-0e52-4020-aa8d-563ad73fa2de

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

即 IDriver:

1764477330053-7942a0a6-ff50-41e3-a9bb-abb186ad6145

3、解析注解

此处使用了传统 java 的动态代理来实现,没有使用 SpringBoot 的 AOP,所以代码上会有点复杂。

因本文目的在于注解,而非动态代理,所以不多做赘述。后续如果发布了动态代理的博客,会补充链接。

代码会贴在最后,这里先梳理核心脉络。

1764476034032-843945b0-a32d-431b-b69d-9a8554cb21f6

4、测试注解

在 main 方法中使用注解:

1764476140251-ba9b6438-800c-451b-841a-a34c1ea497d3

因为使用了动态代理,解析注解,所以第一行的内容看不懂没关系,可以不关心。

总之,现在就是实现了通过注解对方法进行增强。

整体流程如下:

1764477021267-8a14895c-24e1-44ec-abdd-252d2f871547

2 定义方式

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

1764477412583-454de983-4697-4fc5-927b-16dfe63e1830

3 解析注解

本文使用动态代理的方式,自动解析注解,让使用方对注解的解析无感知。

当然也可以通过使用方显性解析,但是这样很不灵活。

本文使用动态代理,只是自动化解析注解的一种方式。

因为注解的本质是打上一个标记,同时可以传递一些变量,所以解析的核心不在于动态代理,而在于动态代理中的解析方式。

1764478203392-c57667bd-db03-4d7e-95b6-384dc1f30d4c

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

1764478334739-50202fab-17f9-46cd-a6a9-c90e677bf83a

这样就太不优雅了。

无论是动态代理,还是显性解析,本质上决定的都是解析的位置,而不是解析行为本身。

而且动态代理更适用于对方法注解的解析,其它类型注解的解析又要通过其它方式。

对照着看,也能够对“注解只是一种标记”这个定义有了更深的理解。

4 元注解

元注解,即注解的注解,如:@Retention、@Target、@Inherited(表示注解能否被继承)

4.1 @Retention

定义注解的保留策略,即注解的有效范围。

注解的三大保留策略:

  1. RetentionPolicy.SOURCE:仅在源码中存在,编译时被丢弃
  2. RetentionPolicy.CLASS:注解存在于编译后的 .class 文件中,但运行时不可用
  3. RetentionPolicy.RUNTIME:注解在运行时可用,可以通过反射访问

三大策略定义如下图:

1764478666776-09bd8d0e-13fe-45d0-8f59-543f1a1b3ca2

4.2 @Target

指定注解可以应用于哪些代码元素。

  1. ElementType.TYPE:类、接口(包括注解类型)或枚举
  2. ElementType.FIELD:字段(包括枚举常量)
  3. ElementType.METHOD:方法
  4. ElementType.PARAMETER:方法参数
  5. ElementType.CONSTRUCTOR:构造方法
  6. ElementType.LOCAL_VARIABLE:局部变量
  7. ElementType.ANNOTATION_TYPE:注解类型
  8. ElementType.PACKAGE:包
  9. ElementType.TYPE_PARAMETER:允许注解出现在泛型类型参数上
  10. ElementType.TYPE_USE:类型使用的地方

5 常见注解

@Override

1764479595230-db38d2f0-41e0-4a36-8b44-865c180ec181

@Autowared
image

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);
        }
    }
}
posted @ 2025-11-30 13:28  临安剑客  阅读(7)  评论(0)    收藏  举报