mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Add customizer and property support for configuring Jackson factories
Closes gh-34709
This commit is contained in:
@@ -108,6 +108,8 @@ To ease the migration when working on an application that previously used Jackso
|
||||
|
||||
The context's javadoc:tools.jackson.databind.json.JsonMapper$Builder[] can be customized by one or more javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans.
|
||||
Such customizer beans can be ordered (Boot's own customizer has an order of 0), letting additional customization be applied both before and after Boot's customization.
|
||||
Furthermore, the `JsonFactory` used by the builder and the mapper that it creates can be customized by one or more javadoc:org.springframework.boot.jackson.autoconfigure.JsonFactoryBuilderCustomizer[] beans.
|
||||
Various `spring.jackson.factory` properties can also be used to configure the factory.
|
||||
|
||||
Any beans of type javadoc:tools.jackson.databind.JacksonModule[] are automatically registered with the auto-configured javadoc:tools.jackson.databind.json.JsonMapper$Builder[] and are applied to any javadoc:tools.jackson.databind.json.JsonMapper[] instances that it creates.
|
||||
This provides an application-wide mechanism for contributing custom modules when you add new features to your application.
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.jackson.autoconfigure;
|
||||
|
||||
import tools.jackson.dataformat.cbor.CBORFactory;
|
||||
import tools.jackson.dataformat.cbor.CBORFactoryBuilder;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to further customize the
|
||||
* {@link CBORFactory} through {@link CBORFactoryBuilder} to fine-tune its
|
||||
* auto-configuration.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CborFactoryBuilderCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the CBORFactoryBuilder.
|
||||
* @param cborFactoryBuilder the builder to customize
|
||||
*/
|
||||
void customize(CBORFactoryBuilder cborFactoryBuilder);
|
||||
|
||||
}
|
||||
+176
-18
@@ -32,6 +32,11 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tools.jackson.core.StreamReadConstraints;
|
||||
import tools.jackson.core.StreamWriteConstraints;
|
||||
import tools.jackson.core.base.DecorableTSFactory.DecorableTSFBuilder;
|
||||
import tools.jackson.core.json.JsonFactory;
|
||||
import tools.jackson.core.json.JsonFactoryBuilder;
|
||||
import tools.jackson.databind.JacksonModule;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.PropertyNamingStrategies;
|
||||
@@ -40,7 +45,11 @@ import tools.jackson.databind.cfg.ConstructorDetector;
|
||||
import tools.jackson.databind.cfg.DateTimeFeature;
|
||||
import tools.jackson.databind.cfg.MapperBuilder;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.dataformat.cbor.CBORFactory;
|
||||
import tools.jackson.dataformat.cbor.CBORFactoryBuilder;
|
||||
import tools.jackson.dataformat.cbor.CBORMapper;
|
||||
import tools.jackson.dataformat.xml.XmlFactory;
|
||||
import tools.jackson.dataformat.xml.XmlFactoryBuilder;
|
||||
import tools.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
@@ -55,10 +64,14 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.jackson.JacksonComponentModule;
|
||||
import org.springframework.boot.jackson.JacksonMixinModule;
|
||||
import org.springframework.boot.jackson.JacksonMixinModuleEntries;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.ConstructorDetectorStrategy;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.Factory.Constraints;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.Factory.Constraints.Read;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.Factory.Constraints.Write;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -94,11 +107,21 @@ public final class JacksonAutoConfiguration {
|
||||
return new JacksonComponentModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JsonFactory jsonFactory(List<JsonFactoryBuilderCustomizer> customizers) {
|
||||
JsonFactoryBuilder builder = JsonFactory.builder();
|
||||
for (JsonFactoryBuilderCustomizer customizer : customizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
@ConditionalOnMissingBean
|
||||
JsonMapper.Builder jsonMapperBuilder(List<JsonMapperBuilderCustomizer> customizers) {
|
||||
JsonMapper.Builder builder = JsonMapper.builder();
|
||||
JsonMapper.Builder jsonMapperBuilder(List<JsonMapperBuilderCustomizer> customizers, JsonFactory jsonFactory) {
|
||||
JsonMapper.Builder builder = JsonMapper.builder(jsonFactory);
|
||||
customize(builder, customizers);
|
||||
return builder;
|
||||
}
|
||||
@@ -137,12 +160,36 @@ public final class JacksonAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(JacksonProperties.class)
|
||||
static class JacksonJsonMapperBuilderCustomizerConfiguration {
|
||||
static class JacksonJsonCustomizerConfiguration {
|
||||
|
||||
private final JacksonProperties jacksonProperties;
|
||||
|
||||
JacksonJsonCustomizerConfiguration(JacksonProperties jacksonProperties) {
|
||||
this.jacksonProperties = jacksonProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardJsonMapperBuilderCustomizer standardJsonMapperBuilderCustomizer(JacksonProperties jacksonProperties,
|
||||
ObjectProvider<JacksonModule> modules) {
|
||||
return new StandardJsonMapperBuilderCustomizer(jacksonProperties, modules.stream().toList());
|
||||
StandardJsonFactoryBuilderCustomizer standardJsonFactoryBuilderCustomizer() {
|
||||
return new StandardJsonFactoryBuilderCustomizer(this.jacksonProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardJsonMapperBuilderCustomizer standardJsonMapperBuilderCustomizer(ObjectProvider<JacksonModule> modules) {
|
||||
return new StandardJsonMapperBuilderCustomizer(this.jacksonProperties, modules.stream().toList());
|
||||
}
|
||||
|
||||
static final class StandardJsonFactoryBuilderCustomizer
|
||||
extends AbstractFactoryBuilderCustomizer<JsonFactoryBuilder> implements JsonFactoryBuilderCustomizer {
|
||||
|
||||
StandardJsonFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
|
||||
super(jacksonProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(JsonFactoryBuilder jsonFactoryBuilder) {
|
||||
super.customize(jsonFactoryBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class StandardJsonMapperBuilderCustomizer
|
||||
@@ -189,17 +236,27 @@ public final class JacksonAutoConfiguration {
|
||||
@EnableConfigurationProperties(JacksonCborProperties.class)
|
||||
static class CborConfiguration {
|
||||
|
||||
private final JacksonProperties jacksonProperties;
|
||||
|
||||
CborConfiguration(JacksonProperties jacksonProperties) {
|
||||
this.jacksonProperties = jacksonProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CBORMapper cborMapper(CBORMapper.Builder builder) {
|
||||
CBORFactory cborFactory(List<CborFactoryBuilderCustomizer> customizers) {
|
||||
CBORFactoryBuilder builder = CBORFactory.builder();
|
||||
for (CborFactoryBuilderCustomizer customizer : customizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
@ConditionalOnMissingBean
|
||||
CBORMapper.Builder cborMapperBuilder(List<CborMapperBuilderCustomizer> customizers) {
|
||||
CBORMapper.Builder builder = CBORMapper.builder();
|
||||
CBORMapper.Builder cborMapperBuilder(CBORFactory factory, List<CborMapperBuilderCustomizer> customizers) {
|
||||
CBORMapper.Builder builder = CBORMapper.builder(factory);
|
||||
customize(builder, customizers);
|
||||
return builder;
|
||||
}
|
||||
@@ -211,12 +268,37 @@ public final class JacksonAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardCborMapperBuilderCustomizer standardCborMapperBuilderCustomizer(JacksonProperties jacksonProperties,
|
||||
ObjectProvider<JacksonModule> modules, JacksonCborProperties cborProperties) {
|
||||
return new StandardCborMapperBuilderCustomizer(jacksonProperties, modules.stream().toList(),
|
||||
@ConditionalOnMissingBean
|
||||
CBORMapper cborMapper(CBORMapper.Builder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardCborFactoryBuilderCustomizer standardCborFactoryBuilderCustomizer() {
|
||||
return new StandardCborFactoryBuilderCustomizer(this.jacksonProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardCborMapperBuilderCustomizer standardCborMapperBuilderCustomizer(ObjectProvider<JacksonModule> modules,
|
||||
JacksonCborProperties cborProperties) {
|
||||
return new StandardCborMapperBuilderCustomizer(this.jacksonProperties, modules.stream().toList(),
|
||||
cborProperties);
|
||||
}
|
||||
|
||||
static final class StandardCborFactoryBuilderCustomizer
|
||||
extends AbstractFactoryBuilderCustomizer<CBORFactoryBuilder> implements CborFactoryBuilderCustomizer {
|
||||
|
||||
StandardCborFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
|
||||
super(jacksonProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(CBORFactoryBuilder cborFactoryBuilder) {
|
||||
super.customize(cborFactoryBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class StandardCborMapperBuilderCustomizer extends AbstractMapperBuilderCustomizer<CBORMapper.Builder>
|
||||
implements CborMapperBuilderCustomizer {
|
||||
|
||||
@@ -244,17 +326,27 @@ public final class JacksonAutoConfiguration {
|
||||
@EnableConfigurationProperties(JacksonXmlProperties.class)
|
||||
static class XmlConfiguration {
|
||||
|
||||
private final JacksonProperties jacksonProperties;
|
||||
|
||||
XmlConfiguration(JacksonProperties jacksonProperties) {
|
||||
this.jacksonProperties = jacksonProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
XmlMapper xmlMapper(XmlMapper.Builder builder) {
|
||||
XmlFactory xmlFactory(List<XmlFactoryBuilderCustomizer> customizers) {
|
||||
XmlFactoryBuilder builder = XmlFactory.builder();
|
||||
for (XmlFactoryBuilderCustomizer customizer : customizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
@ConditionalOnMissingBean
|
||||
XmlMapper.Builder xmlMapperBuilder(List<XmlMapperBuilderCustomizer> customizers) {
|
||||
XmlMapper.Builder builder = XmlMapper.builder();
|
||||
XmlMapper.Builder xmlMapperBuilder(XmlFactory xmlFactory, List<XmlMapperBuilderCustomizer> customizers) {
|
||||
XmlMapper.Builder builder = XmlMapper.builder(xmlFactory);
|
||||
customize(builder, customizers);
|
||||
return builder;
|
||||
}
|
||||
@@ -266,9 +358,21 @@ public final class JacksonAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardXmlMapperBuilderCustomizer standardXmlMapperBuilderCustomizer(JacksonProperties jacksonProperties,
|
||||
ObjectProvider<JacksonModule> modules, JacksonXmlProperties xmlProperties) {
|
||||
return new StandardXmlMapperBuilderCustomizer(jacksonProperties, modules.stream().toList(), xmlProperties);
|
||||
@ConditionalOnMissingBean
|
||||
XmlMapper xmlMapper(XmlMapper.Builder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardXmlFactoryBuilderCustomizer standardXmlFactoryBuilderCustomizer() {
|
||||
return new StandardXmlFactoryBuilderCustomizer(this.jacksonProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
StandardXmlMapperBuilderCustomizer standardXmlMapperBuilderCustomizer(ObjectProvider<JacksonModule> modules,
|
||||
JacksonXmlProperties xmlProperties) {
|
||||
return new StandardXmlMapperBuilderCustomizer(this.jacksonProperties, modules.stream().toList(),
|
||||
xmlProperties);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@@ -291,6 +395,20 @@ public final class JacksonAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
static final class StandardXmlFactoryBuilderCustomizer
|
||||
extends AbstractFactoryBuilderCustomizer<XmlFactoryBuilder> implements XmlFactoryBuilderCustomizer {
|
||||
|
||||
StandardXmlFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
|
||||
super(jacksonProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(XmlFactoryBuilder xmlFactoryBuilder) {
|
||||
super.customize(xmlFactoryBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class StandardXmlMapperBuilderCustomizer extends AbstractMapperBuilderCustomizer<XmlMapper.Builder>
|
||||
implements XmlMapperBuilderCustomizer {
|
||||
|
||||
@@ -344,6 +462,46 @@ public final class JacksonAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
abstract static class AbstractFactoryBuilderCustomizer<B extends DecorableTSFBuilder<?, ?>> implements Ordered {
|
||||
|
||||
private final JacksonProperties jacksonProperties;
|
||||
|
||||
AbstractFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
|
||||
this.jacksonProperties = jacksonProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void customize(B builder) {
|
||||
Constraints constraints = this.jacksonProperties.getFactory().getConstraints();
|
||||
builder.streamReadConstraints(readConstraintsFrom(constraints.getRead()));
|
||||
builder.streamWriteConstraints(writeConstraintsFrom(constraints.getWrite()));
|
||||
}
|
||||
|
||||
private StreamReadConstraints readConstraintsFrom(Read read) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
StreamReadConstraints.Builder constraintsBuilder = StreamReadConstraints.builder();
|
||||
map.from(read::getMaxDocumentLength).to(constraintsBuilder::maxDocumentLength);
|
||||
map.from(read::getMaxNameLength).to(constraintsBuilder::maxNameLength);
|
||||
map.from(read::getMaxNestingDepth).to(constraintsBuilder::maxNestingDepth);
|
||||
map.from(read::getMaxNumberLength).to(constraintsBuilder::maxNumberLength);
|
||||
map.from(read::getMaxStringLength).to(constraintsBuilder::maxStringLength);
|
||||
map.from(read::getMaxTokenCount).to(constraintsBuilder::maxTokenCount);
|
||||
return constraintsBuilder.build();
|
||||
}
|
||||
|
||||
private StreamWriteConstraints writeConstraintsFrom(Write write) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
StreamWriteConstraints.Builder constraintsBuilder = StreamWriteConstraints.builder();
|
||||
map.from(write::getMaxNestingDepth).to(constraintsBuilder::maxNestingDepth);
|
||||
return constraintsBuilder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract static class AbstractMapperBuilderCustomizer<B extends MapperBuilder<?, ?>> implements Ordered {
|
||||
|
||||
private final JacksonProperties jacksonProperties;
|
||||
|
||||
+135
@@ -137,6 +137,8 @@ public class JacksonProperties {
|
||||
|
||||
private final Json json = new Json();
|
||||
|
||||
private final Factory factory = new Factory();
|
||||
|
||||
public @Nullable String getDateFormat() {
|
||||
return this.dateFormat;
|
||||
}
|
||||
@@ -241,6 +243,10 @@ public class JacksonProperties {
|
||||
return this.json;
|
||||
}
|
||||
|
||||
public Factory getFactory() {
|
||||
return this.factory;
|
||||
}
|
||||
|
||||
public enum ConstructorDetectorStrategy {
|
||||
|
||||
/**
|
||||
@@ -319,4 +325,133 @@ public class JacksonProperties {
|
||||
|
||||
}
|
||||
|
||||
public static class Factory {
|
||||
|
||||
private final Constraints constraints = new Constraints();
|
||||
|
||||
public Constraints getConstraints() {
|
||||
return this.constraints;
|
||||
}
|
||||
|
||||
public static class Constraints {
|
||||
|
||||
private final Read read = new Read();
|
||||
|
||||
private final Write write = new Write();
|
||||
|
||||
public Read getRead() {
|
||||
return this.read;
|
||||
}
|
||||
|
||||
public Write getWrite() {
|
||||
return this.write;
|
||||
}
|
||||
|
||||
public static class Read {
|
||||
|
||||
/**
|
||||
* Maximum nesting depth. The depth is a count of objects and arrays that
|
||||
* have not been closed.
|
||||
*/
|
||||
private int maxNestingDepth = 500;
|
||||
|
||||
/**
|
||||
* Maximum allowed document length. A value less than or equal to zero
|
||||
* indicates that any length is acceptable.
|
||||
*/
|
||||
private long maxDocumentLength = -1L;
|
||||
|
||||
/**
|
||||
* Maximum allowed token count. A value less than or equal to zero
|
||||
* indicates that any count is acceptable.
|
||||
*/
|
||||
private long maxTokenCount = -1L;
|
||||
|
||||
/**
|
||||
* Maximum number length.
|
||||
*/
|
||||
private int maxNumberLength = 1_000;
|
||||
|
||||
/**
|
||||
* Maximum string length.
|
||||
*/
|
||||
private int maxStringLength = 20_000_000;
|
||||
|
||||
/**
|
||||
* Maximum name length.
|
||||
*/
|
||||
private int maxNameLength = 50_000;
|
||||
|
||||
public int getMaxNestingDepth() {
|
||||
return this.maxNestingDepth;
|
||||
}
|
||||
|
||||
public void setMaxNestingDepth(int maxNestingDepth) {
|
||||
this.maxNestingDepth = maxNestingDepth;
|
||||
}
|
||||
|
||||
public long getMaxDocumentLength() {
|
||||
return this.maxDocumentLength;
|
||||
}
|
||||
|
||||
public void setMaxDocumentLength(long maxDocumentLength) {
|
||||
this.maxDocumentLength = maxDocumentLength;
|
||||
}
|
||||
|
||||
public long getMaxTokenCount() {
|
||||
return this.maxTokenCount;
|
||||
}
|
||||
|
||||
public void setMaxTokenCount(long maxTokenCount) {
|
||||
this.maxTokenCount = maxTokenCount;
|
||||
}
|
||||
|
||||
public int getMaxNumberLength() {
|
||||
return this.maxNumberLength;
|
||||
}
|
||||
|
||||
public void setMaxNumberLength(int maxNumberLength) {
|
||||
this.maxNumberLength = maxNumberLength;
|
||||
}
|
||||
|
||||
public int getMaxStringLength() {
|
||||
return this.maxStringLength;
|
||||
}
|
||||
|
||||
public void setMaxStringLength(int maxStringLength) {
|
||||
this.maxStringLength = maxStringLength;
|
||||
}
|
||||
|
||||
public int getMaxNameLength() {
|
||||
return this.maxNameLength;
|
||||
}
|
||||
|
||||
public void setMaxNameLength(int maxNameLength) {
|
||||
this.maxNameLength = maxNameLength;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Write {
|
||||
|
||||
/**
|
||||
* Maximum nesting depth. The depth is a count of objects and arrays that
|
||||
* have not been closed.
|
||||
*/
|
||||
private int maxNestingDepth = 500;
|
||||
|
||||
public int getMaxNestingDepth() {
|
||||
return this.maxNestingDepth;
|
||||
}
|
||||
|
||||
public void setMaxNestingDepth(int maxNestingDepth) {
|
||||
this.maxNestingDepth = maxNestingDepth;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.jackson.autoconfigure;
|
||||
|
||||
import tools.jackson.core.json.JsonFactory;
|
||||
import tools.jackson.core.json.JsonFactoryBuilder;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to further customize the
|
||||
* {@link JsonFactory} through {@link JsonFactoryBuilder} to fine-tune its
|
||||
* auto-configuration.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JsonFactoryBuilderCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the JsonFactoryBuilder.
|
||||
* @param jsonFactoryBuilder the builder to customize
|
||||
*/
|
||||
void customize(JsonFactoryBuilder jsonFactoryBuilder);
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.jackson.autoconfigure;
|
||||
|
||||
import tools.jackson.dataformat.xml.XmlFactory;
|
||||
import tools.jackson.dataformat.xml.XmlFactoryBuilder;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to further customize the
|
||||
* {@link XmlFactory} through {@link XmlFactoryBuilder} to fine-tune its
|
||||
* auto-configuration.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 4.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface XmlFactoryBuilderCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the XmlFactoryBuilder.
|
||||
* @param xmlFactoryBuilder the builder to customize
|
||||
*/
|
||||
void customize(XmlFactoryBuilder xmlFactoryBuilder);
|
||||
|
||||
}
|
||||
+112
-5
@@ -32,8 +32,12 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.core.StreamReadConstraints;
|
||||
import tools.jackson.core.StreamReadFeature;
|
||||
import tools.jackson.core.StreamWriteConstraints;
|
||||
import tools.jackson.core.StreamWriteFeature;
|
||||
import tools.jackson.core.TokenStreamFactory;
|
||||
import tools.jackson.core.json.JsonFactory;
|
||||
import tools.jackson.core.json.JsonReadFeature;
|
||||
import tools.jackson.core.json.JsonWriteFeature;
|
||||
import tools.jackson.databind.DeserializationFeature;
|
||||
@@ -56,7 +60,9 @@ import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.json.JsonMapper.Builder;
|
||||
import tools.jackson.databind.module.SimpleModule;
|
||||
import tools.jackson.databind.util.StdDateFormat;
|
||||
import tools.jackson.dataformat.cbor.CBORFactory;
|
||||
import tools.jackson.dataformat.cbor.CBORMapper;
|
||||
import tools.jackson.dataformat.xml.XmlFactory;
|
||||
import tools.jackson.dataformat.xml.XmlMapper;
|
||||
import tools.jackson.module.kotlin.KotlinModule;
|
||||
|
||||
@@ -70,9 +76,12 @@ import org.springframework.boot.jackson.JacksonMixin;
|
||||
import org.springframework.boot.jackson.JacksonMixinModule;
|
||||
import org.springframework.boot.jackson.JacksonMixinModuleEntries;
|
||||
import org.springframework.boot.jackson.ObjectValueSerializer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.CborConfiguration.StandardCborFactoryBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.CborConfiguration.StandardCborMapperBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.JacksonJsonMapperBuilderCustomizerConfiguration.StandardJsonMapperBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.JacksonJsonCustomizerConfiguration.StandardJsonFactoryBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.JacksonJsonCustomizerConfiguration.StandardJsonMapperBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.XmlConfiguration.StandardXmlFactoryBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.XmlConfiguration.StandardXmlMapperBuilderCustomizer;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -106,6 +115,12 @@ class JacksonAutoConfigurationTests {
|
||||
protected final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class));
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void definesFactory(MapperType mapperType) {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(mapperType.factoryClass));
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void definesMapper(MapperType mapperType) {
|
||||
@@ -118,6 +133,15 @@ class JacksonAutoConfigurationTests {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(mapperType.builderClass));
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void factoryBacksOffWhenCustomFactoryIsDefined(MapperType mapperType) {
|
||||
this.contextRunner.withBean("customFactory", mapperType.factoryClass).run((context) -> {
|
||||
assertThat(context).hasSingleBean(mapperType.factoryClass);
|
||||
assertThat(context).hasBean("customFactory");
|
||||
});
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void mapperBacksOffWhenCustomMapperIsDefined(MapperType mapperType) {
|
||||
@@ -161,6 +185,24 @@ class JacksonAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).hasSingleBean(StandardXmlMapperBuilderCustomizer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardJsonFactoryBuilderCustomizerDoesNotBackOffWhenCustomizerIsDefined() {
|
||||
this.contextRunner.withBean(JsonFactoryBuilderCustomizer.class, () -> mock(JsonFactoryBuilderCustomizer.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(StandardJsonFactoryBuilderCustomizer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardCborFactoryBuilderCustomizerDoesNotBackOffWhenCustomizerIsDefined() {
|
||||
this.contextRunner.withBean(CborFactoryBuilderCustomizer.class, () -> mock(CborFactoryBuilderCustomizer.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(StandardCborFactoryBuilderCustomizer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardXmlFactoryBuilderCustomizerDoesNotBackOffWhenCustomizerIsDefined() {
|
||||
this.contextRunner.withBean(XmlFactoryBuilderCustomizer.class, () -> mock(XmlFactoryBuilderCustomizer.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(StandardXmlFactoryBuilderCustomizer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doubleModuleRegistration() {
|
||||
this.contextRunner.withUserConfiguration(DoubleModulesConfig.class).run((context) -> {
|
||||
@@ -814,6 +856,63 @@ class JacksonAutoConfigurationTests {
|
||||
.doesNotHaveAnyElementsOfTypes(KotlinModule.class));
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void defaultStreamReadConstraintsMatchJacksonDefaults(MapperType mapperType) {
|
||||
this.contextRunner.run((context) -> {
|
||||
StreamReadConstraints streamReadConstraints = mapperType.getFactory(context).streamReadConstraints();
|
||||
assertThat(streamReadConstraints.getMaxDocumentLength())
|
||||
.isEqualTo(StreamReadConstraints.DEFAULT_MAX_DOC_LEN);
|
||||
assertThat(streamReadConstraints.getMaxNameLength()).isEqualTo(StreamReadConstraints.DEFAULT_MAX_NAME_LEN);
|
||||
assertThat(streamReadConstraints.getMaxNestingDepth()).isEqualTo(StreamReadConstraints.DEFAULT_MAX_DEPTH);
|
||||
assertThat(streamReadConstraints.getMaxNumberLength()).isEqualTo(StreamReadConstraints.DEFAULT_MAX_NUM_LEN);
|
||||
assertThat(streamReadConstraints.getMaxStringLength())
|
||||
.isEqualTo(StreamReadConstraints.DEFAULT_MAX_STRING_LEN);
|
||||
assertThat(streamReadConstraints.getMaxTokenCount())
|
||||
.isEqualTo(StreamReadConstraints.DEFAULT_MAX_TOKEN_COUNT);
|
||||
});
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void customStreamReadConstraintsAreAppliedToAutoConfiguredFactory(MapperType mapperType) {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.jackson.factory.constraints.read.max-document-length=1000",
|
||||
"spring.jackson.factory.constraints.read.max-name-length=1001",
|
||||
"spring.jackson.factory.constraints.read.max-nesting-depth=1002",
|
||||
"spring.jackson.factory.constraints.read.max-number-length=1003",
|
||||
"spring.jackson.factory.constraints.read.max-string-length=1004",
|
||||
"spring.jackson.factory.constraints.read.max-token-count=1005")
|
||||
.run((context) -> {
|
||||
StreamReadConstraints streamReadConstraints = mapperType.getFactory(context).streamReadConstraints();
|
||||
assertThat(streamReadConstraints.getMaxDocumentLength()).isEqualTo(1000);
|
||||
assertThat(streamReadConstraints.getMaxNameLength()).isEqualTo(1001);
|
||||
assertThat(streamReadConstraints.getMaxNestingDepth()).isEqualTo(1002);
|
||||
assertThat(streamReadConstraints.getMaxNumberLength()).isEqualTo(1003);
|
||||
assertThat(streamReadConstraints.getMaxStringLength()).isEqualTo(1004);
|
||||
assertThat(streamReadConstraints.getMaxTokenCount()).isEqualTo(1005);
|
||||
});
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void customStreamWriteConstraintsAreAppliedToAutoConfiguredFactory(MapperType mapperType) {
|
||||
this.contextRunner.withPropertyValues("spring.jackson.factory.constraints.write.max-nesting-depth=1000")
|
||||
.run((context) -> {
|
||||
StreamWriteConstraints streamWriteConstraints = mapperType.getFactory(context).streamWriteConstraints();
|
||||
assertThat(streamWriteConstraints.getMaxNestingDepth()).isEqualTo(1000);
|
||||
});
|
||||
}
|
||||
|
||||
@EnumSource
|
||||
@ParameterizedTest
|
||||
void defaultStreamWriteConstraintsMatchJacksonDefaults(MapperType mapperType) {
|
||||
this.contextRunner.run((context) -> {
|
||||
StreamWriteConstraints streamWriteConstraints = mapperType.getFactory(context).streamWriteConstraints();
|
||||
assertThat(streamWriteConstraints.getMaxNestingDepth()).isEqualTo(StreamReadConstraints.DEFAULT_MAX_DEPTH);
|
||||
});
|
||||
}
|
||||
|
||||
static class MyDateFormat extends SimpleDateFormat {
|
||||
|
||||
MyDateFormat() {
|
||||
@@ -1137,15 +1236,19 @@ class JacksonAutoConfigurationTests {
|
||||
|
||||
enum MapperType {
|
||||
|
||||
CBOR(CBORMapper.class, CBORMapper.Builder.class), JSON(JsonMapper.class, JsonMapper.Builder.class),
|
||||
XML(XmlMapper.class, XmlMapper.Builder.class);
|
||||
CBOR(CBORFactory.class, CBORMapper.class, CBORMapper.Builder.class),
|
||||
JSON(JsonFactory.class, JsonMapper.class, JsonMapper.Builder.class),
|
||||
XML(XmlFactory.class, XmlMapper.class, XmlMapper.Builder.class);
|
||||
|
||||
private final Class<? extends TokenStreamFactory> factoryClass;
|
||||
|
||||
private final Class<? extends ObjectMapper> mapperClass;
|
||||
|
||||
private final Class<? extends MapperBuilder<?, ?>> builderClass;
|
||||
|
||||
<M extends ObjectMapper, B extends MapperBuilder<M, B>> MapperType(Class<M> mapperClass,
|
||||
Class<B> builderClass) {
|
||||
<F extends TokenStreamFactory, M extends ObjectMapper, B extends MapperBuilder<M, B>> MapperType(
|
||||
Class<F> factoryClass, Class<M> mapperClass, Class<B> builderClass) {
|
||||
this.factoryClass = factoryClass;
|
||||
this.mapperClass = mapperClass;
|
||||
this.builderClass = builderClass;
|
||||
}
|
||||
@@ -1154,6 +1257,10 @@ class JacksonAutoConfigurationTests {
|
||||
return context.getBean(this.mapperClass);
|
||||
}
|
||||
|
||||
TokenStreamFactory getFactory(ApplicationContext context) {
|
||||
return context.getBean(this.mapperClass).tokenStreamFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user