You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建具备实际功能的Java自定义注解?

自定义注解实现类似Lombok的方法生成:原理与解决方案

你之前用Aspect没效果,核心是搞混了运行时注解增强和编译期注解生成代码的区别,先理清楚这俩的差异:

为什么Aspect不适合你的需求?

Aspect是基于运行时注解的增强手段,它只能对已存在的方法做拦截、增强,没法生成新的方法。而且你之前的Aspect切点写错了:@After("@annotation(...)")是匹配方法上标注了该注解的方法,但你的@Dragonrider是加在类上的,所以根本匹配不到。就算改对切点(比如@After("within(@com.example.demo.annotations.Dragonrider *)")),也只能在已有方法执行后打印日志,没法生成新的fly方法。

而Lombok的@Getter/@Setter是编译期注解,在Java编译成字节码的阶段,通过注解处理器修改字节码,直接生成新的方法。这才是你需要的方向。


实现类似Lombok的自定义注解步骤

1. 修改注解的保留策略

首先把你的@Dragonrider改成编译期生效的(不需要保留到运行时):

package com.example.demo.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) // 编译期生效,编译后注解会被丢弃
public @interface Dragonrider {
    String name();
}

2. 编写注解处理器(核心)

注解处理器是编译时执行的代码,用来扫描标注了自定义注解的类,生成或修改字节码。这里用JavaPoet简化代码生成(也可以用ASM直接修改字节码,更接近Lombok的实现)。

第一步:添加依赖(Maven为例)

<dependency>
    <groupId>com.squareup</groupId>
    <artifactId>javapoet</artifactId>
    <version>1.13.0</version>
    <scope>provided</scope> <!-- 编译时用,不需要打包到项目 -->
</dependency>

第二步:实现注解处理器

package com.example.demo.processor;

import com.example.demo.annotations.Dragonrider;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.util.Set;

// 指定要处理的注解全类名
@SupportedAnnotationTypes("com.example.demo.annotations.Dragonrider")
// 指定支持的Java版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DragonriderProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历所有标注了@Dragonrider的类
        for (Element element : roundEnv.getElementsAnnotatedWith(Dragonrider.class)) {
            if (!(element instanceof TypeElement)) continue;
            TypeElement clazz = (TypeElement) element;
            String className = clazz.getSimpleName().toString();
            String packageName = processingEnv.getElementUtils().getPackageOf(clazz).getQualifiedName().toString();
            String riderName = clazz.getAnnotation(Dragonrider.class).name();

            // 生成fly方法的代码
            MethodSpec flyMethod = MethodSpec.methodBuilder("fly")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addStatement("System.out.println($S + \"'s dragon is soaring high!\")", riderName)
                    .build();

            // 生成一个带fly方法的子类(如果要直接修改原类,需要用ASM等字节码工具)
            TypeSpec subclass = TypeSpec.classBuilder(className + "WithFly")
                    .superclass(clazz.asType())
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(flyMethod)
                    .build();

            // 将生成的类写入编译目录
            try {
                JavaFile.builder(packageName, subclass)
                        .build()
                        .writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}

3. 注册注解处理器

在src/main/resources/META-INF/services下创建文件javax.annotation.processing.Processor,内容为处理器的全类名:

com.example.demo.processor.DragonriderProcessor

4. 编译测试

编译你的项目后,会在编译目录下生成DragonWithFly.java和对应的字节码文件,这个类继承自Dragon,并且带有fly方法。你可以直接使用这个子类:

public class Test {
    public static void main(String[] args) {
        DragonWithFly drogon = new DragonWithFly("Drogon", true, 1500.0);
        drogon.fly(); // 输出:Daenerys's dragon is soaring high!
    }
}

进阶:直接修改原类的字节码

上面的例子是生成子类,如果要像Lombok那样直接给原类添加方法,需要用到ASM或Javassist这类字节码操作库,在编译阶段修改原类的字节码,把fly方法直接注入进去。这部分逻辑更复杂,但原理和注解处理器一致,都是在编译期介入修改字节码。

内容的提问来源于stack exchange,提问作者Victor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 20:25:39