使用 Byte Buddy 将 DTO 转换为实体的注释

Annotations to transform DTO to Entity using Byte Buddy

我有一个简单的实体用户。

public class User {

  String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

}

和他对应的DTO

public class UsuarioDTO {

  String name;

  String getName(){
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

}

我想实现如下所示的效果,以避免多个 class 转换器。

@Dto(entity = "Usuario")
public class UsuarioDTO {

  @BasicElement(name = "name")
  String name;

  String getName(){
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BasicElement {

  String name();

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Dto {

  String entity() default "";

}

有了这个例子class我能做到:

public class Transformer {

  public static void main(String[] args) {
    UserDTO usuarioDTO = new UserDTO("Gabriel");

    Class<UserDTO> obj = UserDTO.class;

    if (obj.isAnnotationPresent(Dto.class)) {

      Dto annotation = obj.getAnnotation(Dto.class);

      Class<?> clazz;
      try {
        clazz = Class.forName(annotation.entity());
        Constructor<?> constructor = clazz.getConstructor();
        Object instance = constructor.newInstance();

        for (Field originField : UserDTO.class.getDeclaredFields()) {
          originField.setAccessible(true);
          if (originField.isAnnotationPresent(BasicElement.class)) {
            BasicElement basicElement = originField.getAnnotation(BasicElement.class);
            Field destinationField = instance.getClass().getDeclaredField(basicElement.name());
            destinationField.setAccessible(true);
            destinationField.set(instance, originField.get(usuarioDTO));

          }
        }
        System.out.println(((User) instance).getName());

      } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    }
  }
}

但这会很昂贵,因为在每个转换中都会消耗注释。

使用 Byte-buddy 可以读取注释并创建一个 class 转换器,其反编译代码如下所示:

public class TransformerImpl implements ITransformer{
  public Object toEntity(Object dto){
    User user = new User();
    user.setName(dto.getName());
  }
}

更新: @Rafael Winterhalter,像这样吗?

public class Transformer<D,E> {

    List<Field> dtoFields = new ArrayList<Field>();

    Constructor<D> dtoConstructor;

    List<Field> entityFields = new ArrayList<Field>();

    Constructor<E> entityConstructor;

    public Transformer(Class<D> dtoClass){
        try {
            Dto annotation = dtoClass.getAnnotation(Dto.class);
            Class<E> entityClass = (Class<E>) annotation.entity();
            //entityConstructor = entityClass.getConstructor();
            entityConstructor = entityClass.getDeclaredConstructor();
            entityConstructor.setAccessible(true);
            dtoConstructor = dtoClass.getConstructor();
            dtoConstructor.setAccessible(true);
            lookupFields(entityClass, dtoClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void lookupFields(Class<E> entityClass, Class<D> dtoClass) throws NoSuchFieldException {
        for (Field dtoField : dtoClass.getDeclaredFields()) {
            if (dtoField.isAnnotationPresent(BasicElement.class)) {
                BasicElement basicElement = dtoField.getAnnotation(BasicElement.class);
                String entityFieldName = (basicElement.name().equals("")) ? dtoField.getName() : basicElement.name();
                Field entityField = entityClass.getDeclaredField(entityFieldName);
                dtoField.setAccessible(true);
                entityField.setAccessible(true);
                dtoFields.add(dtoField);
                entityFields.add(entityField);
            }
        }
    }

    public E toEntity(D dto) throws ReflectiveOperationException {
        E entity = entityConstructor.newInstance();
        for (int i = 0; i < entityFields.size(); i++){
            Field destination = entityFields.get(i);
            Field origin = dtoFields.get(i);
            destination.set(entity, origin.get(dto));
        }
        return entity;
    }

    public D toDto(E entity) throws ReflectiveOperationException {
        D dto = dtoConstructor.newInstance();
        for (int i = 0; i < entityFields.size(); i++){
            Field origin = entityFields.get(i);
            Field destination = dtoFields.get(i);
            destination.set(dto, origin.get(entity));
        }
        return dto;
    }
}

回答你的问题:是的,有可能。您可以要求 Byte Buddy 为您创建 ITransformer 的实例,您可以在其中实现唯一的方法来执行您想要的操作。但是,您需要为此实现自己的 Implementation 实例。

但是,我不建议你这样做。我通常会告诉用户,不应该将 Byte Buddy 用于性能工作,对于大多数用例来说,这是事实。您的用例就是其中之一。

如果您实现了 classes,则必须为任何映射缓存这些 classes。否则,class 发电成本将占很大一部分。相反,您宁愿维护一个转换器来缓存反射 API (reflective lookups are the expensive part of your operation, reflective invocation is not so problematic) 的对象并重用以前查找的值。这样,您无需将代码生成作为应用程序的另一个(复杂)元素拖入即可获得性能。