第四章:结构化输出
- 作者:影子, Spring AI Alibaba Committer
- 本文档基于 Spring AI 1.0.0 版本,Spring AI Alibaba 1.0.0.2 版本
- 本章内容是结构化快速上手 + 源码解读
结构化输出 快速上手
将 AI 模型的结果转换为特定的数据类型(JSON、Java 类等),方便传递到其他应用程序函数和方法。以下实现了 Map、List、实例对象,实战代码可见:https://github.com/GTyingzi/spring-ai-tutorial 下的structured-output
pom 文件
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-openai</artifactId> <version>${spring-ai.version}</version> </dependency>
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-chat-client</artifactId> </dependency>
</dependencies>
application.yml
server: port: 8080
spring: application: name: structured-output
ai: openai: api-key: ${DASHSCOPEAPIKEY} base-url: https://dashscope.aliyuncs.com/compatible-mode chat: options: model: qwen-max
Map、List 转换
MapListController
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.converter.ListOutputConverter;import org.springframework.ai.converter.MapOutputConverter;import org.springframework.core.convert.support.DefaultConversionService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;
import java.util.List;import java.util.Map;
@RestController@RequestMapping("/map-list")public class MapListController {
private static final Logger logger = LoggerFactory.getLogger(MapListController.class);
private final ChatClient chatClient; private final MapOutputConverter mapConverter; private final ListOutputConverter listConverter;
public MapListController(ChatClient.Builder builder) { // map转换器 this.mapConverter = new MapOutputConverter(); // list转换器 this.listConverter = new ListOutputConverter(new DefaultConversionService());
this.chatClient = builder .build(); }
@GetMapping("/map") public Map<String, Object> map(@RequestParam(value = "query", defaultValue = "请为我描述下影子的特性") String query) { return chatClient.prompt(query) .advisors( a -> a.param(ChatClientAttributes.OUTPUT_FORMAT.getKey(), mapConverter.getFormat()) ).call().entity(mapConverter); }
@GetMapping("/list") public List<String> list(@RequestParam(value = "query", defaultValue = "请为我描述下影子的特性") String query) { return chatClient.prompt(query) .advisors( a -> a.param(ChatClientAttributes.OUTPUT_FORMAT.getKey(), listConverter.getFormat()) ).call().entity(listConverter); }
}
效果
转换为 Map 类型
转换为 List 类型
实例对象转换
BeanEntity
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"title", "date", "author", "content"}) // 指定属性的顺序public record BeanEntity(String title, String author, String date, String content) {}
BeanController
package com.spring.ai.tutorial.outparser.controller;
import com.spring.ai.tutorial.outparser.model.entity.BeanEntity;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.ai.converter.BeanOutputConverter;import org.springframework.ai.template.st.StTemplateRenderer;import org.springframework.core.ParameterizedTypeReference;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController@RequestMapping("/bean")public class BeanController {
private static final Logger log = LoggerFactory.getLogger(BeanController.class);
private final ChatClient chatClient; private final BeanOutputConverter<BeanEntity> converter;
public BeanController(ChatClient.Builder builder) { this.converter = new BeanOutputConverter<>( new ParameterizedTypeReference<BeanEntity>() { } ); this.chatClient = builder .build(); }
@GetMapping("/call") public String call(@RequestParam(value = "query", defaultValue = "以影子为作者,写一篇200字左右的有关人工智能诗篇") String query) { String result = chatClient.prompt(query) .call().content();
log.info("result: {}", result); assert result != null; try { BeanEntity convert = converter.convert(result); log.info("反序列成功,convert: {}", convert); } catch (Exception e) { log.error("反序列化失败"); } return result; }
@GetMapping("/call/format") public BeanEntity callFormat(@RequestParam(value = "query", defaultValue = "以影子为作者,写一篇200字左右的有关人工智能诗篇") String query) { return chatClient.prompt(query) .call().entity(BeanEntity.class); }
}
效果
结构化源码解读
FormatProvider(格式提供接口)
用于定义输出格式规划的接口,为 AI 模型生成的内容提供格式化指令
package org.springframework.ai.converter;
public interface FormatProvider {
String getFormat();
}
Converter(转换接口)
实现 convert 方法,格式 S 转换为 T
import org.springframework.lang.Nullable;import org.springframework.util.Assert;
@FunctionalInterfacepublic interface Converter<S, T> { @Nullable T convert(S source);
default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) { Assert.notNull(after, "'after' Converter must not be null"); return (s) -> { T initialResult = this.convert(s); return initialResult != null ? after.convert(initialResult) : null; }; }}
StructuredOutputConverter
用于将 AI 模型的原始输出转换为结构化数据的核心接口,结合 Converter 和 FormatProvider 的能力,既定义了数据转换逻辑,又明确了输出格式规范,确保模型输出能被可靠解析为 Java 对象
- Converter<String, T>:定义将原始字符串(模型输出)转换为结构化类型 T 的约定
- FormatProvider: 提供模型输出应遵循的格式规范(如 JSON、XML、CSV 等),供模型调用时作为提示词的一部分
package org.springframework.ai.converter;
import org.springframework.core.convert.converter.Converter;
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {}
BeanOutputConverter
用于将 AI 模型的原始输出转换为特定 Java 类型对象的转换器,通过生成目标类型的 JSON Schema 确保输出格式规范,并使用 Jackson 进行反序列化,实现从非结构化文本到结构化对象转换
Type type
:目标类型信息ObjectMapper objectMapper
:Jackson 的 JSON 序列化/反序列化工具,支持自定义配置String jsonSchema
:生成的目标类型的 JSON Schema 描述,用于模型输出格式约束
方法名称 | 描述 |
BeanOutputConverter | 根据目标类型、自定义ObjectMapper、泛型类型等构造 |
convert | 将 LLM 输出的原始文本转换为目标类型对象 |
getFormat | 返回 JSON Schema 指令,指导模型输出格式 |
getJsonSchema | 获取生成的 JSON Schema 字符串 |
getJsonSchemaMap | 将 JSON Schema 转换为 Map 结构,便于程序动态解析 |
package org.springframework.ai.converter;
import java.lang.reflect.Type;import java.util.Map;import java.util.Objects;
import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.core.util.DefaultIndenter;import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.ObjectWriter;import com.fasterxml.jackson.databind.json.JsonMapper;import com.github.victools.jsonschema.generator.Option;import com.github.victools.jsonschema.generator.SchemaGenerator;import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;import com.github.victools.jsonschema.module.jackson.JacksonModule;import com.github.victools.jsonschema.module.jackson.JacksonOption;import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import org.springframework.ai.util.JacksonUtils;import org.springframework.core.ParameterizedTypeReference;import org.springframework.lang.NonNull;
import static org.springframework.ai.util.LoggingMarkers.SENSITIVEDATAMARKER;
public class BeanOutputConverter<T> implements StructuredOutputConverter<T> {
private final Logger logger = LoggerFactory.getLogger(BeanOutputConverter.class);
private final Type type;
private final ObjectMapper objectMapper;
private String jsonSchema;
public BeanOutputConverter(Class<T> clazz) { this(ParameterizedTypeReference.forType(clazz)); }
public BeanOutputConverter(Class<T> clazz, ObjectMapper objectMapper) { this(ParameterizedTypeReference.forType(clazz), objectMapper); }
public BeanOutputConverter(ParameterizedTypeReference<T> typeRef) { this(typeRef.getType(), null); }
public BeanOutputConverter(ParameterizedTypeReference<T> typeRef, ObjectMapper objectMapper) { this(typeRef.getType(), objectMapper); }
private BeanOutputConverter(Type type, ObjectMapper objectMapper) { Objects.requireNonNull(type, "Type cannot be null;"); this.type = type; this.objectMapper = objectMapper != null ? objectMapper : getObjectMapper(); generateSchema(); }
private void generateSchema() { JacksonModule jacksonModule = new JacksonModule(JacksonOption.RESPECTJSONPROPERTYREQUIRED, JacksonOption.RESPECTJSONPROPERTYORDER); SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder( com.github.victools.jsonschema.generator.SchemaVersion.DRAFT202012, com.github.victools.jsonschema.generator.OptionPreset.PLAINJSON) .with(jacksonModule) .with(Option.FORBIDDENADDITIONALPROPERTIESBYDEFAULT); SchemaGeneratorConfig config = configBuilder.build(); SchemaGenerator generator = new SchemaGenerator(config); JsonNode jsonNode = generator.generateSchema(this.type); ObjectWriter objectWriter = this.objectMapper.writer(new DefaultPrettyPrinter() .withObjectIndenter(new DefaultIndenter().withLinefeed(System.lineSeparator()))); try { this.jsonSchema = objectWriter.writeValueAsString(jsonNode); } catch (JsonProcessingException e) { logger.error("Could not pretty print json schema for jsonNode: {}", jsonNode); throw new RuntimeException("Could not pretty print json schema for " + this.type, e); } }
@SuppressWarnings("unchecked") @Override public T convert(@NonNull String text) { try { // Remove leading and trailing whitespace text = text.trim();
// Check for and remove triple backticks and "json" identifier if (text.startsWith("```") && text.endsWith("```")) { // Remove the first line if it contains "```json" String[] lines = text.split("\n", 2); if (lines[0].trim().equalsIgnoreCase("```json")) { text = lines.length > 1 ? lines[1] : ""; } else { text = text.substring(3); // Remove leading ``` }
// Remove trailing ``` text = text.substring(0, text.length() - 3);
// Trim again to remove any potential whitespace text = text.trim(); } return (T) this.objectMapper.readValue(text, this.objectMapper.constructType(this.type)); } catch (JsonProcessingException e) { logger.error(SENSITIVEDATAMARKER, "Could not parse the given text to the desired target type: \"{}\" into {}", text, this.type); throw new RuntimeException(e); } }
protected ObjectMapper getObjectMapper() { return JsonMapper.builder() .addModules(JacksonUtils.instantiateAvailableModules()) .configure(DeserializationFeature.FAILONUNKNOWNPROPERTIES, false) .build(); }
@Override public String getFormat() { String template = """ Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in your response. Remove the ```json markdown from the output. Here is the JSON Schema instance your output must adhere to: ```%s``` """; return String.format(template, this.jsonSchema); }
public String getJsonSchema() { return this.jsonSchema; }
public Map<String, Object> getJsonSchemaMap() { try { return this.objectMapper.readValue(this.jsonSchema, Map.class); } catch (JsonProcessingException ex) { logger.error("Could not parse the JSON Schema to a Map object", ex); throw new IllegalStateException(ex); } }
}
ListOutputConverter
List 转换器,通过约定逗号分隔的文本格式,实现从非结构化文本到结构化列表数据的高效转换
- 继承自 AbstractConversionServiceOutputConverter 类,DefaultConversionService 实现复杂的类型转换
方法名称 | 描述 |
getFormat | 输送给大模型的List格式说明 |
convert | 将模型输出的实例字符串转List |
package org.springframework.ai.converter;
import java.util.List;
import org.springframework.core.convert.support.DefaultConversionService;import org.springframework.lang.NonNull;
public class ListOutputConverter extends AbstractConversionServiceOutputConverter<List<String>> {
public ListOutputConverter() { this(new DefaultConversionService()); }
public ListOutputConverter(DefaultConversionService defaultConversionService) { super(defaultConversionService); }
@Override public String getFormat() { return """ Respond with only a list of comma-separated values, without any leading or trailing text. Example format: foo, bar, baz """; }
@Override public List<String> convert(@NonNull String text) { return this.getConversionService().convert(text, List.class); }
}
AbstractConversionServiceOutputConverter
SpringAI 框架中所有基于 ConversionService 的结构化输出转换器的基类,其核心作用是 封装类型转换的通用逻辑,为子类提供统一的 DefaultConversionService 实例
package org.springframework.ai.converter;
import org.springframework.core.convert.support.DefaultConversionService;
public abstract class AbstractConversionServiceOutputConverter<T> implements StructuredOutputConverter<T> {
private final DefaultConversionService conversionService;
public AbstractConversionServiceOutputConverter(DefaultConversionService conversionService) { this.conversionService = conversionService; }
public DefaultConversionService getConversionService() { return this.conversionService; }
}
DefaultConversionService
Spring 框架中用于提供默认类型转逻辑的类,继承自 GenericConversionService,并扩展其能力,支持常见的标量类型、集合类型之间的转换
方法名称 | 描述 |
getSharedInstance | 缓存单例实例,确保全局唯一性 |
addDefaultConverters | 向注册表中注册所有默认的类型转换器 |
addCollectionConverters | 注册集合类型(数组、集合、Map)之间的转换器 |
addScalarConverters | 注册标量类型(非集合类型)的转换器 |
设计模式的典范:
- 单例模式:通过 getSharedInstance() 提供全局唯一实例,减少资源开销。
- 策略模式:通过 Converter 接口定义统一的转换策略,支持多种类型组合。
- 工厂模式:使用 ConverterFactory 动态生成转换器(如 StringToNumberConverterFactory)。
- 组合模式:通过 addDefaultConverters 组合标量和集合转换器,形成完整的转换体系
package org.springframework.core.convert.support;
import java.nio.charset.Charset;import java.util.Currency;import java.util.Locale;import java.util.UUID;import java.util.regex.Pattern;import kotlin.text.Regex;import org.springframework.core.KotlinDetector;import org.springframework.core.convert.ConversionService;import org.springframework.core.convert.converter.ConverterRegistry;import org.springframework.lang.Nullable;
public class DefaultConversionService extends GenericConversionService { @Nullable private static volatile DefaultConversionService sharedInstance;
public DefaultConversionService() { addDefaultConverters(this); }
public static ConversionService getSharedInstance() { DefaultConversionService cs = sharedInstance; if (cs == null) { synchronized(DefaultConversionService.class) { cs = sharedInstance; if (cs == null) { cs = new DefaultConversionService(); sharedInstance = cs; } } }
return cs; }
public static void addDefaultConverters(ConverterRegistry converterRegistry) { addScalarConverters(converterRegistry); addCollectionConverters(converterRegistry); converterRegistry.addConverter(new ByteBufferConverter((ConversionService)converterRegistry)); converterRegistry.addConverter(new StringToTimeZoneConverter()); converterRegistry.addConverter(new ZoneIdToTimeZoneConverter()); converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter()); converterRegistry.addConverter(new ObjectToObjectConverter()); converterRegistry.addConverter(new IdToEntityConverter((ConversionService)converterRegistry)); converterRegistry.addConverter(new FallbackObjectToStringConverter()); converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService)converterRegistry)); }
public static void addCollectionConverters(ConverterRegistry converterRegistry) { ConversionService conversionService = (ConversionService)converterRegistry; converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToArrayConverter(conversionService)); converterRegistry.addConverter(new ArrayToArrayConverter(conversionService)); converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService)); converterRegistry.addConverter(new MapToMapConverter(conversionService)); converterRegistry.addConverter(new ArrayToStringConverter(conversionService)); converterRegistry.addConverter(new StringToArrayConverter(conversionService)); converterRegistry.addConverter(new ArrayToObjectConverter(conversionService)); converterRegistry.addConverter(new ObjectToArrayConverter(conversionService)); converterRegistry.addConverter(new CollectionToStringConverter(conversionService)); converterRegistry.addConverter(new StringToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToObjectConverter(conversionService)); converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService)); converterRegistry.addConverter(new StreamConverter(conversionService)); }
private static void addScalarConverters(ConverterRegistry converterRegistry) { converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); converterRegistry.addConverterFactory(new StringToNumberConverterFactory()); converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCharacterConverter()); converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new NumberToCharacterConverter()); converterRegistry.addConverterFactory(new CharacterToNumberFactory()); converterRegistry.addConverter(new StringToBooleanConverter()); converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverterFactory(new StringToEnumConverterFactory()); converterRegistry.addConverter(new EnumToStringConverter((ConversionService)converterRegistry)); converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory()); converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService)converterRegistry)); converterRegistry.addConverter(new StringToLocaleConverter()); converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCharsetConverter()); converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCurrencyConverter()); converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToPropertiesConverter()); converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToUUIDConverter()); converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToPatternConverter()); converterRegistry.addConverter(Pattern.class, String.class, new ObjectToStringConverter()); if (KotlinDetector.isKotlinPresent()) { converterRegistry.addConverter(new StringToRegexConverter()); converterRegistry.addConverter(Regex.class, String.class, new ObjectToStringConverter()); }
}}
GenericConversionService
为类型转换服务提供了通用的实现基础,定了类型转换的核心机制,实现了 ConfigurableConversionService 接口,主要提供以下功能
- 类型转换:支持任意类型之间的转换(如 String → Integer、List
→ Set 等) - 转换器管理:支持注册、查找和缓存转换器(Converter、ConverterFactory、GenericConverter)
- 性能优化:通过缓存机制减少重复查找转换器的开销
- 条件匹配:支持动态匹配复杂类型(如泛型、集合、Map 等)
各字段说明
- GenericConverter NOOPCONVERTER:无需转换的场景
- GenericConverter NOMATCH:无匹配的转换器
- Converters converters:存储所有注册的转换器(Converter、ConverterFactory、GenericConverter)
- Map<ConverterCacheKey, GenericConverter> converterCache:缓存已解析的转换器
方法名称 | 描述 |
addConverter | 注册一个转换器 |
addConverterFactory | 注册一个转换器工厂 |
convert | 类型转换,将source转换为targetType |
canConvert | 判断是否支持从 sourceType 到 targetType 的转换 |
getConverter | 根据源类型和目标类型查找匹配的转换器 |
removeConvertible | 移除指定源类型和目标类型的转换器 |
package org.springframework.core.convert.support;
import java.util.ArrayList;import java.util.Collections;import java.util.Deque;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Optional;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentLinkedDeque;import java.util.concurrent.CopyOnWriteArraySet;import org.springframework.core.DecoratingProxy;import org.springframework.core.ResolvableType;import org.springframework.core.convert.ConversionFailedException;import org.springframework.core.convert.ConverterNotFoundException;import org.springframework.core.convert.TypeDescriptor;import org.springframework.core.convert.converter.ConditionalConverter;import org.springframework.core.convert.converter.ConditionalGenericConverter;import org.springframework.core.convert.converter.Converter;import org.springframework.core.convert.converter.ConverterFactory;import org.springframework.core.convert.converter.GenericConverter;import org.springframework.lang.Nullable;import org.springframework.util.Assert;import org.springframework.util.ClassUtils;import org.springframework.util.ConcurrentReferenceHashMap;import org.springframework.util.StringUtils;
public class GenericConversionService implements ConfigurableConversionService { private static final GenericConverter NOOPCONVERTER = new NoOpConverter("NOOP"); private static final GenericConverter NOMATCH = new NoOpConverter("NOMATCH"); private final Converters converters = new Converters(); private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap(64);
public GenericConversionService() { }
public void addConverter(Converter<?, ?> converter) { ResolvableType[] typeInfo = this.getRequiredTypeInfo(converter.getClass(), Converter.class); if (typeInfo == null && converter instanceof DecoratingProxy decoratingProxy) { typeInfo = this.getRequiredTypeInfo(decoratingProxy.getDecoratedClass(), Converter.class); }
if (typeInfo == null) { throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?"); } else { this.addConverter((GenericConverter)(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]))); } }
public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) { this.addConverter((GenericConverter)(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)))); }
public void addConverter(GenericConverter converter) { this.converters.add(converter); this.invalidateCache(); }
public void addConverterFactory(ConverterFactory<?, ?> factory) { ResolvableType[] typeInfo = this.getRequiredTypeInfo(factory.getClass(), ConverterFactory.class); if (typeInfo == null && factory instanceof DecoratingProxy decoratingProxy) { typeInfo = this.getRequiredTypeInfo(decoratingProxy.getDecoratedClass(), ConverterFactory.class); }
if (typeInfo == null) { throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your ConverterFactory [" + factory.getClass().getName() + "]; does the class parameterize those types?"); } else { this.addConverter((GenericConverter)(new ConverterFactoryAdapter(factory, new GenericConverter.ConvertiblePair(typeInfo[0].toClass(), typeInfo[1].toClass())))); } }
public void removeConvertible(Class<?> sourceType, Class<?> targetType) { this.converters.remove(sourceType, targetType); this.invalidateCache(); }
public boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); return this.canConvert(sourceType != null ? TypeDescriptor.valueOf(sourceType) : null, TypeDescriptor.valueOf(targetType)); }
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); return sourceType == null || this.getConverter(sourceType, targetType) != null; }
public boolean canBypassConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); return sourceType == null || this.getConverter(sourceType, targetType) == NOOPCONVERTER; }
@Nullable public <T> T convert(@Nullable Object source, Class<T> targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); return this.convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); }
@Nullable public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); if (sourceType == null) { Assert.isTrue(source == null, "Source must be [null] if source type == [null]"); return this.handleResult((TypeDescriptor)null, targetType, this.convertNullSource((TypeDescriptor)null, targetType)); } else if (source != null && !sourceType.getObjectType().isInstance(source)) { String var10002 = String.valueOf(sourceType); throw new IllegalArgumentException("Source to convert from must be an instance of [" + var10002 + "]; instead it was a [" + source.getClass().getName() + "]"); } else { GenericConverter converter = this.getConverter(sourceType, targetType); if (converter != null) { Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType); return this.handleResult(sourceType, targetType, result); } else { return this.handleConverterNotFound(source, sourceType, targetType); } } }
public String toString() { return this.converters.toString(); }
@Nullable protected Object convertNullSource(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { return targetType.getObjectType() == Optional.class ? Optional.empty() : null; }
@Nullable protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType); GenericConverter converter = (GenericConverter)this.converterCache.get(key); if (converter != null) { return converter != NOMATCH ? converter : null; } else { converter = this.converters.find(sourceType, targetType); if (converter == null) { converter = this.getDefaultConverter(sourceType, targetType); }
if (converter != null) { this.converterCache.put(key, converter); return converter; } else { this.converterCache.put(key, NOMATCH); return null; } } }
@Nullable protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { return sourceType.isAssignableTo(targetType) ? NOOPCONVERTER : null; }
@Nullable private ResolvableType[] getRequiredTypeInfo(Class<?> converterClass, Class<?> genericIfc) { ResolvableType resolvableType = ResolvableType.forClass(converterClass).as(genericIfc); ResolvableType[] generics = resolvableType.getGenerics(); if (generics.length < 2) { return null; } else { Class<?> sourceType = generics[0].resolve(); Class<?> targetType = generics[1].resolve(); return sourceType != null && targetType != null ? generics : null; } }
private void invalidateCache() { this.converterCache.clear(); }
@Nullable private Object handleConverterNotFound(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { this.assertNotPrimitiveTargetType(sourceType, targetType); return null; } else if ((sourceType == null || sourceType.isAssignableTo(targetType)) && targetType.getObjectType().isInstance(source)) { return source; } else { throw new ConverterNotFoundException(sourceType, targetType); } }
@Nullable private Object handleResult(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType, @Nullable Object result) { if (result == null) { this.assertNotPrimitiveTargetType(sourceType, targetType); }
return result; }
private void assertNotPrimitiveTargetType(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { if (targetType.isPrimitive()) { throw new ConversionFailedException(sourceType, targetType, (Object)null, new IllegalArgumentException("A null value cannot be assigned to a primitive type")); } }
private static class Converters { private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet(); private final Map<GenericConverter.ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap(256);
private Converters() { }
public void add(GenericConverter converter) { Set<GenericConverter.ConvertiblePair> convertibleTypes = converter.getConvertibleTypes(); if (convertibleTypes == null) { Assert.state(converter instanceof ConditionalConverter, "Only conditional converters may return null convertible types"); this.globalConverters.add(converter); } else { Iterator var3 = convertibleTypes.iterator();
while(var3.hasNext()) { GenericConverter.ConvertiblePair convertiblePair = (GenericConverter.ConvertiblePair)var3.next(); this.getMatchableConverters(convertiblePair).add(converter); } }
}
private ConvertersForPair getMatchableConverters(GenericConverter.ConvertiblePair convertiblePair) { return (ConvertersForPair)this.converters.computeIfAbsent(convertiblePair, (k) -> { return new ConvertersForPair(); }); }
public void remove(Class<?> sourceType, Class<?> targetType) { this.converters.remove(new GenericConverter.ConvertiblePair(sourceType, targetType)); }
@Nullable public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) { List<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType()); List<Class<?>> targetCandidates = this.getClassHierarchy(targetType.getType()); Iterator var5 = sourceCandidates.iterator();
while(var5.hasNext()) { Class<?> sourceCandidate = (Class)var5.next(); Iterator var7 = targetCandidates.iterator();
while(var7.hasNext()) { Class<?> targetCandidate = (Class)var7.next(); GenericConverter.ConvertiblePair convertiblePair = new GenericConverter.ConvertiblePair(sourceCandidate, targetCandidate); GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair); if (converter != null) { return converter; } } }
return null; }
@Nullable private GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType, GenericConverter.ConvertiblePair convertiblePair) { ConvertersForPair convertersForPair = (ConvertersForPair)this.converters.get(convertiblePair); if (convertersForPair != null) { GenericConverter converter = convertersForPair.getConverter(sourceType, targetType); if (converter != null) { return converter; } }
Iterator var7 = this.globalConverters.iterator();
GenericConverter globalConverter; do { if (!var7.hasNext()) { return null; }
globalConverter = (GenericConverter)var7.next(); } while(!((ConditionalConverter)globalConverter).matches(sourceType, targetType));
return globalConverter; }
private List<Class<?>> getClassHierarchy(Class<?> type) { List<Class<?>> hierarchy = new ArrayList(20); Set<Class<?>> visited = new HashSet(20); this.addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited); boolean array = type.isArray();
for(int i = 0; i < hierarchy.size(); ++i) { Class<?> candidate = (Class)hierarchy.get(i); candidate = array ? candidate.componentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate); Class<?> superclass = candidate.getSuperclass(); if (superclass != null && superclass != Object.class && superclass != Enum.class) { this.addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited); }
this.addInterfacesToClassHierarchy(candidate, array, hierarchy, visited); }
if (Enum.class.isAssignableFrom(type)) { this.addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited); this.addInterfacesToClassHierarchy(Enum.class, false, hierarchy, visited); }
this.addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited); this.addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited); return hierarchy; }
private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray, List<Class<?>> hierarchy, Set<Class<?>> visited) { Class[] var5 = type.getInterfaces(); int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) { Class<?> implementedInterface = var5[var7]; this.addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited); }
}
private void addToClassHierarchy(int index, Class<?> type, boolean asArray, List<Class<?>> hierarchy, Set<Class<?>> visited) { if (asArray) { type = type.arrayType(); }
if (visited.add(type)) { hierarchy.add(index, type); }
}
public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ConversionService converters =\n"); Iterator var2 = this.getConverterStrings().iterator();
while(var2.hasNext()) { String converterString = (String)var2.next(); builder.append('\t').append(converterString).append('\n'); }
return builder.toString(); }
private List<String> getConverterStrings() { List<String> converterStrings = new ArrayList(); Iterator var2 = this.converters.values().iterator();
while(var2.hasNext()) { ConvertersForPair convertersForPair = (ConvertersForPair)var2.next(); converterStrings.add(convertersForPair.toString()); }
Collections.sort(converterStrings); return converterStrings; } }
private final class ConverterAdapter implements ConditionalGenericConverter { private final Converter<Object, Object> converter; private final GenericConverter.ConvertiblePair typeInfo; private final ResolvableType targetType;
public ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) { this.converter = converter; this.typeInfo = new GenericConverter.ConvertiblePair(sourceType.toClass(), targetType.toClass()); this.targetType = targetType; }
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() { return Collections.singleton(this.typeInfo); }
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { if (this.typeInfo.getTargetType() != targetType.getObjectType()) { return false; } else { ResolvableType rt = targetType.getResolvableType(); if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) && !this.targetType.hasUnresolvableGenerics()) { return false; } else { Converter var5 = this.converter; boolean var10000; if (var5 instanceof ConditionalConverter) { ConditionalConverter conditionalConverter = (ConditionalConverter)var5; if (!conditionalConverter.matches(sourceType, targetType)) { var10000 = false; return var10000; } }
var10000 = true; return var10000; } } }
@Nullable public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source == null ? GenericConversionService.this.convertNullSource(sourceType, targetType) : this.converter.convert(source); }
public String toString() { String var10000 = String.valueOf(this.typeInfo); return var10000 + " : " + String.valueOf(this.converter); } }
private final class ConverterFactoryAdapter implements ConditionalGenericConverter { private final ConverterFactory<Object, Object> converterFactory; private final GenericConverter.ConvertiblePair typeInfo;
public ConverterFactoryAdapter(ConverterFactory<?, ?> converterFactory, GenericConverter.ConvertiblePair typeInfo) { this.converterFactory = converterFactory; this.typeInfo = typeInfo; }
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() { return Collections.singleton(this.typeInfo); }
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { boolean matches = true; ConverterFactory var5 = this.converterFactory; if (var5 instanceof ConditionalConverter conditionalConverterx) { matches = conditionalConverterx.matches(sourceType, targetType); }
if (matches) { Converter<?, ?> converter = this.converterFactory.getConverter(targetType.getType()); if (converter instanceof ConditionalConverter) { ConditionalConverter conditionalConverter = (ConditionalConverter)converter; matches = conditionalConverter.matches(sourceType, targetType); } }
return matches; }
@Nullable public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source == null ? GenericConversionService.this.convertNullSource(sourceType, targetType) : this.converterFactory.getConverter(targetType.getObjectType()).convert(source); }
public String toString() { String var10000 = String.valueOf(this.typeInfo); return var10000 + " : " + String.valueOf(this.converterFactory); } }
private static final class ConverterCacheKey implements Comparable<ConverterCacheKey> { private final TypeDescriptor sourceType; private final TypeDescriptor targetType;
public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) { this.sourceType = sourceType; this.targetType = targetType; }
public boolean equals(@Nullable Object other) { boolean var10000; if (this != other) { label28: { if (other instanceof ConverterCacheKey) { ConverterCacheKey that = (ConverterCacheKey)other; if (this.sourceType.equals(that.sourceType) && this.targetType.equals(that.targetType)) { break label28; } }
var10000 = false; return var10000; } }
var10000 = true; return var10000; }
public int hashCode() { return this.sourceType.hashCode() * 29 + this.targetType.hashCode(); }
public String toString() { String var10000 = String.valueOf(this.sourceType); return "ConverterCacheKey [sourceType = " + var10000 + ", targetType = " + String.valueOf(this.targetType) + "]"; }
public int compareTo(ConverterCacheKey other) { int result = this.sourceType.getResolvableType().toString().compareTo(other.sourceType.getResolvableType().toString()); if (result == 0) { result = this.targetType.getResolvableType().toString().compareTo(other.targetType.getResolvableType().toString()); }
return result; } }
private static class NoOpConverter implements GenericConverter { private final String name;
public NoOpConverter(String name) { this.name = name; }
@Nullable public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() { return null; }
@Nullable public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source; }
public String toString() { return this.name; } }
private static class ConvertersForPair { private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque();
private ConvertersForPair() { }
public void add(GenericConverter converter) { this.converters.addFirst(converter); }
@Nullable public GenericConverter getConverter(TypeDescriptor var1, TypeDescriptor var2) { // $FF: Couldn't be decompiled }
public String toString() { return StringUtils.collectionToCommaDelimitedString(this.converters); } }}
ConfigurableConversionService
一个组合接口,继承并整合了以下两个关键接口的
- ConversionService:提供类型转换的核心方法(如 convert、canConvert),用于执行具体的类型转换逻辑
- ConverterRegistry:提供注册和管理转换器的方法(如 addConverter、removeConvertible),用于动态扩展类型转换能力
package org.springframework.core.convert.support;
import org.springframework.core.convert.ConversionService;import org.springframework.core.convert.converter.ConverterRegistry;
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {}
ConversionService
Spring 类型转换机制的核心接口,提供了灵活的类型转换能力。
- canConvert:判断是否可以将 sourceType 类型转换为 targetType 类型
- convert:将 sourceType 类型对象转换为 targetType 类型对象
package org.springframework.core.convert;
import org.springframework.lang.Nullable;
public interface ConversionService { boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
@Nullable <T> T convert(@Nullable Object source, Class<T> targetType);
@Nullable default Object convert(@Nullable Object source, TypeDescriptor targetType) { return this.convert(source, TypeDescriptor.forObject(source), targetType); }
@Nullable Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);}
ConverterRegistry
用于管理类型转换器的接口,提供了多种方法来注册和移除转换器。
- addConverter:添加一个转换器,用于从源类型到目标类型的转换
- addConverterFactory:工厂类,创建特定类型的转换器
- removeConvertible:移除注册表中与指定源类型和目标类型匹配的转换器
package org.springframework.core.convert.converter;
public interface ConverterRegistry { void addConverter(Converter<?, ?> converter);
<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
void addConverter(GenericConverter converter);
void addConverterFactory(ConverterFactory<?, ?> factory);
void removeConvertible(Class<?> sourceType, Class<?> targetType);}
MapOutputConverter
Map 转换器,实现从非结构化文本到结构化键值对数据的高效转换
- 继承自 AbstractMessageOutputConverter 类,MappingJackson2MessageConverter 实现复杂的类型转换
方法名称 | 描述 |
getFormat | 输送给大模型的Map |
convert | 将模型输出的实例字符串转Map |
package org.springframework.ai.converter;
import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;
import org.springframework.lang.NonNull;import org.springframework.messaging.Message;import org.springframework.messaging.converter.MappingJackson2MessageConverter;import org.springframework.messaging.support.MessageBuilder;
public class MapOutputConverter extends AbstractMessageOutputConverter<Map<String, Object>> { public MapOutputConverter() { super(new MappingJackson2MessageConverter()); }
public Map<String, Object> convert(@NonNull String text) { if (text.startsWith("```json") && text.endsWith("```")) { text = text.substring(7, text.length() - 3); } // 将字符串text转换为UTF-8编码的字节数组,确保文本数据能以字节刘的形式传输 // 构建Message对象,并将字节数组作为消息的负载 Message<?> message = MessageBuilder.withPayload(text.getBytes(StandardCharsets.UTF8)).build(); return (Map)this.getMessageConverter().fromMessage(message, HashMap.class); }
public String getFormat() { String raw = "Your response should be in JSON format.\nThe data structure for the JSON should match this Java class: %s\nDo not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.\nRemove the ```json markdown surrounding the output including the trailing \"```\".\n"; return String.format(raw, HashMap.class.getName()); }}
AbstractMessageOutputConverter
使用预配置的 MessageConverter 来将 AI 模型的输出转换为目标类型格式
package org.springframework.ai.converter;
import org.springframework.messaging.converter.MessageConverter;
public abstract class AbstractMessageOutputConverter<T> implements StructuredOutputConverter<T> { private MessageConverter messageConverter;
public AbstractMessageOutputConverter(MessageConverter messageConverter) { this.messageConverter = messageConverter; }
public MessageConverter getMessageConverter() { return this.messageConverter; }}
MessageConverter
消息转换接口,用于将消息内容(payload)与目标类型之间进行转换
方法名称 | 描述 |
fromMessage | 将Message对象转换为目标类型的对象 |
toMessage | 将给定对象转换为Message对象 |
package org.springframework.messaging.converter;
import org.springframework.lang.Nullable;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;
public interface MessageConverter { @Nullable Object fromMessage(Message<?> message, Class<?> targetClass);
@Nullable Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);}
SmartMessageConverter
聪明的消息转换接口,用于将消息内容(payload)与目标类型之间进行转换
方法名称 | 描述 |
fromMessage | 将Message对象转换为目标类型的对象 |
toMessage | 将给定对象转换为Message对象 |
package org.springframework.messaging.converter;
import org.springframework.lang.Nullable;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;
public interface SmartMessageConverter extends MessageConverter { @Nullable Object fromMessage(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint);
@Nullable Message<?> toMessage(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint);}
MappingJackson2MessageConverter
基于 Jackson 库实现了消息的序列化和反序列化,主要用于将 Java 对象转换为 JSON 格式的字节流(序列化),或将 JSON 字节流还原为 Java 对象(反序列化)。这类操作通常用于在系统之间传递数据,例如网络通信、消息队列等场景
MimeType[] DEFAULTMIMETYPES
:定义默认支持的 MIME 类型,如 application/json 和 application/*+jsonObjectMapper objectMapper
:Jackson 库的核心类,负责实际的 JSON 转换工作Boolean prettyPrint
:控制是否以美化格式输出 JSON 数据(即带有缩进和换行)
方法名称 | 描述 |
canConvertFrom | 判断是否可以从消息中反序列化出目标类型的对象 |
canConvertTo | 判断是否可以将给定的对象序列化为 JSON 消息 |
convertFromInternal | 将消息内容(payload)反序列化为目标 Java 对象 |
convertToInternal | 将 Java 对象序列化为 JSON 字符串或字节数组 |
logWarningIfNecessary | 在反序列化失败时记录警告日志 |
getJsonEncoding | 根据 MIME 类型获取对应的 JSON 编码方式(如 UTF-8) |
getSerializationView | 从 conversionHint 中提取用于序列化/反序列化的视图类 |
extractViewClass | 从 @JsonView 注解中提取具体的视图类 |
package org.springframework.messaging.converter;
import com.fasterxml.jackson.annotation.JsonView;import com.fasterxml.jackson.core.JsonEncoding;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.JavaType;import com.fasterxml.jackson.databind.JsonMappingException;import com.fasterxml.jackson.databind.MapperFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.StringWriter;import java.io.Writer;import java.lang.reflect.Type;import java.nio.charset.Charset;import java.util.concurrent.atomic.AtomicReference;import org.springframework.core.MethodParameter;import org.springframework.lang.Nullable;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;import org.springframework.util.Assert;import org.springframework.util.ClassUtils;import org.springframework.util.MimeType;
public class MappingJackson2MessageConverter extends AbstractMessageConverter { private static final MimeType[] DEFAULTMIMETYPES = new MimeType[]{new MimeType("application", "json"), new MimeType("application", "*+json")}; private ObjectMapper objectMapper; @Nullable private Boolean prettyPrint;
public MappingJackson2MessageConverter() { this(DEFAULTMIMETYPES); }
public MappingJackson2MessageConverter(MimeType... supportedMimeTypes) { super(supportedMimeTypes); this.objectMapper = new ObjectMapper(); this.objectMapper.configure(MapperFeature.DEFAULTVIEWINCLUSION, false); this.objectMapper.configure(DeserializationFeature.FAILONUNKNOWNPROPERTIES, false); }
public MappingJackson2MessageConverter(ObjectMapper objectMapper) { this(objectMapper, DEFAULTMIMETYPES); }
public MappingJackson2MessageConverter(ObjectMapper objectMapper, MimeType... supportedMimeTypes) { super(supportedMimeTypes); Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.objectMapper = objectMapper; }
public void setObjectMapper(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.objectMapper = objectMapper; this.configurePrettyPrint(); }
public ObjectMapper getObjectMapper() { return this.objectMapper; }
public void setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; this.configurePrettyPrint(); }
private void configurePrettyPrint() { if (this.prettyPrint != null) { this.objectMapper.configure(SerializationFeature.INDENTOUTPUT, this.prettyPrint); }
}
protected boolean canConvertFrom(Message<?> message, @Nullable Class<?> targetClass) { if (targetClass != null && this.supportsMimeType(message.getHeaders())) { JavaType javaType = this.objectMapper.constructType(targetClass); AtomicReference<Throwable> causeRef = new AtomicReference(); if (this.objectMapper.canDeserialize(javaType, causeRef)) { return true; } else { this.logWarningIfNecessary(javaType, (Throwable)causeRef.get()); return false; } } else { return false; } }
protected boolean canConvertTo(Object payload, @Nullable MessageHeaders headers) { if (!this.supportsMimeType(headers)) { return false; } else { AtomicReference<Throwable> causeRef = new AtomicReference(); if (this.objectMapper.canSerialize(payload.getClass(), causeRef)) { return true; } else { this.logWarningIfNecessary(payload.getClass(), (Throwable)causeRef.get()); return false; } } }
protected void logWarningIfNecessary(Type type, @Nullable Throwable cause) { if (cause != null) { boolean debugLevel = cause instanceof JsonMappingException && cause.getMessage() != null && cause.getMessage().startsWith("Cannot find"); if (debugLevel) { if (!this.logger.isDebugEnabled()) { return; } } else if (!this.logger.isWarnEnabled()) { return; }
String msg = "Failed to evaluate Jackson " + (type instanceof JavaType ? "de" : "") + "serialization for type [" + String.valueOf(type) + "]"; if (debugLevel) { this.logger.debug(msg, cause); } else if (this.logger.isDebugEnabled()) { this.logger.warn(msg, cause); } else { this.logger.warn(msg + ": " + String.valueOf(cause)); }
} }
protected boolean supports(Class<?> clazz) { throw new UnsupportedOperationException(); }
@Nullable protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) { JavaType javaType = this.objectMapper.constructType(getResolvedType(targetClass, conversionHint)); Object payload = message.getPayload(); Class<?> view = this.getSerializationView(conversionHint);
try { if (ClassUtils.isAssignableValue(targetClass, payload)) { return payload; } else if (payload instanceof byte[]) { byte[] bytes = (byte[])payload; return view != null ? this.objectMapper.readerWithView(view).forType(javaType).readValue(bytes) : this.objectMapper.readValue(bytes, javaType); } else { return view != null ? this.objectMapper.readerWithView(view).forType(javaType).readValue(payload.toString()) : this.objectMapper.readValue(payload.toString(), javaType); } } catch (IOException var8) { throw new MessageConversionException(message, "Could not read JSON: " + var8.getMessage(), var8); } }
@Nullable protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { try { Class<?> view = this.getSerializationView(conversionHint); if (byte[].class == this.getSerializedPayloadClass()) { ByteArrayOutputStream out = new ByteArrayOutputStream(1024); JsonEncoding encoding = this.getJsonEncoding(this.getMimeType(headers)); JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding);
try { if (view != null) { this.objectMapper.writerWithView(view).writeValue(generator, payload); } else { this.objectMapper.writeValue(generator, payload); }
payload = out.toByteArray(); } catch (Throwable var11) { if (generator != null) { try { generator.close(); } catch (Throwable var10) { var11.addSuppressed(var10); } }
throw var11; }
if (generator != null) { generator.close(); } } else { Writer writer = new StringWriter(1024); if (view != null) { this.objectMapper.writerWithView(view).writeValue(writer, payload); } else { this.objectMapper.writeValue(writer, payload); }
payload = writer.toString(); }
return payload; } catch (IOException var12) { throw new MessageConversionException("Could not write JSON: " + var12.getMessage(), var12); } }
@Nullable protected Class<?> getSerializationView(@Nullable Object conversionHint) { if (conversionHint instanceof MethodParameter param) { JsonView annotation = param.getParameterIndex() >= 0 ? (JsonView)param.getParameterAnnotation(JsonView.class) : (JsonView)param.getMethodAnnotation(JsonView.class); if (annotation != null) { return this.extractViewClass(annotation, conversionHint); } } else { if (conversionHint instanceof JsonView jsonView) { return this.extractViewClass(jsonView, conversionHint); }
if (conversionHint instanceof Class<?> clazz) { return clazz; } }
return null; }
private Class<?> extractViewClass(JsonView annotation, Object conversionHint) { Class<?>[] classes = annotation.value(); if (classes.length != 1) { throw new IllegalArgumentException("@JsonView only supported for handler methods with exactly 1 class argument: " + String.valueOf(conversionHint)); } else { return classes[0]; } }
protected JsonEncoding getJsonEncoding(@Nullable MimeType contentType) { if (contentType != null && contentType.getCharset() != null) { Charset charset = contentType.getCharset(); JsonEncoding[] var3 = JsonEncoding.values(); int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) { JsonEncoding encoding = var3[var5]; if (charset.name().equals(encoding.getJavaName())) { return encoding; } } }
return JsonEncoding.UTF8; }}
AbstractMessageConverter
Spring 框架中消息转换的抽象基类,主要用于消息的序列化和反序列化。具体的转换逻辑由子类实现
List<MimeType> supportedMimeTypes
:当前转换器支持的 MIME 类型列表,如 application/jsonContentTypeResolver contentTypeResolver
:内容类型解析器,默认使用 DefaultContentTypeResolverboolean strictContentTypeMatch
:是否严格匹配 MIME 类型(精确匹配 subtype)Class<?> serializedPayloadClass
:序列化后的消息体类型,只能是 byte[] 或 String
方法名称 | 描述 |
fromMessage | 将消息体反序列化为目标类的对象 |
toMessage | 将 Java 对象序列化为消息对象 |
canConvertFrom | 判断当前消息是否可以被反序列化为目标类型 |
canConvertTo | 判断当前对象是否可以被序列化为消息 |
supportsMimeType | 判断消息头中的 MIME 类型是否在支持范围内 |
getMimeType | 使用配置的 contentTypeResolver 解析消息头中的 MIME 类型 |
getDefaultContentType | 获取默认的内容类型(即 supportedMimeTypes 中的第一个) |
supports | 子类需实现:判断是否支持给定类型的转换 |
convertFromInternal | 子类需实现:从消息体中反序列化出目标对象 |
convertToInternal | 子类需实现:将对象序列化为消息体 |
getResolvedType | 解析泛型类型信息 |
package org.springframework.messaging.converter;
import java.lang.reflect.Type;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.List;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.core.GenericTypeResolver;import org.springframework.core.MethodParameter;import org.springframework.lang.Nullable;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHeaders;import org.springframework.messaging.support.MessageBuilder;import org.springframework.messaging.support.MessageHeaderAccessor;import org.springframework.util.Assert;import org.springframework.util.MimeType;
public abstract class AbstractMessageConverter implements SmartMessageConverter { protected final Log logger; private final List<MimeType> supportedMimeTypes; @Nullable private ContentTypeResolver contentTypeResolver; private boolean strictContentTypeMatch; private Class<?> serializedPayloadClass;
protected AbstractMessageConverter(MimeType supportedMimeType) { this((Collection)Collections.singletonList(supportedMimeType)); }
protected AbstractMessageConverter(MimeType... supportedMimeTypes) { this((Collection)Arrays.asList(supportedMimeTypes)); }
protected AbstractMessageConverter(Collection<MimeType> supportedMimeTypes) { this.logger = LogFactory.getLog(this.getClass()); this.supportedMimeTypes = new ArrayList(4); this.contentTypeResolver = new DefaultContentTypeResolver(); this.strictContentTypeMatch = false; this.serializedPayloadClass = byte[].class; this.supportedMimeTypes.addAll(supportedMimeTypes); }
public List<MimeType> getSupportedMimeTypes() { return Collections.unmodifiableList(this.supportedMimeTypes); }
protected void addSupportedMimeTypes(MimeType... supportedMimeTypes) { this.supportedMimeTypes.addAll(Arrays.asList(supportedMimeTypes)); }
public void setContentTypeResolver(@Nullable ContentTypeResolver resolver) { this.contentTypeResolver = resolver; }
@Nullable public ContentTypeResolver getContentTypeResolver() { return this.contentTypeResolver; }
public void setStrictContentTypeMatch(boolean strictContentTypeMatch) { if (strictContentTypeMatch) { Assert.notEmpty(this.getSupportedMimeTypes(), "Strict match requires non-empty list of supported mime types"); Assert.notNull(this.getContentTypeResolver(), "Strict match requires ContentTypeResolver"); }
this.strictContentTypeMatch = strictContentTypeMatch; }
public boolean isStrictContentTypeMatch() { return this.strictContentTypeMatch; }
public void setSerializedPayloadClass(Class<?> payloadClass) { Assert.isTrue(byte[].class == payloadClass || String.class == payloadClass, () -> { return "Payload class must be byte[] or String: " + String.valueOf(payloadClass); }); this.serializedPayloadClass = payloadClass; }
public Class<?> getSerializedPayloadClass() { return this.serializedPayloadClass; }
@Nullable public final Object fromMessage(Message<?> message, Class<?> targetClass) { return this.fromMessage(message, targetClass, (Object)null); }
@Nullable public final Object fromMessage(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) { return !this.canConvertFrom(message, targetClass) ? null : this.convertFromInternal(message, targetClass, conversionHint); }
@Nullable public final Message<?> toMessage(Object payload, @Nullable MessageHeaders headers) { return this.toMessage(payload, headers, (Object)null); }
@Nullable public final Message<?> toMessage(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { if (!this.canConvertTo(payload, headers)) { return null; } else { Object payloadToUse = this.convertToInternal(payload, headers, conversionHint); if (payloadToUse == null) { return null; } else { MimeType mimeType = this.getDefaultContentType(payloadToUse); if (headers != null) { MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class); if (accessor != null && accessor.isMutable()) { if (mimeType != null) { accessor.setHeaderIfAbsent("contentType", mimeType); }
return MessageBuilder.createMessage(payloadToUse, accessor.getMessageHeaders()); } }
MessageBuilder<?> builder = MessageBuilder.withPayload(payloadToUse); if (headers != null) { builder.copyHeaders(headers); }
if (mimeType != null) { builder.setHeaderIfAbsent("contentType", mimeType); }
return builder.build(); } } }
protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) { return this.supports(targetClass) && this.supportsMimeType(message.getHeaders()); }
protected boolean canConvertTo(Object payload, @Nullable MessageHeaders headers) { return this.supports(payload.getClass()) && this.supportsMimeType(headers); }
protected boolean supportsMimeType(@Nullable MessageHeaders headers) { if (this.getSupportedMimeTypes().isEmpty()) { return true; } else { MimeType mimeType = this.getMimeType(headers); if (mimeType == null) { return !this.isStrictContentTypeMatch(); } else { Iterator var3 = this.getSupportedMimeTypes().iterator();
MimeType current; do { if (!var3.hasNext()) { return false; }
current = (MimeType)var3.next(); } while(!current.getType().equals(mimeType.getType()) || !current.getSubtype().equals(mimeType.getSubtype()));
return true; } } }
@Nullable protected MimeType getMimeType(@Nullable MessageHeaders headers) { return this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null; }
@Nullable protected MimeType getDefaultContentType(Object payload) { List<MimeType> mimeTypes = this.getSupportedMimeTypes(); return !mimeTypes.isEmpty() ? (MimeType)mimeTypes.get(0) : null; }
protected abstract boolean supports(Class<?> clazz);
@Nullable protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) { return null; }
@Nullable protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { return null; }
static Type getResolvedType(Class<?> targetClass, @Nullable Object conversionHint) { if (conversionHint instanceof MethodParameter param) { param = param.nestedIfOptional(); if (Message.class.isAssignableFrom(param.getParameterType())) { param = param.nested(); }
Type genericParameterType = param.getNestedGenericParameterType(); Class<?> contextClass = param.getContainingClass(); return GenericTypeResolver.resolveType(genericParameterType, contextClass); } else { return targetClass; } }}