Quarkus扩展如何实现自定义注解标注字段的注入赋值
Quarkus 自定义注解字段注入实现方案
Quarkus 完全支持保留原有基于自定义注解的字段注入行为,不需要修改业务侧的注解写法,有两种成熟落地路径,可根据团队改造成本、性能要求选择:
方案一:直接复用原有CDI扩展(改造成本最低)
Quarkus 全版本线都兼容标准CDI规范,你之前在J2EE环境中实现的、基于重写InjectionTarget在Bean实例化阶段反射赋值的CDI扩展,几乎可以零改动迁移:- 将原有CDI扩展的依赖引入Quarkus项目
- 确认扩展的服务声明文件
META-INF/services/javax.enterprise.inject.spi.Extension(Quarkus 3.x+对应路径为META-INF/services/jakarta.enterprise.inject.spi.Extension)配置正确即可正常加载
这种方案完全对齐原有环境的注入逻辑,改造成本几乎为0,缺点是运行时依赖反射完成赋值,没有用到Quarkus构建时预编译的性能优势。
方案二:Quarkus构建时字节码增强实现(性能最优,推荐)
你之前调研的InjectionPointTransformerBuildItem仅用于修改注入点的元数据(比如追加限定符、调整注入类型),本身不负责实现字段赋值逻辑,不用在这个能力上耗费时间。要实现字段初始化逻辑增强,直接用Quarkus提供的构建时字节码增强能力即可,全程不需要运行时反射,启动速度、运行性能都远高于反射方案:- 构建阶段通过Jandex索引扫描所有Bean类中携带
@CustomAnnotation的字段,定位需要增强的目标类 - 通过
BytecodeTransformerBuildItem注册字节码增强器,在目标Bean的构造方法执行完成、return指令前,直接插入字段赋值的字节码逻辑 - 如果注入值需要在运行时动态获取(比如从配置中心、运行时上下文读取),配合Quarkus Recorder生成运行时方法调用的字节码即可,不需要额外找字段初始化的扩展点
核心实现代码示例如下:
- 构建阶段通过Jandex索引扫描所有Bean类中携带
import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.DotName; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.HashSet; import java.util.Set; public class CustomAnnotationProcessor { private static final DotName CUSTOM_ANNOTATION_DOTNAME = DotName.createSimple("com.yourpackage.CustomAnnotation"); @BuildStep void registerFieldInjector(CombinedIndexBuildItem indexBuildItem, BuildProducer<BytecodeTransformerBuildItem> transformerProducer) { // 扫描所有带@CustomAnnotation的字段,归集所属的Bean类 Set<String> targetClassNames = new HashSet<>(); for (AnnotationInstance annotation : indexBuildItem.getIndex().getAnnotations(CUSTOM_ANNOTATION_DOTNAME)) { if (annotation.target().kind() == AnnotationTarget.Kind.FIELD) { targetClassNames.add(annotation.target().asField().declaringClass().name().toString()); } } // 为每个目标类注册字节码增强逻辑 for (String className : targetClassNames) { transformerProducer.produce(new BytecodeTransformerBuildItem(className, (currentClassName, classVisitor) -> new ClassVisitor(Opcodes.ASM9, classVisitor) { @Override public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions); // 只增强类的构造方法 if (!"<init>".equals(methodName)) { return mv; } return new MethodVisitor(Opcodes.ASM9, mv) { @Override public void visitInsn(int opcode) { // 在构造方法执行完成、返回前插入字段赋值逻辑 if (opcode == Opcodes.RETURN) { // 示例:给String类型的myField字段注入值,实际场景可替换为Recorder调用逻辑获取动态值 mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitLdcInsn("injected value"); mv.visitFieldInsn(Opcodes.PUTFIELD, className.replace('.', '/'), "myField", "Ljava/lang/String;"); } super.visitInsn(opcode); } }; } })); } } }
注意:如果是Quarkus 3.x及以上版本,注意将原有代码中
javax.*包下的CDI相关引用替换为jakarta.*,其余逻辑无需调整。
内容的提问来源于stack exchange,提问作者cheeghi
相关产品推荐
相关产品推荐

