如何在运行时提供 MapStruct Mapping 注解映射元数据

How to provide MapStruct Mapping annotation mapping meta data at runtime

我们在业务模型和我们的ui-模型之间使用MapStruct。 当 UI 客户端想要获取排序后的数据时,它可以从 ui-model 中指定一个字段。 我们的 MapStructParser 可以获得相应的 business-model 字段名称并创建所需的条件对其进行排序。

示例:

public interface ModelMapping extends BridgeMapping<BusinessModel, UiModel> {
   @Mapping(source = "zip", target = "plz")
   UiModel modelToUiModel(BusinessModel model, @MappingTarget UiModel uiModel);
}

问题:
如何读取@Mapping(source = "zip", target = "plz")注解得到sourcetarget值? Mapping-注释有 RetentionPolicy.CLASS,因此无法通过反射访问它。

我们使用ASM(字节码操作和分析框架)解决了这个问题 阅读 Mapping-注释并在元模型中提供它:

示例(还有available on Github

public class AnnotationParser {
   public void parse(Class<?> mapper) {
      ClassInfoCollector classPrinter = new ClassInfoCollector(annotationInfos);
      ClassReader cr = new ClassReader(mapper.getCanonicalName());
      cr.accept(classPrinter, 0);
   }
}

public class ClassInfoCollector extends ClassVisitor {

   private final List<MethodAnnotationInfo> mapStructParser;

   public ClassInfoCollector(List<MethodAnnotationInfo> mapStructParser) {
      super(ASMversion);
      this.mapStructParser = mapStructParser;
   }

   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
      super.visit(version, access, name, signature, superName, interfaces);
   }

   @Override
   public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, String[] exceptions) {
      return new MethodInfoCollector(methodName,  mapStructParser);
   }

}

public class ClassInfoCollector extends ClassVisitor {

   private final List<MethodAnnotationInfo> mapStructParser;

   public ClassInfoCollector(List<MethodAnnotationInfo> mapStructParser) {
      super(ASMversion);
      this.mapStructParser = mapStructParser;
   }

   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
      super.visit(version, access, name, signature, superName, interfaces);
   }

   @Override
   public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, String[] exceptions) {
      return new MethodInfoCollector(methodName,  mapStructParser);
   }

}

class MethodInfoCollector extends MethodVisitor {

    private final String methodName;
    private final List<MethodAnnotationInfo> mapStructParser;

   public MethodInfoCollector(String method, List<MethodAnnotationInfo> mapStructParser) {
      super(ASMversion);
      this.methodName = method;
      this.mapStructParser = mapStructParser;
   }

   @Override
   public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { 
      return new MethodAnnotationInfoCollector(methodName, descriptor,  mapStructParser);
   }
}

class MethodAnnotationInfoCollector extends AnnotationVisitor {

   private final String method;
   private final String annotationType;
   private final List<MethodAnnotationInfo> mapStructParser;

   public MethodAnnotationInfoCollector(String method, String annotationType, List<MethodAnnotationInfo> mapStructParser) {
      super(ASMversion);
      this.method = method;
      this.annotationType = annotationType;
      this.mapStructParser = mapStructParser;
   }

   @Override
   public void visit(String name, Object value) {
      MethodAnnotationInfo annotationInfo = new MethodAnnotationInfo(method,  annotationType, name, value.toString());
      mapStructParser.add(annotationInfo);
      super.visit(name, value);
   }

}

有了这个 AnnotationParser 就可以通过这种方式获取映射信息:

class BusinessModel{
   String zip; 
}

class UiModel{
   String plz; 
}

public interface ModelMapping extends BridgeMapping<BusinessModel, UiModel> {
   @Mapping(source = "zip", target = "plz")
   UiModel modelToUiModel(BusinessModel model, @MappingTarget UiModel uiModel);
}

@Test
public testMappingInfo(){
   MapStructParser mappingInfo = new MapStructParser();
   mappingInfo.parseMappingInterface(ModelMapping.class);
   assertEquals("zip", mappingInfo.mapToTargetField("plz"));
}

mappingInfo.mapToTargetField("plz") returns if BusinessModel (zip).
的映射字段 AnnotationParser 是一个通用注释解析器,它提供 MethodAnnotationInfo.
的列表 MapStructParser 使用 AnnotationParser 模型通过收集 Mapping-注释来构建 MapStructMappingInfo

可在此处获得完整的可运行和测试示例:
https://github.com/TobseF/mapstruct-metadata-example

Theoretically it also possible to hook into the MapStruct annotation processing process and generate MetaModel Java classes (ModelElementProcessor). But I wasn't able to get it working. Adding additional Annotation processors is't straight forward and debugging Annotation processing during class compilation is more than cumbersome. Four our purpose the ASM way and a simple mapping is sufficient.