【问题标题】:Conditional JsonProperty using Jackson with Spring Boot使用 Jackson 和 Spring Boot 的条件 JsonProperty
【发布时间】:2016-05-07 11:13:39
【问题描述】:

Spring Boot 应用程序的任务是每隔几分钟更新一次远程集成 API。此应用程序可以部署到测试或生产环境,应用程序通过“application.properties”标志被告知它应该查看的端点。 POJO 正在使用 Jackson 序列化并推送到端点,其中 JsonProperty 注释包含要推送到的 API 的字段 ID。

@JsonProperty("field_001)
private String name;

@JsonProperty("field_002)
private String address;

这些值的字段标签在测试端点上有所不同。所以测试端点可能期望属性映射为

@JsonProperty("field_005)
private String name;

@JsonProperty("field_006)
private String address;

我希望能够利用 Spring Boot 对基于配置文件的属性文件的原生支持。在运行时从外部属性文件中读取 JsonProperty 注释值。

例如,

可能存在三个文件 application.properties、application-test.properties 和 application-prod.properties。 除了基于“spring.profiles.active”设置的普通属性文件之外,Spring Boot 还可以读取 test 或 prod 属性。

...-test.properties 将包含测试服务器字段的常量值。并且 ...-prod.properties 将包含 prod 服务器字段的常量值。

嵌套注解比如Spring的@Value标签,像这样:

@JsonProperty(@Value("${property.file.reference.here})) 

似乎不起作用。

【问题讨论】:

    标签: java json spring spring-boot jackson


    【解决方案1】:

    我怀疑您是否能够在 Jackson 注释中使用 Spring 表达式语言 (SpEL) 来执行此操作,因为您正在尝试(使用或不使用 @Value 注释)。

    我会通过创建一个 JsonSerializer<YourPojo> 和/或 JsonDeserializer<YourPojo> 来实现这一点,它接受您的 SpEL 表达式并使用提供的字段名称创建(或读取)。

    //make me a spring managed bean!
    public class PojoSerializer extends JsonSerializer<YourPojo> {
        @Value("${property.file.reference.name")
        private String nameField;
    
        @Value("${property.file.reference.address")
        private String addrField;
    
        @Override
        public void serialize(YourPojo pojo, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            jgen.writeStartObject();
            jgen.writeStringField(nameField, pojo.getName());
            jgen.writeStringField(addrField, pojo.getAddress());
            jgen.writeEndObject();
        }
    }
    

    由于这是一个 Spring 托管的 bean,您需要将其插入您的 Spring 托管 ObjectMapper

    ObjectMapper mapper = //my ObjectMapper from spring
    PojoSerializer pojoSerializer = //my PojoSerializer from spring
    
    SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
    module.addSerializer(YourPojo.class, pojoSerializer);
    mapper.registerModule(module);
    

    SpringBoot 的 AutoConfiguration 可能不需要其中的一些。我一般不知道 SpringBoot 会为其 Jackson AutoConfiguration 选择什么,但如果 JsonSerializerJsonDeserializerApplicationContext 中,它们可能会被自动注册。

    【讨论】:

    • 可以使用序列化程序来指示 POJO 上仅几个字段的序列化吗?有近 30 个字段会因环境而异。理想情况下,序列化程序可以包含序列化服务器特定字段所需的逻辑,但让 POJO 上的 @JsonProperty 值处理其余部分。
    • 我对此表示怀疑,但我会指出我不确定。
    【解决方案2】:

    我很抱歉再次提出一个老问题,但我仍然无法找到令人满意的答案。

    这是我使用扩展JacksonAnnotationIntrospector 的解决方案,它允许在@JsonProperty 注释中使用${environment.properties}

    首先扩展自省

    public class DynamicJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
        private final Environment environment;
    
        public DynamicJacksonAnnotationIntrospector(Environment environment) {
            this.environment = environment;
        }
    
        @Override
        public PropertyName findNameForSerialization(Annotated a) {
            PropertyName name = super.findNameForSerialization(a);
            if (name == null) {
                return null;
            }
            String simpleName = name.getSimpleName();
            return PropertyName.construct(environment.resolvePlaceholders(simpleName), name.getNamespace());
        }
        //For deserialization I think the same mechanism could be used,
        //just override `findNameForDeserialization`, although I haven't tested it
    }
    

    然后配合ObjectMapper配置使用

    @Configuration
    public class ObjectMapperConfiguration {
        @Bean
        public ObjectMapper getObjectMapper(DynamicJacksonAnnotationIntrospector introspector) {
            ObjectMapper mapper = new ObjectMapper();
            SerializationConfig config = mapper.getSerializationConfig().withInsertedAnnotationIntrospector(introspector);
            mapper.setConfig(config);
            return mapper;
        }
    
        @Bean
        public DynamicJacksonAnnotationIntrospector introspector(Environment environment) {
            return new DynamicJacksonAnnotationIntrospector(environment);
        }
    }
    

    例子:

    public class DynamicTestClass {
        @JsonProperty("${dynamic.property.name}")
        private String dynamicPropertyName;
        //getters/setters
    }
    
    @ContextConfiguration(classes = [
            ObjectMapperConfiguration
    ])
    @TestPropertySource("classpath:test.properties")
    class DynamicJacksonAnnotationIntrospectorTest extends Specification {
        @Autowired
        ObjectMapper mapper
    
        def "should find name for serialization from properties"() {
            def bean = new DynamicTestClass()
            bean.dynamicPropertyName = "qwerty"
    
            when:
            def result = mapper.writeValueAsString(bean)
    
            then:
            result == "{\"overriddenName\":\"qwerty\"}"
        }
    }
    

    test.properties

    dynamic.property.name=overriddenName
    

    该解决方案是反向兼容的,因此您仍然可以在@JsonProperty 中使用常量值

    【讨论】:

    • 这正是我所需要的,非常感谢。为了使其适用于反序列化,基本上复制序列化代码,但确保调用 super.findNameForDeserialization,并确保还在对象映射器上设置 DeserializationConfig,一切顺利。
    • @Matiz 您使用了哪些版本的库?使用您的代码时出现错误。 java.lang.IllegalStateException: Conflicting/ambiguous property name definitions (implicit name 'dynamicPropertyName'): found multiple explicit names: [${dynamic.property.name}, overriddenName], but also implicit accessor: [method com.severstal.jirasm.core.dto.jira.webhook.issue.DynamicTestClass#getDynamicPropertyName(0 params)][visible=true,ignore=false,explicitName=false]
    【解决方案3】:

    这是Matiz 的扩充答案。

    使用扩展JacksonAnnotationIntrospector 的解决方案允许在@JsonProperty 注释中使用${environment.properties}

    public class DynamicJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
        private final Environment environment;
    
        public DynamicJacksonAnnotationIntrospector(Environment environment) {
            this.environment = environment;
        }
    
        @Override
        public PropertyName findNameForSerialization(Annotated a) {
            return injectEnvironmentInJsonProperty(super.findNameForSerialization(a));
        }
    
        @Override
        public PropertyName findNameForDeserialization(Annotated a){
            return injectEnvironmentInJsonProperty(super.findNameForDeserialization(a));
        }
    
        @Override
        public PropertyName findRootName(AnnotatedClass ac) {
            return injectEnvironmentInJsonProperty(super.findNameForDeserialization(ac));
        }
    
        private PropertyName injectEnvironmentInJsonProperty(PropertyName name){
            if (name == null) {
                return null;
            }
            String simpleName = name.getSimpleName();
            log.info(environment.resolvePlaceholders(simpleName));
            return PropertyName.construct(environment.resolvePlaceholders(simpleName), name.getNamespace());
        }
    }
    

    为控制器中的实体识别创建 webconfig 类。

    @EnableWebMvc
    @Configuration
    @RequiredArgsConstructor
    public class WebConfig implements WebMvcConfigurer {
    
        private final ApplicationContext context;
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            //JSON
            AnnotationIntrospector pairedIntrospectors = AnnotationIntrospector.pair(introspector(context.getEnvironment()),
                    new JacksonAnnotationIntrospector());
            converters.add(new MappingJackson2HttpMessageConverter(
                    Jackson2ObjectMapperBuilder.json()
                            .annotationIntrospector(pairedIntrospectors)
                            .build()));
        }
    
        @Bean
        public DynamicJacksonAnnotationIntrospector introspector(Environment environment) {
            return new DynamicJacksonAnnotationIntrospector(environment);
        }
    
        @Bean
        @Primary
        public ObjectMapper getObjectMapper(DynamicJacksonAnnotationIntrospector introspector) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            SerializationConfig serializationConfig = mapper.getSerializationConfig()
                    .withInsertedAnnotationIntrospector(introspector);
            mapper.setConfig(serializationConfig);
            DeserializationConfig deserializationConfig = mapper.getDeserializationConfig()
                    .withInsertedAnnotationIntrospector(introspector);
            mapper.setConfig(deserializationConfig);
            return mapper;
        }
    }
    

    并在 Spring Boot 的自动配置中禁用类

    spring:
      autoconfigure:
        exclude:
          - org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
    

    例子

    @Getter
    @Setter
    public class DynamicTestClass {
        @JsonProperty("${dynamic.property.name}")
        private String dynamicPropertyName;
    }
    
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = JiraSmCoreApplication.class)
    @TestPropertySource("classpath:application-test.yml")
    public class DynamicJacksonAnnotationIntrospectorTest {
        @Autowired
        MappingJackson2HttpMessageConverter mapper;
    
        @Test
        public void shouldFindNameForSerializationFromProperties() throws JsonProcessingException {
            DynamicTestClass bean = new DynamicTestClass();
            bean.setDynamicPropertyName("qwerty");
            log.info(mapper.getObjectMapper().writeValueAsString(bean));
        }
    }
    

    application-test.yml

    spring:
      autoconfigure:
        exclude:
          - org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
    
    dynamic:
      property:
        name: overriddenName
    

    【讨论】:

      猜你喜欢
      • 2017-03-30
      • 2020-06-22
      • 2018-08-03
      • 2020-06-06
      • 2018-03-21
      • 2018-03-21
      • 2014-05-05
      • 2018-01-25
      • 1970-01-01
      相关资源
      最近更新 更多