Skip to content

Commit 918e59b

Browse files
committed
Add customizer and property support for configuring Jackson factories
Closes gh-34709
1 parent 6c82077 commit 918e59b

File tree

7 files changed

+542
-23
lines changed

7 files changed

+542
-23
lines changed

documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ To ease the migration when working on an application that previously used Jackso
108108

109109
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.
110110
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.
111+
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.
112+
Various `spring.jackson.factory` properties can also be used to configure the factory.
111113

112114
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.
113115
This provides an application-wide mechanism for contributing custom modules when you add new features to your application.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.jackson.autoconfigure;
18+
19+
import tools.jackson.dataformat.cbor.CBORFactory;
20+
import tools.jackson.dataformat.cbor.CBORFactoryBuilder;
21+
22+
/**
23+
* Callback interface that can be implemented by beans wishing to further customize the
24+
* {@link CBORFactory} through {@link CBORFactoryBuilder} to fine-tune its
25+
* auto-configuration.
26+
*
27+
* @author Andy Wilkinson
28+
* @since 4.1.0
29+
*/
30+
@FunctionalInterface
31+
public interface CborFactoryBuilderCustomizer {
32+
33+
/**
34+
* Customize the CBORFactoryBuilder.
35+
* @param cborFactoryBuilder the builder to customize
36+
*/
37+
void customize(CBORFactoryBuilder cborFactoryBuilder);
38+
39+
}

module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java

Lines changed: 176 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
import com.fasterxml.jackson.annotation.JsonInclude.Include;
3333
import com.fasterxml.jackson.annotation.PropertyAccessor;
3434
import org.jspecify.annotations.Nullable;
35+
import tools.jackson.core.StreamReadConstraints;
36+
import tools.jackson.core.StreamWriteConstraints;
37+
import tools.jackson.core.base.DecorableTSFactory.DecorableTSFBuilder;
38+
import tools.jackson.core.json.JsonFactory;
39+
import tools.jackson.core.json.JsonFactoryBuilder;
3540
import tools.jackson.databind.JacksonModule;
3641
import tools.jackson.databind.ObjectMapper;
3742
import tools.jackson.databind.PropertyNamingStrategies;
@@ -40,7 +45,11 @@
4045
import tools.jackson.databind.cfg.DateTimeFeature;
4146
import tools.jackson.databind.cfg.MapperBuilder;
4247
import tools.jackson.databind.json.JsonMapper;
48+
import tools.jackson.dataformat.cbor.CBORFactory;
49+
import tools.jackson.dataformat.cbor.CBORFactoryBuilder;
4350
import tools.jackson.dataformat.cbor.CBORMapper;
51+
import tools.jackson.dataformat.xml.XmlFactory;
52+
import tools.jackson.dataformat.xml.XmlFactoryBuilder;
4453
import tools.jackson.dataformat.xml.XmlMapper;
4554

4655
import org.springframework.aot.hint.ReflectionHints;
@@ -55,10 +64,14 @@
5564
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
5665
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5766
import org.springframework.boot.context.properties.EnableConfigurationProperties;
67+
import org.springframework.boot.context.properties.PropertyMapper;
5868
import org.springframework.boot.jackson.JacksonComponentModule;
5969
import org.springframework.boot.jackson.JacksonMixinModule;
6070
import org.springframework.boot.jackson.JacksonMixinModuleEntries;
6171
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.ConstructorDetectorStrategy;
72+
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.Factory.Constraints;
73+
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.Factory.Constraints.Read;
74+
import org.springframework.boot.jackson.autoconfigure.JacksonProperties.Factory.Constraints.Write;
6275
import org.springframework.context.ApplicationContext;
6376
import org.springframework.context.annotation.Bean;
6477
import org.springframework.context.annotation.Configuration;
@@ -94,11 +107,21 @@ JacksonComponentModule jsonComponentModule() {
94107
return new JacksonComponentModule();
95108
}
96109

110+
@Bean
111+
@ConditionalOnMissingBean
112+
JsonFactory jsonFactory(List<JsonFactoryBuilderCustomizer> customizers) {
113+
JsonFactoryBuilder builder = JsonFactory.builder();
114+
for (JsonFactoryBuilderCustomizer customizer : customizers) {
115+
customizer.customize(builder);
116+
}
117+
return builder.build();
118+
}
119+
97120
@Bean
98121
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
99122
@ConditionalOnMissingBean
100-
JsonMapper.Builder jsonMapperBuilder(List<JsonMapperBuilderCustomizer> customizers) {
101-
JsonMapper.Builder builder = JsonMapper.builder();
123+
JsonMapper.Builder jsonMapperBuilder(List<JsonMapperBuilderCustomizer> customizers, JsonFactory jsonFactory) {
124+
JsonMapper.Builder builder = JsonMapper.builder(jsonFactory);
102125
customize(builder, customizers);
103126
return builder;
104127
}
@@ -137,12 +160,36 @@ JacksonMixinModule jacksonMixinModule(ApplicationContext context, JacksonMixinMo
137160

138161
@Configuration(proxyBeanMethods = false)
139162
@EnableConfigurationProperties(JacksonProperties.class)
140-
static class JacksonJsonMapperBuilderCustomizerConfiguration {
163+
static class JacksonJsonCustomizerConfiguration {
164+
165+
private final JacksonProperties jacksonProperties;
166+
167+
JacksonJsonCustomizerConfiguration(JacksonProperties jacksonProperties) {
168+
this.jacksonProperties = jacksonProperties;
169+
}
141170

142171
@Bean
143-
StandardJsonMapperBuilderCustomizer standardJsonMapperBuilderCustomizer(JacksonProperties jacksonProperties,
144-
ObjectProvider<JacksonModule> modules) {
145-
return new StandardJsonMapperBuilderCustomizer(jacksonProperties, modules.stream().toList());
172+
StandardJsonFactoryBuilderCustomizer standardJsonFactoryBuilderCustomizer() {
173+
return new StandardJsonFactoryBuilderCustomizer(this.jacksonProperties);
174+
}
175+
176+
@Bean
177+
StandardJsonMapperBuilderCustomizer standardJsonMapperBuilderCustomizer(ObjectProvider<JacksonModule> modules) {
178+
return new StandardJsonMapperBuilderCustomizer(this.jacksonProperties, modules.stream().toList());
179+
}
180+
181+
static final class StandardJsonFactoryBuilderCustomizer
182+
extends AbstractFactoryBuilderCustomizer<JsonFactoryBuilder> implements JsonFactoryBuilderCustomizer {
183+
184+
StandardJsonFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
185+
super(jacksonProperties);
186+
}
187+
188+
@Override
189+
public void customize(JsonFactoryBuilder jsonFactoryBuilder) {
190+
super.customize(jsonFactoryBuilder);
191+
}
192+
146193
}
147194

148195
static final class StandardJsonMapperBuilderCustomizer
@@ -189,17 +236,27 @@ public void customize(JsonMapper.Builder builder) {
189236
@EnableConfigurationProperties(JacksonCborProperties.class)
190237
static class CborConfiguration {
191238

239+
private final JacksonProperties jacksonProperties;
240+
241+
CborConfiguration(JacksonProperties jacksonProperties) {
242+
this.jacksonProperties = jacksonProperties;
243+
}
244+
192245
@Bean
193246
@ConditionalOnMissingBean
194-
CBORMapper cborMapper(CBORMapper.Builder builder) {
247+
CBORFactory cborFactory(List<CborFactoryBuilderCustomizer> customizers) {
248+
CBORFactoryBuilder builder = CBORFactory.builder();
249+
for (CborFactoryBuilderCustomizer customizer : customizers) {
250+
customizer.customize(builder);
251+
}
195252
return builder.build();
196253
}
197254

198255
@Bean
199256
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
200257
@ConditionalOnMissingBean
201-
CBORMapper.Builder cborMapperBuilder(List<CborMapperBuilderCustomizer> customizers) {
202-
CBORMapper.Builder builder = CBORMapper.builder();
258+
CBORMapper.Builder cborMapperBuilder(CBORFactory factory, List<CborMapperBuilderCustomizer> customizers) {
259+
CBORMapper.Builder builder = CBORMapper.builder(factory);
203260
customize(builder, customizers);
204261
return builder;
205262
}
@@ -211,12 +268,37 @@ private void customize(CBORMapper.Builder builder, List<CborMapperBuilderCustomi
211268
}
212269

213270
@Bean
214-
StandardCborMapperBuilderCustomizer standardCborMapperBuilderCustomizer(JacksonProperties jacksonProperties,
215-
ObjectProvider<JacksonModule> modules, JacksonCborProperties cborProperties) {
216-
return new StandardCborMapperBuilderCustomizer(jacksonProperties, modules.stream().toList(),
271+
@ConditionalOnMissingBean
272+
CBORMapper cborMapper(CBORMapper.Builder builder) {
273+
return builder.build();
274+
}
275+
276+
@Bean
277+
StandardCborFactoryBuilderCustomizer standardCborFactoryBuilderCustomizer() {
278+
return new StandardCborFactoryBuilderCustomizer(this.jacksonProperties);
279+
}
280+
281+
@Bean
282+
StandardCborMapperBuilderCustomizer standardCborMapperBuilderCustomizer(ObjectProvider<JacksonModule> modules,
283+
JacksonCborProperties cborProperties) {
284+
return new StandardCborMapperBuilderCustomizer(this.jacksonProperties, modules.stream().toList(),
217285
cborProperties);
218286
}
219287

288+
static final class StandardCborFactoryBuilderCustomizer
289+
extends AbstractFactoryBuilderCustomizer<CBORFactoryBuilder> implements CborFactoryBuilderCustomizer {
290+
291+
StandardCborFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
292+
super(jacksonProperties);
293+
}
294+
295+
@Override
296+
public void customize(CBORFactoryBuilder cborFactoryBuilder) {
297+
super.customize(cborFactoryBuilder);
298+
}
299+
300+
}
301+
220302
static class StandardCborMapperBuilderCustomizer extends AbstractMapperBuilderCustomizer<CBORMapper.Builder>
221303
implements CborMapperBuilderCustomizer {
222304

@@ -244,17 +326,27 @@ public void customize(CBORMapper.Builder builder) {
244326
@EnableConfigurationProperties(JacksonXmlProperties.class)
245327
static class XmlConfiguration {
246328

329+
private final JacksonProperties jacksonProperties;
330+
331+
XmlConfiguration(JacksonProperties jacksonProperties) {
332+
this.jacksonProperties = jacksonProperties;
333+
}
334+
247335
@Bean
248336
@ConditionalOnMissingBean
249-
XmlMapper xmlMapper(XmlMapper.Builder builder) {
337+
XmlFactory xmlFactory(List<XmlFactoryBuilderCustomizer> customizers) {
338+
XmlFactoryBuilder builder = XmlFactory.builder();
339+
for (XmlFactoryBuilderCustomizer customizer : customizers) {
340+
customizer.customize(builder);
341+
}
250342
return builder.build();
251343
}
252344

253345
@Bean
254346
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
255347
@ConditionalOnMissingBean
256-
XmlMapper.Builder xmlMapperBuilder(List<XmlMapperBuilderCustomizer> customizers) {
257-
XmlMapper.Builder builder = XmlMapper.builder();
348+
XmlMapper.Builder xmlMapperBuilder(XmlFactory xmlFactory, List<XmlMapperBuilderCustomizer> customizers) {
349+
XmlMapper.Builder builder = XmlMapper.builder(xmlFactory);
258350
customize(builder, customizers);
259351
return builder;
260352
}
@@ -266,9 +358,21 @@ private void customize(XmlMapper.Builder builder, List<XmlMapperBuilderCustomize
266358
}
267359

268360
@Bean
269-
StandardXmlMapperBuilderCustomizer standardXmlMapperBuilderCustomizer(JacksonProperties jacksonProperties,
270-
ObjectProvider<JacksonModule> modules, JacksonXmlProperties xmlProperties) {
271-
return new StandardXmlMapperBuilderCustomizer(jacksonProperties, modules.stream().toList(), xmlProperties);
361+
@ConditionalOnMissingBean
362+
XmlMapper xmlMapper(XmlMapper.Builder builder) {
363+
return builder.build();
364+
}
365+
366+
@Bean
367+
StandardXmlFactoryBuilderCustomizer standardXmlFactoryBuilderCustomizer() {
368+
return new StandardXmlFactoryBuilderCustomizer(this.jacksonProperties);
369+
}
370+
371+
@Bean
372+
StandardXmlMapperBuilderCustomizer standardXmlMapperBuilderCustomizer(ObjectProvider<JacksonModule> modules,
373+
JacksonXmlProperties xmlProperties) {
374+
return new StandardXmlMapperBuilderCustomizer(this.jacksonProperties, modules.stream().toList(),
375+
xmlProperties);
272376
}
273377

274378
@Configuration(proxyBeanMethods = false)
@@ -291,6 +395,20 @@ public void customize(XmlMapper.Builder builder) {
291395

292396
}
293397

398+
static final class StandardXmlFactoryBuilderCustomizer
399+
extends AbstractFactoryBuilderCustomizer<XmlFactoryBuilder> implements XmlFactoryBuilderCustomizer {
400+
401+
StandardXmlFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
402+
super(jacksonProperties);
403+
}
404+
405+
@Override
406+
public void customize(XmlFactoryBuilder xmlFactoryBuilder) {
407+
super.customize(xmlFactoryBuilder);
408+
}
409+
410+
}
411+
294412
static class StandardXmlMapperBuilderCustomizer extends AbstractMapperBuilderCustomizer<XmlMapper.Builder>
295413
implements XmlMapperBuilderCustomizer {
296414

@@ -344,6 +462,46 @@ private boolean isPropertyNamingStrategyField(Field candidate) {
344462

345463
}
346464

465+
abstract static class AbstractFactoryBuilderCustomizer<B extends DecorableTSFBuilder<?, ?>> implements Ordered {
466+
467+
private final JacksonProperties jacksonProperties;
468+
469+
AbstractFactoryBuilderCustomizer(JacksonProperties jacksonProperties) {
470+
this.jacksonProperties = jacksonProperties;
471+
}
472+
473+
@Override
474+
public int getOrder() {
475+
return 0;
476+
}
477+
478+
protected void customize(B builder) {
479+
Constraints constraints = this.jacksonProperties.getFactory().getConstraints();
480+
builder.streamReadConstraints(readConstraintsFrom(constraints.getRead()));
481+
builder.streamWriteConstraints(writeConstraintsFrom(constraints.getWrite()));
482+
}
483+
484+
private StreamReadConstraints readConstraintsFrom(Read read) {
485+
PropertyMapper map = PropertyMapper.get();
486+
StreamReadConstraints.Builder constraintsBuilder = StreamReadConstraints.builder();
487+
map.from(read::getMaxDocumentLength).to(constraintsBuilder::maxDocumentLength);
488+
map.from(read::getMaxNameLength).to(constraintsBuilder::maxNameLength);
489+
map.from(read::getMaxNestingDepth).to(constraintsBuilder::maxNestingDepth);
490+
map.from(read::getMaxNumberLength).to(constraintsBuilder::maxNumberLength);
491+
map.from(read::getMaxStringLength).to(constraintsBuilder::maxStringLength);
492+
map.from(read::getMaxTokenCount).to(constraintsBuilder::maxTokenCount);
493+
return constraintsBuilder.build();
494+
}
495+
496+
private StreamWriteConstraints writeConstraintsFrom(Write write) {
497+
PropertyMapper map = PropertyMapper.get();
498+
StreamWriteConstraints.Builder constraintsBuilder = StreamWriteConstraints.builder();
499+
map.from(write::getMaxNestingDepth).to(constraintsBuilder::maxNestingDepth);
500+
return constraintsBuilder.build();
501+
}
502+
503+
}
504+
347505
abstract static class AbstractMapperBuilderCustomizer<B extends MapperBuilder<?, ?>> implements Ordered {
348506

349507
private final JacksonProperties jacksonProperties;

0 commit comments

Comments
 (0)