Forma de replicar getters / setters para propiedades públicas en un POJO

9

Tenemos un POJO que se genera automáticamente con ~ 60 propiedades. Esto se genera con avro 1.4, que no incluye getters / setters.

Una biblioteca que utilizamos para proporcionar transformaciones simples entre objetos requiere métodos de tipo getter / setter para funcionar correctamente.

¿Hay alguna manera de replicar getters / setters sin tener que anular manualmente el POJO y crear todos los getters / setters manualmente?

public class BigGeneratedPojo {
  public String firstField;
  public int secondField;
  ...
  public ComplexObject nthField;
}

public class OtherObject {
  private String reprOfFirstFieldFromOtherObject;
  private ComplexObject reprOfFirstFieldFromOtherObject;
  public String getReprOfFirstFieldFromOtherObject() { ... standard impl ... };
  public void setReprOfFirstFieldFromOtherObject() { ... standard impl ... };
}

el deseo es escribir código que se vea así:

Mapper<BigGeneratedPojo, OtherObject> mapper = 
  MagicalMapperLibrary.mapperBuilder(BigGeneratedPojo.class, OtherObject.class)
    .from(BigGeneratedPojo::getFirstField).to(OtherObject::reprOfFirstFieldFromOtherObject)
    .build();

BigGeneratedPojo pojo = new BigGeneratedPojo();
pojo.firstField = "test";

OtherObject mappedOtherObj = mapper.map(pojo);

assertEquals(mappedOtherObj.getReprOfFirstFieldFromOtherObject(), "test");
Antonio
fuente

Respuestas:

7

Puede intentar generar los beans proxy dinámicamente, por ejemplo, usando BitBuddy: https://bytebuddy.net/

El siguiente ejemplo muestra cómo representar un campo de propiedad de un método. Tenga en cuenta que esto es solo una muestra, y lo más probable es que tenga que envolverlo y agregar algo dinámico usando reflexiones, pero creo que es una opción bastante interesante si desea extender el código dinámicamente.

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class M1 {

    public static class PojoBase {
        int property;
        String strProp;
    }



    public static class Intereptor {

        private final String fieldName;
        private final PojoBase pojo;
        public Intereptor(PojoBase pojo, String fieldName) {
            this.pojo = pojo;
            this.fieldName = fieldName;
        }
        @RuntimeType
        public Object intercept(@RuntimeType Object value) throws NoSuchFieldException, IllegalAccessException {

            Field field = pojo.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(pojo, value);
            return value;
        }
    }



    public static void main(String... args) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
            PojoBase origBean = new PojoBase();
            PojoBase destBean = new PojoBase();

            origBean.property = 555666;
            origBean.strProp = "FooBar";

        DynamicType.Builder<Object> stub = new ByteBuddy()
            .subclass(Object.class);

        DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<Object> dynamic = stub.defineMethod("getProperty", Integer.TYPE, Opcodes.ACC_PUBLIC).intercept(FixedValue.value(origBean.property))
                .defineMethod("setProperty", Void.TYPE, Opcodes.ACC_PUBLIC).withParameters(Integer.TYPE).intercept(MethodDelegation.to(new Intereptor(destBean, "property")))
                .defineMethod("getStrProp", String.class, Opcodes.ACC_PUBLIC).intercept(FixedValue.value(origBean.strProp))
                .defineMethod("setStrProp", Void.TYPE, Opcodes.ACC_PUBLIC).withParameters(String.class).intercept(MethodDelegation.to(new Intereptor(destBean, "strProp")));

        Class<?> dynamicType =     dynamic.make()
                .load(M1.class.getClassLoader())
                .getLoaded();


            Object readerObject = dynamicType.newInstance();
            Object writterObject = dynamicType.newInstance();


            BeanUtils.copyProperties(readerObject, writterObject);
            System.out.println("Out property:" + destBean.property);
            System.out.println("Out strProp:" + destBean.property);
    }



}
Vicctor
fuente
10

Project Lombok proporciona anotaciones @Getter y @Setter que se pueden usar a nivel de clase para generar métodos getter y setter automáticamente.

Lombok también tiene la capacidad de generar métodos igual y hashcode.

O puede usar el @Dataque está de acuerdo con el sitio web de lombok:

@Data All together now: Un acceso directo para @ToString, @EqualsAndHashCode, @Getter en todos los campos, @Setter en todos los campos no finales y @RequiredArgsConstructor.

@Data
public class BigGeneratedPojo {
  public String firstField;
  public int secondField;
  ...
  public ComplexObject nthField;
}
Karthik Rao
fuente
1
Lombok es fácil de usar y rápido de configurar. Esta es una buena solución.
Hayes Roach el
Creo que para un atajo es la implementación fácil, resolverá el problema y también le dará una alta legibilidad
leonardo rey
4

Dadas sus limitaciones, agregaría otro paso de generación de código. Cómo implementarlo depende exactamente en su sistema de construcción (Maven / Gradle / otra cosa), pero javaparser o tostador le permitirá analizar sintácticamente BigGeneratedPojo.javay crear una subclase con los getters / setters deseados, y el sistema de construcción debe actualizar de forma automática si BigGeneratedPojolos cambios.

Alexey Romanov
fuente
1

IDEs como Eclipse y STS brindan la opción de agregar métodos getters / setters. podemos usar esas opciones para crear métodos setters / getters

Durai Kasinathan
fuente
El problema no es escribir los métodos reales. Sé cómo generarlos rápidamente en intellij. El problema surge en el hecho de que BigGeneratedPojo es un archivo generado, por lo que para manipularlo realmente necesitaría subclasificarlo y tener una clase de contenedor con ~ 120 métodos ficticios (60 captadores / establecedores) y es una pesadilla de mantener.
Anthony
1
@Anthony Cuando abre el archivo en el editor del IDE, es irrelevante si el archivo se ha generado o escrito manualmente. En cualquier caso, puede agregar los métodos con una sola acción. Solo cuando planee volver a generar el archivo, no funcionará. Pero entonces, tener una clase con 60 propiedades potencialmente cambiantes ya es "una pesadilla para mantener".
Holger
1

Sugeriría usar la reflexión para obtener los campos públicos de su clase y crear los captadores y establecedores utilizando su propio programa Java de la siguiente manera. Considere la siguiente clase como ejemplo.

import java.lang.reflect.Field;
import java.util.Arrays;

class TestClass {

    private int value;
    private String name;
    private boolean flag;
}

public class GetterSetterGenerator {

    public static void main(String[] args) {
        try {
            GetterSetterGenerator gt = new GetterSetterGenerator();
            StringBuffer sb = new StringBuffer();

            Class<?> c = Class.forName("TestClass");
            // Getting fields of the class
            Field[] fields = c.getDeclaredFields();

            for (Field f : fields) {
                String fieldName = f.getName();
                String fieldType = f.getType().getSimpleName();

                gt.createSetter(fieldName, fieldType, sb);
                gt.createGetter(fieldName, fieldType, sb);
            }
            System.out.println("" + sb.toString());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void createSetter(String fieldName, String fieldType, StringBuffer setter) {
        setter.append("public void").append(" set");
        setter.append(getFieldName(fieldName));
        setter.append("(" + fieldType + " " + fieldName + ") {");
        setter.append("\n\t this." + fieldName + " = " + fieldName + ";");
        setter.append("\n" + "}" + "\n");
    }

    private void createGetter(String fieldName, String fieldType, StringBuffer getter) {
        // for boolean field method starts with "is" otherwise with "get"
        getter.append("public " + fieldType).append((fieldType.equals("boolean") ? " is" : " get") + getFieldName(fieldName) + " () { ");
        getter.append("\n\treturn " + fieldName + ";");
        getter.append("\n" + "}" + "\n");
    }

    private String getFieldName(String fieldName) {
        return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, fieldName.length());
    }
}

El código se toma desde aquí , ligeramente modificado para evitar innecesarios System.out. Puede crear fácilmente un archivo desde su mainfunción y poner allí sus captadores y establecedores.

Puede verificar el programa ejecutándolo aquí también. Espero que eso ayude.

Reaz Murshed
fuente
1

Puedes usar Lombok. Es fácil de usar e implementar. Creará getters y setter en la compilación posterior a archivos .class. También mantiene el código más limpio.

@Getter @Setter @NoArgsConstructor
public class User implements Serializable {
 private @Id Long id;

private String firstName;
private String lastName;
private int age;

public User(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
}

}

CodeRider
fuente