Правильно ли работает Джексон для конечных переменных, инициализированных во время объявления? - PullRequest
3 голосов
/ 21 февраля 2020

Учитывая следующий (неудачный) тест JUnit для Jackson 2.9.5:

package de.azamir.test;

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonFinalTest {

    private static final class MyClass {

        @JsonProperty("myProperty")
        public final String myProperty = "myPropertyValueInit";
    }

    @Test
    public void doTest() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, JsonParseException, JsonMappingException, IOException {

        final MyClass deserializedMyClass1 = new ObjectMapper().readValue("{\"myProperty\":\"myPropertyValueDeserialized\"}", MyClass.class);

        // "myPropertyValueInit"
        final String directValue = deserializedMyClass1.myProperty;

        // "myPropertyValueDeserialized"
        final String reflectValue = (String) MyClass.class.getDeclaredField("myProperty").get(deserializedMyClass1);

        assertEquals(directValue, reflectValue);

    }
}

Я подозреваю, что виртуальная машина Java оптимизирует доступ к конечным полям, так что значение, записанное Джексоном (через отражение) может быть восстановлен только через отражение. Это только сегодня привело к очень тонкой и трудной для поиска ошибке в нашей кодовой базе.

Является ли эта проблема, которая так или иначе решалась Джексоном? Мне кажется, что у кода Джексона нет способа «увидеть» это, но кто знает.

Я знаю, что есть несколько способов улучшить этот код, например, удалить

  • @JsonProperty и отключение конечных полей как мутаторов
  • не инициализирует поле во время объявления, но в конструкторе
  • не делает поля окончательными вообще

, но является Есть ли рекомендуемый способ убедиться, что ни один разработчик в нашей команде (кто может не знать об этом) делает это?

Ответы [ 2 ]

0 голосов
/ 21 февраля 2020

Я только что нашел другое решение, которое вызывает исключение RuntimeException. Вы можете проверить поле во время десериализации, используя BeanDeserializationModifier (на основе этот ответ ):

    @Test
    public void doTest() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, JsonParseException, JsonMappingException, IOException {

        final BeanDeserializerModifier modifier = new BeanDeserializerModifier() {

            @Override
            public List<BeanPropertyDefinition> updateProperties(final DeserializationConfig config, final BeanDescription beanDesc, final List<BeanPropertyDefinition> propDefs) {
                propDefs.stream()
                        .filter(pd -> pd.hasField())
                        .forEach(propDef -> {
                            final AnnotatedField f = propDef.getField();
                            if (Modifier.isFinal(f.getModifiers())) {
                                throw new RuntimeException("Bad final field " + f.getFullName());
                            }
                        });

                return super.updateProperties(config, beanDesc, propDefs);
            }

        };
        final DeserializerFactory dFactory = BeanDeserializerFactory.instance.withDeserializerModifier(modifier);
        final ObjectMapper mapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(dFactory));

        final ObjectMapper objectMapper = mapper;
        final String json = "{\"myProperty\":\"myPropertyValueDeserialized\"}";
        final MyClass deserializedMyClass1 = objectMapper.readValue(json, MyClass.class);

        // "myPropertyValueInit"
        final String directValue = deserializedMyClass1.myProperty;

        // "myPropertyValueDeserialized"
        final String reflectValue = (String) MyClass.class.getDeclaredField("myProperty").get(deserializedMyClass1);

        assertEquals(directValue, reflectValue);

    }
0 голосов
/ 21 февраля 2020

Таким образом, есть способ: (идея из этого ответа , также приятно читать это этот вопрос ) Вместо использования константного выражения и непосредственного назначения переменная на сайте объявления, инициализируйте ваши последние переменные в конструкторе. Смотрите изменения, когда вы используете MyClass2:

public class JacksonFinalTest {

    private static final class MyClass {

        @JsonProperty("myProperty")
        public final String myProperty = "myPropertyValueInit";
    }

    private static final class MyClass2 {

        @JsonProperty("myProperty")
        public final String myProperty;

        public MyClass2() {
            myProperty = "myPropertyValueInit";
        }
    }

    @Test
    public void doTest() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, JsonParseException, JsonMappingException, IOException {

        final String json = "{\"myProperty\":\"myPropertyValueDeserialized\"}";
        final MyClass2 deserializedMyClass1 = new ObjectMapper().readValue(json, MyClass2.class);

        // "myPropertyValueInit"
        final String directValue = deserializedMyClass1.myProperty;

        // "myPropertyValueDeserialized"
        final String reflectValue = (String) MyClass2.class.getDeclaredField("myProperty").get(deserializedMyClass1);

        assertEquals(directValue, reflectValue);

    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...