diff --git a/spring-web/src/main/java/org/springframework/http/support/JacksonHandlerInstantiator.java b/spring-web/src/main/java/org/springframework/http/support/JacksonHandlerInstantiator.java new file mode 100644 index 00000000000..cf2235fa2c6 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/support/JacksonHandlerInstantiator.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2025 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.http.support; + +import com.fasterxml.jackson.annotation.ObjectIdGenerator; +import com.fasterxml.jackson.annotation.ObjectIdResolver; +import org.jspecify.annotations.Nullable; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.KeyDeserializer; +import tools.jackson.databind.PropertyNamingStrategy; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.cfg.HandlerInstantiator; +import tools.jackson.databind.cfg.MapperConfig; +import tools.jackson.databind.deser.ValueInstantiator; +import tools.jackson.databind.introspect.Annotated; +import tools.jackson.databind.jsontype.TypeIdResolver; +import tools.jackson.databind.jsontype.TypeResolverBuilder; +import tools.jackson.databind.ser.VirtualBeanPropertyWriter; +import tools.jackson.databind.util.Converter; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.util.Assert; + +/** + * Allows for creating Jackson 3.x ({@link ValueSerializer}, {@link ValueDeserializer}, + * {@link KeyDeserializer}, {@link TypeResolverBuilder}, {@link TypeIdResolver}) + * beans with autowiring against a Spring {@link ApplicationContext}. + * + *
Also overrides all factory methods in {@link HandlerInstantiator},
+ * including non-abstract ones for {@link ValueInstantiator}, {@link ObjectIdGenerator}, {@link ObjectIdResolver},
+ * {@link PropertyNamingStrategy}, {@link Converter}, {@link VirtualBeanPropertyWriter}.
+ *
+ * @author Sebastien Deleuze
+ * @since 7.0
+ * @see ApplicationContext#getAutowireCapableBeanFactory()
+ * @see tools.jackson.databind.cfg.HandlerInstantiator
+ */
+public class JacksonHandlerInstantiator extends HandlerInstantiator {
+
+ private final AutowireCapableBeanFactory beanFactory;
+
+
+ /**
+ * Create a new AutowiredHandlerInstantiator for the given BeanFactory.
+ * @param beanFactory the target BeanFactory
+ */
+ public JacksonHandlerInstantiator(AutowireCapableBeanFactory beanFactory) {
+ Assert.notNull(beanFactory, "BeanFactory must not be null");
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ @Nullable
+ public ValueDeserializer> deserializerInstance(DeserializationConfig config, Annotated annotated, Class> deserClass) {
+ return (ValueDeserializer>) this.beanFactory.createBean(deserClass);
+ }
+
+ @Override
+ public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, Class> keyDeserClass) {
+ return (KeyDeserializer) this.beanFactory.createBean(keyDeserClass);
+ }
+
+ @Override
+ public ValueSerializer> serializerInstance(SerializationConfig config, Annotated annotated, Class> serClass) {
+ return (ValueSerializer>) this.beanFactory.createBean(serClass);
+ }
+
+ @Override
+ public TypeResolverBuilder> typeResolverBuilderInstance(MapperConfig> config, Annotated annotated, Class> builderClass) {
+ return (TypeResolverBuilder>) this.beanFactory.createBean(builderClass);
+ }
+
+ @Override
+ public TypeIdResolver typeIdResolverInstance(MapperConfig> config, Annotated annotated, Class> resolverClass) {
+ return (TypeIdResolver) this.beanFactory.createBean(resolverClass);
+ }
+
+ @Override
+ public ValueInstantiator valueInstantiatorInstance(MapperConfig> config, Annotated annotated, Class> implClass) {
+
+ return (ValueInstantiator) this.beanFactory.createBean(implClass);
+ }
+
+ @Override
+ public ObjectIdGenerator> objectIdGeneratorInstance(MapperConfig> config, Annotated annotated, Class> implClass) {
+
+ return (ObjectIdGenerator>) this.beanFactory.createBean(implClass);
+ }
+
+ @Override
+ public ObjectIdResolver resolverIdGeneratorInstance(MapperConfig> config, Annotated annotated, Class> implClass) {
+
+ return (ObjectIdResolver) this.beanFactory.createBean(implClass);
+ }
+
+ @Override
+ public PropertyNamingStrategy namingStrategyInstance(MapperConfig> config, Annotated annotated, Class> implClass) {
+
+ return (PropertyNamingStrategy) this.beanFactory.createBean(implClass);
+ }
+
+ @Override
+ public Converter, ?> converterInstance(MapperConfig> config, Annotated annotated, Class> implClass) {
+ return (Converter, ?>) this.beanFactory.createBean(implClass);
+ }
+
+ @Override
+ public VirtualBeanPropertyWriter virtualPropertyWriterInstance(MapperConfig> config, Class> implClass) {
+ return (VirtualBeanPropertyWriter) this.beanFactory.createBean(implClass);
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/support/JacksonHandlerInstantiatorTests.java b/spring-web/src/test/java/org/springframework/http/support/JacksonHandlerInstantiatorTests.java
new file mode 100644
index 00000000000..1c58297dc99
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/support/JacksonHandlerInstantiatorTests.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2002-2025 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.http.support;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.core.JsonParser;
+import tools.jackson.databind.DatabindContext;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.JavaType;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.KeyDeserializer;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.ValueDeserializer;
+import tools.jackson.databind.ValueSerializer;
+import tools.jackson.databind.annotation.JsonDeserialize;
+import tools.jackson.databind.annotation.JsonSerialize;
+import tools.jackson.databind.annotation.JsonTypeIdResolver;
+import tools.jackson.databind.annotation.JsonTypeResolver;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.databind.jsontype.NamedType;
+import tools.jackson.databind.jsontype.TypeDeserializer;
+import tools.jackson.databind.jsontype.TypeIdResolver;
+import tools.jackson.databind.jsontype.TypeSerializer;
+import tools.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test class for {@link JacksonHandlerInstantiator}.
+ *
+ * @author Sebastien Deleuze
+ */
+class JacksonHandlerInstantiatorTests {
+
+ private JacksonHandlerInstantiator instantiator;
+
+ private ObjectMapper objectMapper;
+
+
+ @BeforeEach
+ void setup() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("capitalizer", new RootBeanDefinition(Capitalizer.class));
+ instantiator = new JacksonHandlerInstantiator(bf);
+ objectMapper = JsonMapper.builder().handlerInstantiator(instantiator).build();
+ }
+
+
+ @Test
+ void autowiredSerializer() {
+ User user = new User("bob");
+ String json = this.objectMapper.writeValueAsString(user);
+ assertThat(json).isEqualTo("{\"username\":\"BOB\"}");
+ }
+
+ @Test
+ void autowiredDeserializer() {
+ String json = "{\"username\":\"bob\"}";
+ User user = this.objectMapper.readValue(json, User.class);
+ assertThat(user.getUsername()).isEqualTo("BOB");
+ }
+
+ @Test
+ void autowiredKeyDeserializer() {
+ String json = "{\"credentials\":{\"bob\":\"admin\"}}";
+ SecurityRegistry registry = this.objectMapper.readValue(json, SecurityRegistry.class);
+ assertThat(registry.getCredentials()).containsKey("BOB");
+ assertThat(registry.getCredentials()).doesNotContainKey("bob");
+ }
+
+ @Test
+ void applicationContextAwaretypeResolverBuilder() {
+ this.objectMapper.writeValueAsString(new Group());
+ assertThat(CustomTypeResolverBuilder.isAutowiredFiledInitialized).isTrue();
+ }
+
+ @Test
+ void applicationContextAwareTypeIdResolver() {
+ this.objectMapper.writeValueAsString(new Group());
+ assertThat(CustomTypeIdResolver.isAutowiredFiledInitialized).isTrue();
+ }
+
+
+ public static class UserDeserializer extends ValueDeserializer