问题
A Spring Boot application is tasked with updating a remote integration API every so many minutes. This application can be deployed to a test or prod environment, the application is informed of the end point it should be looking at through an "application.properties" flag. A POJO is being serialized with Jackson and pushed to the endpoint, with the JsonProperty annotations containing the field IDs for the API that it is being pushed to.
ie
@JsonProperty("field_001)
private String name;
@JsonProperty("field_002)
private String address;
The field labels for these values differ on the test endpoint. So the test endpoint might expect the properties to map as
@JsonProperty("field_005)
private String name;
@JsonProperty("field_006)
private String address;
I would like to be able to utilize the Spring Boot native support for profile based properties files. To read in the JsonProperty annotation values at run time from an external properties file.
So for example,
There might be three files application.properties, application-test.properties and application-prod.properties. Spring Boot could read in the test or prod properties in addition to the vanilla properties file based on the "spring.profiles.active" setting.
...-test.properties would contain the constant values for the test server fields. And ...-prod.properties would contain the constant values for the prod server fields.
Nesting annotations such as Spring's @Value tag, like this:
@JsonProperty(@Value("${property.file.reference.here}))
doesn't seem to work.
回答1:
I apologize for reviving an old question however I still was not able to find satisfying answer.
Here's my solution using extended JacksonAnnotationIntrospector
which allows to use ${environment.properties}
within @JsonProperty
annotation
First extend the introspector
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
}
Then use it with ObjectMapper
configuration
@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);
}
}
Examples:
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
The solution is reverse compatible so you can still use constant values in @JsonProperty
回答2:
I doubt you will be able to do this using Spring Expression Language (SpEL) inside of a Jackson annotation, as you are trying (with or without the @Value
annotation).
I would do this by creating a JsonSerializer<YourPojo>
and/or JsonDeserializer<YourPojo>
that takes in your SpEL expressions and creates (or reads) using the provided field names.
//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();
}
}
Being that this is a Spring managed bean, you would need to plug this into your Spring managed 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);
Some of this might not be necessary with SpringBoot's AutoConfiguration. I am generally unaware to what SpringBoot will pick up for its Jackson AutoConfiguration, but JsonSerializer
and JsonDeserializer
might be autoregistered if they are in the ApplicationContext
.
来源:https://stackoverflow.com/questions/35089257/conditional-jsonproperty-using-jackson-with-spring-boot