Как предоставить метаданные сопоставления аннотаций MapStruct Mapping во время выполнения

Мы используем MapStruct между бизнес-моделью и нашей моделью пользовательского интерфейса. Когда клиент пользовательского интерфейса хочет получить отсортированные данные, он может указать поле из ui-model. Наш MapStructParser может получить соответствующее имя поля бизнес-модели и создать необходимые критерии для его сортировки.

Пример:

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

Проблема:
Как прочитать аннотацию @Mapping(source = "zip", target = "plz") и получить значения source и target? Mapping-Аннотация имеет RetentionPolicy.CLASS, поэтому получить к ней доступ через отражения невозможно.


person Tobse    schedule 01.07.2020    source источник
comment
Я внес изменения, но для справки: [java-bytecode-asm] — это тег для байт-кода Java, а не [assembly]. Я бы также предложил добавить тег [java], но для этого потребуется удалить еще один тег.   -  person Thomas Jager    schedule 01.07.2020


Ответы (1)


Мы решили эту проблему, используя ASM (среду обработки и анализа байт-кода), чтобы прочитать Mapping-аннотацию и предоставить ее. в метамодели:

Пример (также доступен на 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") возвращает сопоставленное поле, если BusinessModel (zip).
AnnotationParser — это анализатор аннотаций общего назначения, который предоставляет список MethodAnnotationInfo.
MapStructParser использует модель AnnotationParser для построения MapStructMappingInfo путем сбора Mapping-Аннотации.

Полный работоспособный и протестированный пример доступен здесь:
https://github.com/TobseF/mapstruct-metadata-example

Теоретически также возможно подключиться к процессу обработки аннотаций MapStruct и сгенерировать Java-классы MetaModel (ModelElementProcessor). Но я не смог заставить его работать. Добавление дополнительных обработчиков аннотаций — непростая задача, а отладка обработки аннотаций во время компиляции класса более чем обременительна. Для нашей цели достаточно ASM и простого отображения.

person Tobse    schedule 01.07.2020