Автоматически генерировать код для динамического создания Java объектов (отражение) - PullRequest
1 голос
/ 01 мая 2020

У меня есть классы, которые автоматически создаются из схемы JSON. В моем примере один из этих классов называется AvroPropertie.

Объявление и инициализация объектов из этого класса выглядит следующим образом:

AvroPropertie aPropertie = AvroPropertie.newBuilder()
                    .setName(properties.get("name").asText())
                    .setDate(properties.get("date").asText())
                    .build();

Если схема JSON отличается от переменных типа name и date также различаются. Там может быть больше, меньше или совершенно разные. Это всегда только эти сеттеры и метод build() в конце.

Есть ли способ автоматически сгенерировать этот код? Может быть, отражение?

Больше контекста: я использую ApacheAvro Сериализация и десериализация с генерацией кода .

Ty!

1 Ответ

1 голос
/ 02 мая 2020

Используя Reflection, вы можете:

  1. Вызвать static newBuilder метод для создания построителя.
  2. Найти все fields / setters для данного POJO учебный класс. Если во всех случаях setters в компоновщике и setters в POJO имеют одно и то же имя, как в вашем примере, преобразование должно быть простым.
  3. Вызов build в экземпляре компоновщика для создания нового POJO.

При использовании basi c Java Reflection и Stream API -s это может выглядеть следующим образом:

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Stream;

public class ReflectionApp {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.put("name", "John");
        properties.put("date", "today");

        AvroPropertie manualInstance = AvroPropertie.newBuilder()
                .setName(properties.getProperty("name"))
                .setDate(properties.getProperty("date"))
                .build();

        Object dynamicInstance = AvroAutoCoder.createAndSet(AvroPropertie.class, properties::getProperty);

        System.out.println(manualInstance);
        System.out.println(dynamicInstance);
        System.out.println("manualInstance == dynamicInstance => " + manualInstance.equals(dynamicInstance));
    }
}

class AvroAutoCoder {

    public static Object createAndSet(Class clazz, Function<String, String> dataSupplier) throws Exception {
        Object builderInstance = findMethod(clazz, "newBuilder")
                .invoke(null);

        Class<?> builderClass = builderInstance.getClass();
        getSetters(clazz).forEach(setter -> {
            try {
                String fieldName = setter.getName().substring(3).toLowerCase();
                findMethod(builderClass, setter.getName())
                        .invoke(builderInstance, dataSupplier.apply(fieldName));
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        });

        return findMethod(builderClass, "build")
                .invoke(builderInstance);
    }

    private static Method findMethod(Class clazz, String methodName) {
        return Arrays.stream(clazz.getDeclaredMethods())
                .filter(method -> method.getName().equals(methodName))
                .findFirst()
                .orElseThrow(IllegalArgumentException::new);
    }

    private static Stream<Method> getSetters(Class clazz) {
        return Arrays.stream(clazz.getDeclaredMethods())
                .filter(method -> method.getParameterCount() == 1 && method.getReturnType() == Void.TYPE)
                .filter(method -> method.getName().startsWith("set"));
    }
}

Над отпечатками кода:

AvroPropertie[name='John', date='today']
AvroPropertie[name='John', date='today']
manualInstance == dynamicInstance => true

Комментарий, добавленный автором вопроса:

В этом конкретном c примере класса APropertie есть внутренний класс c с именем Builder, который содержит искомый сеттера. В этом случае методы getSetters() нужно немного изменить:

// Find all setter of class
private static Stream<Method> getSetters2(Class clazz) {
    Optional<Class> first = Arrays.stream(clazz.getDeclaredClasses())
            .findFirst();

    return Arrays.stream(first.get().getDeclaredMethods())
            .filter(method -> method.getName().startsWith("set"));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...