Статьи

Преобразование объектов в карту и обратно

В больших корпоративных приложениях иногда нам нужно преобразовывать объекты данных в Map и из Map . Обычно это промежуточный шаг к специальной сериализации. Если возможно использовать что-то стандартное, то лучше использовать это, но во многих случаях архитектура, предусмотренная неким ведущим архитектором, жесткой средой или по аналогичной причине, не позволяет использовать JOOQ, Hibernate, Jackson, JAX или что-то подобное. как это. В такой ситуации, как это случилось со мной несколько лет назад, мы должны преобразовать объекты в какой-то собственный формат, являющийся строковым или двоичным, и первым шагом в этом направлении является преобразование объекта в Map .

В конце концов, преобразование является более сложным, чем просто

1
Map myMap =  (Map)myObject;

потому что эти объекты почти никогда не являются картами сами по себе. Что нам действительно нужно в преобразовании, так это иметь Map которой каждая запись соответствует полю в классе «MyObject». Ключ в записи — это имя поля, а значение — это фактическое значение поля, которое может быть преобразовано в саму Map .

Одним из решений является использование отражения и рефлексивного чтения полей объекта и создания карты из него. Другой подход заключается в создании toMap() в классе, который необходимо преобразовать в Map которая просто добавляет каждое поле к возвращаемой карте, используя имя поля. Это несколько быстрее, чем основанное на отражении решение, а код намного проще.

Когда несколько лет назад я столкнулся с этой проблемой в реальном приложении, я был настолько разочарован написанием примитивных, но многочисленных toMap() для каждого объекта данных, что я создал простой инструмент, основанный на отражениях, который делал бы это для любого класса, который мы хотели , Это решило проблему? Нет.

Это была профессиональная среда, в которой важны не только функциональность, но и качество кода, и качество моего кода, судя по оценкам моих коллег-программистов. Они утверждали, что основанное на отражении решение является сложным, и в случае, если оно станет частью кодовой базы, разработчики, которые позже присоединятся к нему, не смогут его поддерживать. Ну, я должен был признать, что они были правы. В другой ситуации я бы сказал, что разработчик должен изучать рефлексию и программирование на Java на уровне, который необходим коду. В этом случае, однако, мы говорили не о конкретном человеке, а о ком-то, кто приходит и присоединяется к команде в будущем, возможно, когда-то, когда мы уже покинули проект. Этот человек, как предполагалось, был средним разработчиком, который казался разумным, поскольку мы ничего не знали об этом человеке. В этом смысле качество кода не было хорошим, потому что оно было слишком сложным. Кворум команды разработчиков решил, что поддержание многочисленных вручную созданных toMap() будет дешевле, чем поиск старших и опытных разработчиков в будущем.

Честно говоря, я немного неохотно принимал их решение, но я принял его, хотя у меня была возможность отменить его просто на основании моей позиции в команде. Я склонен принимать решения команды, даже если я не согласен с этим, но только если я могу жить с этими решениями. Если решение опасно, ужасно и угрожает будущему проекта, мы должны продолжать обсуждать детали, пока не достигнем соглашения.

Спустя годы я начал создавать Java :: Geci как побочный проект, который вы можете скачать с http://github.com/verhas/javageci

Java :: Geci — это инструмент генерации кода, который запускается на этапе тестирования жизненного цикла Java. Генерация кода в Java :: Geci — это «тест». Он запускает генерацию кода и в случае, если весь сгенерированный код остается на месте, тогда тест проходит успешно Если что-либо в базе кода изменилось таким образом, что генератор кода сгенерировал код, отличный от предыдущего, и, таким образом, исходный код изменился, то тест не пройден. Если тест не пройден, вы должны исправить ошибку и запустить сборку, включая тесты снова. В этом случае тест генерирует новый, уже исправленный код, поэтому все, что вам нужно сделать, это только запустить сборку снова.

При разработке фреймворка я создал несколько простых генераторов для генерации equals() и hashCode() , сеттеры и геттеры, генератор делегаторов и, наконец, я не смог устоять, но я создал универсальный генератор toMap() . Этот генератор генерирует код, который преобразует объект в Map точно так же, как мы обсуждали ранее, а также из fromMap() о котором я не упоминал ранее, но, очевидно, он также необходим.

Генераторы Java :: Geci — это классы, которые реализуют интерфейс Generator . Генератор Mapper делает это, расширяя абстрактный класс AbstractJavaGenerator . Это позволяет генератору генерировать любые исключения, облегчая жизнь разработчику генератора, а также он ищет класс Java, который был сгенерирован из текущего обработанного источника. Генератор имеет доступ к реальному объекту Class через параметр klass и одновременно к исходному коду через параметр source , который представляет исходный код и предоставляет методы для создания кода Java, который будет вставлен в него.

Третий параметр global — это что-то вроде карты, содержащей параметры конфигурации, которые определяет аннотация исходного кода @Geci .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package javax0.geci.mapper;
 
import ...
 
public class Mapper extends AbstractJavaGenerator {
 
...
 
    @Override
    public void process(Source source, Class<?> klass, CompoundParams global)
                                                             throws Exception {
        final var gid = global.get("id");
        var segment = source.open(gid);
        generateToMap(source, klass, global);
        generateFromMap(source, klass, global);
 
        final var factory = global.get("factory", "new {{class}}()");
        final var placeHolders = Map.of(
                "mnemonic", mnemonic(),
                "generatedBy", generatedAnnotation.getCanonicalName(),
                "class", klass.getSimpleName(),
                "factory", factory,
                "Map", "java.util.Map",
                "HashMap", "java.util.HashMap"
        );
        final var rawContent = segment.getContent();
        try {
            segment.setContent(Format.format(rawContent, placeHolders));
        } catch (BadSyntax badSyntax) {
            throw new IOException(badSyntax);
        }
    }

Сам генератор вызывает только два метода generateToMap() и generateFromMap() , которые генерируют, поскольку имена подразумевают в toMap() и fromMap() .

Оба метода используют поддержку генерации источника, предоставляемую классом Segment и они также используют шаблоны, предоставляемые Jamal . Следует также отметить, что поля собираются с помощью метода инструментов отражения getAllFieldsSorted() который возвращает все поля, которые имеет класс, в определенном порядке, который не зависит от фактического поставщика JVM или версии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
private void generateToMap(Source source, Class<?> klass, CompoundParams global) throws Exception {
        final var fields = GeciReflectionTools.getAllFieldsSorted(klass);
        final var gid = global.get("id");
        var segment = source.open(gid);
        segment.write_r(getResourceString("tomap.jam"));
        for (final var field : fields) {
            final var local = GeciReflectionTools.getParameters(field, mnemonic());
            final var params = new CompoundParams(local, global);
            final var filter = params.get("filter", DEFAULTS);
            if (Selector.compile(filter).match(field)) {
                final var name = field.getName();
                if (hasToMap(field.getType())) {
                    segment.write("map.put(\"%s\", %s == null ? null : %s.toMap0(cache));", field2MapKey(name), name, name);
                } else {
                    segment.write("map.put(\"%s\",%s);", field2MapKey(name), name);
                }
            }
        }
        segment.write("return map;")
                ._l("}\n\n");
    }

Код выбирает только те поля, которые обозначены выражением filter .

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Преобразование объектов в карту и обратно

Мнения, высказанные участниками Java Code Geeks, являются их собственными.