Jackson 2.x 系列【24】Spring Web 集成

发布于:2024-04-19 ⋅ 阅读:(58) ⋅ 点赞:(0)

有道无术,术尚可求,有术无道,止于术。

本系列Jackson 版本 2.17.0

源码地址:https://gitee.com/pearl-organization/study-jaskson-demo

1. 前言

Spring生态使用Jackson作为默认的JSON处理框架,这些年随着Spring的大发异彩和Jackson本身的优越特性,已成为世界上最流行的JSON库。

接下来,本系列会讲解Spring MVCSpring Boot中如何使用Jackson,以及Spring对其进行扩展增强的相关源码,所以需要读者有一定的SpringSpring Boot开发经验。

2. Spring Web

Spring MVC对于后端开发人员来说已经很熟悉了,它是一个构建在Servlet之上的一个Web框架,而Spring WebSpring MVC的基础模块(还有一个Spring Flux ),它们同属于spring-framework框架。

Web框架在处理HTTP请求响应时,需要将请求报文反序列化为Java对象进行接收处理,在响应阶段,需要将Java对象反序列化为报文写出,所以需要依赖JSON框架去处理。

Spring Web中可以看到引入了JacksonGJson

在这里插入图片描述
spring-web模块的org.springframework.http.converter.json包下,可以看到Json集成的代码:

在这里插入图片描述

集成Jackson的主要有:

  • Jackson2ObjectMapperBuilderObjectMapper构建器
  • Jackson2ObjectMapperFactoryBeanFactoryBean创建和管理ObjectMapper对象
  • MappingJackson2HttpMessageConverter:基于Jackson的消息转换器
  • SpringHandlerInstantiatorSpring容器创建Jackson组件,例如JsonSerializerJsonDeserializerKeyDeserializer
  • JacksonModulesRuntimeHintsSpring AOT所需的RuntimeHints(运行时提示)

3. Jackson2ObjectMapperBuilder

Jackson2ObjectMapperBuilder从名字上看已经很好理解,它是一个JacksonObjectMapper构建器,提供了Fluent API来定制ObjectMapper的默认属性并构建实例。

在之前我们都是通过new的方式创建ObjectMapper实例,现在可以使用构建者模式,这也是Spring自身使用和推荐使用的方式。

它的构造方法是public的,说明可以直接new创建Jackson2ObjectMapperBuilder

    public Jackson2ObjectMapperBuilder() {
    }

提供了多个创建不同数据类型支持的静态方法:

	// 构建 Jackson2ObjectMapperBuilder
	public static Jackson2ObjectMapperBuilder json() {
		return new Jackson2ObjectMapperBuilder();
	}

	// 构建 Jackson2ObjectMapperBuilder,并设置需要创建 XmlMapper,需要引入`jackson-dataformat-xml`模块,用于支持`XML`数据格式
	public static Jackson2ObjectMapperBuilder xml() {
		return new Jackson2ObjectMapperBuilder().createXmlMapper(true);
	}

	// 指定 JsonFactory-》SmileFactory,需要引入`jackson-dataformat-smile`模块,用于支持`Smile`数据格式
	public static Jackson2ObjectMapperBuilder smile() {
		return new Jackson2ObjectMapperBuilder().factory(new SmileFactoryInitializer().create());
	}
	
	// 指定 JsonFactory-》CBORFactory-》需要引入`jackson-dataformat-cbor`模块,用于支持`CBOR`数据格式
	public static Jackson2ObjectMapperBuilder cbor() {
		return new Jackson2ObjectMapperBuilder().factory(new CborFactoryInitializer().create());
	}

一般场景中,使用json()创建即可:

ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();

此外还提供了很多方法,例如自定义序列化/反序列化器、模块注册、启用/禁用特征、 混合注解等等,和ObjectMapper 本身的方法大多类似,这里就不一一赘述了。

	// 配置自定义序列化器(多个)
	public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) {
		for (JsonSerializer<?> serializer : serializers) {
			Class<?> handledType = serializer.handledType();
			if (handledType == null || handledType == Object.class) {
				throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName());
			}
			this.serializers.put(serializer.handledType(), serializer);
		}
		return this;
	}
	// 模块注册
	public Jackson2ObjectMapperBuilder modules(Module... modules) {
		return modules(Arrays.asList(modules));
	}
	/**
	 * 启用特征
	 *
	 * @see com.fasterxml.jackson.core.JsonParser.Feature
	 * @see com.fasterxml.jackson.core.JsonGenerator.Feature
	 * @see com.fasterxml.jackson.databind.SerializationFeature
	 * @see com.fasterxml.jackson.databind.DeserializationFeature
	 * @see com.fasterxml.jackson.databind.MapperFeature
	 */
	public Jackson2ObjectMapperBuilder featuresToEnable(Object... featuresToEnable) {
		for (Object feature : featuresToEnable) {
			this.features.put(feature, Boolean.TRUE);
		}
		return this;
	}

需要重点关注的方法有buildconfigurebuild方法用于构建一个ObjectMapper实例,每次调用都会返回一个新的对象,对于处理不同JSON格式或需要不同序列化/反序列化行为的场景,可以构建新的实例来处理。

	@SuppressWarnings("unchecked")
	public <T extends ObjectMapper> T build() {
		// 创建实例
		ObjectMapper mapper;
		if (this.createXmlMapper) {
			// 需要创建XmlMapper,则使用 XmlFactory
			mapper = (this.defaultUseWrapper != null ?
					new XmlObjectMapperInitializer().create(this.defaultUseWrapper, this.factory) :
					new XmlObjectMapperInitializer().create(this.factory));
		} else {
			// 不需要创建XmlMapper,则使用指定的工厂
			mapper = (this.factory != null ? new ObjectMapper(this.factory) : new ObjectMapper());
		}
		// 配置
		configure(mapper);
		return (T) mapper;
	}

configure方法用于将成员属性都设置给已存在的ObjectMapper实例:

	public void configure(ObjectMapper objectMapper) {
		Assert.notNull(objectMapper, "ObjectMapper must not be null");
		// 注册模块
		MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();
		if (this.findModulesViaServiceLoader) {
			ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister));
		} else if (this.findWellKnownModules : modulesToRegister.values()) {
			modules.addAll(nestedModules);
		}
		objectMapper.registerModules(modules);
		// 设置时间日期格式化
		if (this.dateFormat != null) {
			objectMapper.setDateFormat(this.dateFormat);
		}
		// 设置 Locale
		if (this.locale != null) {
			objectMapper.setLocale(this.locale);
		}
		// 设置 TimeZone	
		if (this.timeZone != null) {
			objectMapper.setTimeZone(this.timeZone);
		}
		// 设置 注解解析器
		if (this.annotationIntrospector != null) {
			objectMapper.setAnnotationIntrospector(this.annotationIntrospector);
		}
		// 设置属性名称策略
		if (this.propertyNamingStrategy != null) {
			objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
		}
		// 省略其他.........
		if (this.configurer != null) {
			this.configurer.accept(objectMapper);
		}
	}

4. Jackson2ObjectMapperFactoryBean

Jackson2ObjectMapperFactoryBean提供了基于Spring框架FactoryBean机制创建和配置ObjectMapper实例。

示例如下:

@Configuration
public class MvcConfig {

    @Bean
    public Jackson2ObjectMapperFactoryBean jacksonObjectMapper() {
        Jackson2ObjectMapperFactoryBean factoryBean = new Jackson2ObjectMapperFactoryBean();
        factoryBean.setIndentOutput(true);
        factoryBean.setSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 还可以设置其他属性,如自定义的序列化器、反序列化器等
        return factoryBean;
    }
}

5. MappingJackson2HttpMessageConverter

MappingJackson2HttpMessageConverter即基于Jackson2的消息转换器实现,其本身并没有多少代码,读写能力来自于父类。

继承关系如下:

  • HttpMessageConverter
    • GenericHttpMessageConverter
      • AbstractGenericHttpMessageConverter
        • AbstractJackson2HttpMessageConverter
          • MappingJackson2HttpMessageConverter

顶级接口HttpMessageConverterSpring Web定义的一个HTTP消息转换器。负责将请求和响应的数据从Java对象转换为HTTP协议所需的格式,或者将HTTP协议中的数据转换为Java对象,是Spring框架中用于处理HTTP请求和响应数据转换的重要组件。

直接父类AbstractJackson2HttpMessageConverter定义了泛型为Object,其内部维护了多个ObjectMapper对象:

public abstract class AbstractJackson2HttpMessageConverter extends 
										AbstractGenericHttpMessageConverter<Object> 
	// 默认的ObjectMapper
    protected ObjectMapper defaultObjectMapper;
    @Nullable
    // registerObjectMappersForType 方法注册的多个ObjectMapper
    private Map<Class<?>, Map<MediaType, ObjectMapper>> objectMapperRegistrations;

可以调用构造方法或者set方法设置默认的ObjectMapper

    protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        this.defaultObjectMapper = objectMapper;
        DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
        prettyPrinter.indentObjectsWith(new DefaultIndenter("  ", "\ndata:"));
        this.ssePrettyPrinter = prettyPrinter;
    }

    protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {
        this(objectMapper);
        this.setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
    }

    protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
        this(objectMapper);
        this.setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
    }
    
    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "ObjectMapper must not be null");
        this.defaultObjectMapper = objectMapper;
        this.configurePrettyPrint();
    }

重写了父类的canReadcanWrite方法,例如canRead,首先调用父类的 canRead 方法,是否支持当前 MediaType,然后查询并判断ObjectMapper是否支持反序列化当前类型:

    public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
    	// 调用父类的 canRead 方法,是否支持当前 MediaType
        if (!this.canRead(mediaType)) {
            return false;
        } else {
        	// 根据JAVA Type 类型,转换为 Jackson 的 JavaType 
            JavaType javaType = this.getJavaType(type, contextClass);
            // 查询一个可用的 ObjectMapper 
            ObjectMapper objectMapper = this.selectObjectMapper(javaType.getRawClass(), mediaType);
            if (objectMapper == null) {
                return false;
            } else {
            	// ObjectMapper  是否可反序列化当前类型 
                AtomicReference<Throwable> causeRef = new AtomicReference();
                if (objectMapper.canDeserialize(javaType, causeRef)) {
                    return true;
                } else {
                    this.logWarningIfNecessary(javaType, (Throwable)causeRef.get());
                    return false;
                }
            }
        }
    }

最重要的是真正实现了转换器最核心的读写方法,例如read方法中并不是直接通过ObjectMapper进行读操作,而是通过ObjectMapper创建了底层的ObjectReader 去执行读操作。

	// 将 HTTP 输入消息,转换为 JAVA 对象
    public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        JavaType javaType = this.getJavaType(type, contextClass);
        return this.readJavaType(javaType, inputMessage);
    }
	
    private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
    	// 获取 Content-Type
        MediaType contentType = inputMessage.getHeaders().getContentType();
        // 获取 Charset 
        Charset charset = this.getCharset(contentType);
        // 查询可用的 ObjectMapper 
        ObjectMapper objectMapper = this.selectObjectMapper(javaType.getRawClass(), contentType);
        Assert.state(objectMapper != null, () -> {
            return "No ObjectMapper for " + javaType;
        });
        boolean isUnicode = ENCODINGS.containsKey(charset.name()) || "UTF-16".equals(charset.name()) || "UTF-32".equals(charset.name());

        try {
        	// 获取输入流 
            InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
            if (inputMessage instanceof MappingJacksonInputMessage) {
            	// 支持 Jackson 视图 
                MappingJacksonInputMessage mappingJacksonInputMessage = (MappingJacksonInputMessage)inputMessage;
                Class<?> deserializationView = mappingJacksonInputMessage.getDeserializationView();
                if (deserializationView != null) {
                    ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);
                    objectReader = this.customizeReader(objectReader, javaType);
                    if (isUnicode) {
                        return objectReader.readValue(inputStream);
                    }

                    Reader reader = new InputStreamReader(inputStream, charset);
                    return objectReader.readValue(reader);
                }
            }
			// 使用 `ObjectMapper` 底层的`ObjectReader`读取
            ObjectReader objectReader = objectMapper.reader().forType(javaType);
            objectReader = this.customizeReader(objectReader, javaType);
            if (isUnicode) {
                return objectReader.readValue(inputStream);
            } else {
                Reader reader = new InputStreamReader(inputStream, charset);
                return objectReader.readValue(reader);
            }
        } catch (InvalidDefinitionException var12) {
            throw new HttpMessageConversionException("Type definition error: " + var12.getType(), var12);
        } catch (JsonProcessingException var13) {
            throw new HttpMessageNotReadableException("JSON parse error: " + var13.getOriginalMessage(), var13, inputMessage);
        }
    }