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

时间:2015-09-23 12:53:05

标签: java bytecode 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;
  }

}

我希望实现类似于下面显示的内容,以避免多类变换器。

@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 "";

}

通过这个示例类,我可以做到:

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可以读取注释并创建一个类转换器,其反编译代码如下所示:

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;
    }
}

1 个答案:

答案 0 :(得分:1)

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

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

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