spring boot 是如何利用jackson进行反序列化的?
以下面的代码为例:
- @RestController
- public class HelloController {
- @RequestMapping("/")
- public BillSearch hello(@RequestBody BillSearch search) {
- return search;
- }
- }
前端通过Postman进行模拟:
下面开始一步步的揭开它的面纱:
先从HandlerMethodArgumentResolverComposite开始:
- public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
- HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
- if (resolver == null) {
- throw new IllegalArgumentException(
- "Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
- " supportsParameter should be called first.");
- }
- return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
- }
resolver为RequestResponseBodyMethodProcessor 这个类是序列化和反序列化常用到的类。下面是它的resolveArgument方法:
- public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
- parameter = parameter.nestedIfOptional();
- Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
- String name = Conventions.getVariableNameForParameter(parameter);
- if (binderFactory != null) {
- WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
- if (arg != null) {
- validateIfApplicable(binder, parameter);
- if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
- throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
- }
- }
- if (mavContainer != null) {
- mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
- }
- }
- return adaptArgumentIfNecessary(arg, parameter);
- }
- readWithMessageConverters方法如下:
- protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
- Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
- HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
- Assert.state(servletRequest != null, "No HttpServletRequest");
- ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
- Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
- if (arg == null && checkRequired(parameter)) {
- throw new HttpMessageNotReadableException("Required request body is missing: " +
- parameter.getExecutable().toGenericString(), inputMessage);
- }
- return arg;
- }
- protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
- Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
- MediaType contentType;
- boolean noContentType = false;
- try {
- contentType = inputMessage.getHeaders().getContentType();
- }
- catch (InvalidMediaTypeException ex) {
- throw new HttpMediaTypeNotSupportedException(ex.getMessage());
- }
- if (contentType == null) {
- noContentType = true;
- contentType = MediaType.APPLICATION_OCTET_STREAM;
- }
- Class<?> contextClass = parameter.getContainingClass();
- Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
- if (targetClass == null) {
- ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
- targetClass = (Class<T>) resolvableType.resolve();
- }
- HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
- Object body = NO_VALUE;
- EmptyBodyCheckingHttpInputMessage message;
- try {
- message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
- for (HttpMessageConverter<?> converter : this.messageConverters) {
- Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
- GenericHttpMessageConverter<?> genericConverter =
- (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
- if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
- (targetClass != null && converter.canRead(targetClass, contentType))) {
- if (message.hasBody()) {
- HttpInputMessage msgToUse =
- getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
- body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
- ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
- body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
- }
- else {
- body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
- }
- break;
- }
- }
- }
- catch (IOException ex) {
- throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
- }
- if (body == NO_VALUE) {
- if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
- (noContentType && !message.hasBody())) {
- return null;
- }
- throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
- }
- MediaType selectedContentType = contentType;
- Object theBody = body;
- LogFormatUtils.traceDebug(logger, traceOn -> {
- String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
- return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
- });
- return body;
- }
- 上一篇博客里介绍了messageConverters,在项目启动时添加了MappingJackson2HttpMessageConverter,这里主是就是找到这个converter对参数进行解析:
再进一步追踪:在AbstractJackson2HttpMessageConverter类中,就找到了我们要找到objectMapper:
- private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
- try {
- if (inputMessage instanceof MappingJacksonInputMessage) {
- Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
- if (deserializationView != null) {
- return this.objectMapper.readerWithView(deserializationView).forType(javaType).
- readValue(inputMessage.getBody());
- }
- }
- return this.objectMapper.readValue(inputMessage.getBody(), javaType);
- }
- catch (InvalidDefinitionException ex) {
- throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
- }
- catch (JsonProcessingException ex) {
- throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
- }
- }
顺便再介绍一下objectMapper反序列化的主要步骤:
- protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
- throws IOException
- {
- try (JsonParser p = p0) {
- Object result;
- JsonToken t = _initForReading(p, valueType);
- final DeserializationConfig cfg = getDeserializationConfig();
- final DeserializationContext ctxt = createDeserializationContext(p, cfg);
- if (t == JsonToken.VALUE_NULL) {
- // Ask JsonDeserializer what 'null value' to use:
- result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
- } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
- result = null;
- } else {
//com.fasterxml.jackson.databind.deser.BeanDeserializer- JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
- if (cfg.useRootWrapping()) {
- result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
- } else {
- result = deser.deserialize(p, ctxt);
- }
- ctxt.checkUnresolvedObjectId();
- }
- if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
- _verifyNoTrailingTokens(p, ctxt, valueType);
- }
- return result;
- }
- }
- public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
- {
- // common case first
- if (p.isExpectedStartObjectToken()) {
- if (_vanillaProcessing) {
- return vanillaDeserialize(p, ctxt, p.nextToken());
- }
- // 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
- // what it is, including "expected behavior".
- p.nextToken();
- if (_objectIdReader != null) {
- return deserializeWithObjectId(p, ctxt);
- }
- return deserializeFromObject(p, ctxt);
- }
- return _deserializeOther(p, ctxt, p.getCurrentToken());
- }
- if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
- String propName = p.getCurrentName();
- do {
- p.nextToken();
//根据获取属性名,获取这个属性- SettableBeanProperty prop = _beanProperties.find(propName);
- if (prop != null) { // normal case
- try {
- prop.deserializeAndSet(p, ctxt, bean);
- } catch (Exception e) {
- wrapAndThrow(e, bean, propName, ctxt);
- }
- continue;
- }
- handleUnknownVanilla(p, ctxt, bean, propName);
- } while ((propName = p.nextFieldName()) != null);
- }
- return bean;
针对这个属性进行反序列化解析,由于这个属性是个枚举,所以它的_valueDeserializer是com.fasterxml.jackson.databind.deser.std.EnumDeserializer
- public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
- Object instance) throws IOException
- {
- Object value;
- if (p.hasToken(JsonToken.VALUE_NULL)) {
- if (_skipNulls) {
- return;
- }
- value = _nullProvider.getNullValue(ctxt);
- } else if (_valueTypeDeserializer == null) {
//com.fasterxml.jackson.databind.deser.std.EnumDeserializer- value = _valueDeserializer.deserialize(p, ctxt);
- // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
- if (value == null) {
- if (_skipNulls) {
- return;
- }
- value = _nullProvider.getNullValue(ctxt);
- }
- } else {
- value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
- }
- try {
- _setter.invoke(instance, value);
- } catch (Exception e) {
- _throwAsIOE(p, e, value);
- }
- }
那为什么jackson枚举的反序列化默认用的是EnumDeserializer呢?
这要回到文章开始的地方说起:在一步中会判断指定的类型是否能够进行canRead()
- for (HttpMessageConverter<?> converter : this.messageConverters) {
- Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
- GenericHttpMessageConverter<?> genericConverter =
- (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
- if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
- (targetClass != null && converter.canRead(targetClass, contentType))) {
- if (message.hasBody()) {
- HttpInputMessage msgToUse =
- getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
- body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
- ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
- body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
- }
- else {
- body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
- }
- break;
- }
- }
就从canRead()方法说起:
- public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
- if (!canRead(mediaType)) {
- return false;
- }
- JavaType javaType = getJavaType(type, contextClass);
- AtomicReference<Throwable> causeRef = new AtomicReference<>();
- if (this.objectMapper.canDeserialize(javaType, causeRef)) {
- return true;
- }
- logWarningIfNecessary(javaType, causeRef.get());
- return false;
- }
- public boolean canDeserialize(JavaType type, AtomicReference<Throwable> cause)
- {
- return createDeserializationContext(null,
- getDeserializationConfig()).hasValueDeserializerFor(type, cause);
- }
- public boolean hasValueDeserializerFor(JavaType type, AtomicReference<Throwable> cause) {
- try {
- return _cache.hasValueDeserializerFor(this, _factory, type);
- } ...
- }
- public boolean hasValueDeserializerFor(DeserializationContext ctxt,
- DeserializerFactory factory, JavaType type)
- throws JsonMappingException
- {
- /* Note: mostly copied from findValueDeserializer, except for
- * handling of unknown types
- */
- JsonDeserializer<Object> deser = _findCachedDeserializer(type);
- if (deser == null) {
- deser = _createAndCacheValueDeserializer(ctxt, factory, type);
- }
- return (deser != null);
- }
注意这个名称createAndCache它是会缓存的,也就是这个类型只会找一次,找到之后,就它的反序列化类就绑定了,缓存起来了,
这们有时配置 jackson的objectMapper,可能会添加很多反序列化的模块,都会注册到_factoryConfig.deserializers()
- protected JsonDeserializer<Object> _findCustomBeanDeserializer(JavaType type,
- DeserializationConfig config, BeanDescription beanDesc)
- throws JsonMappingException
- {
- for (Deserializers d : _factoryConfig.deserializers()) {
- JsonDeserializer<?> deser = d.findBeanDeserializer(type, config, beanDesc);
- if (deser != null) {
- return (JsonDeserializer<Object>) deser;
- }
- }
- return null;
- }
- private final JsonDeserializer<?> _find(JavaType type) {
- if (_classMappings == null) {
- return null;
- }
- return _classMappings.get(new ClassKey(type.getRawClass()));
- }
- 每个模块都有_classMappings这样的集合,记录着类与它的序列化类的对应关系。由于我们没有定义这样的关系,那它就有一个默认的。
再找到这个类的反序列化类以后,再开始找类里面的属性的:
同样是在DeserializerCache类中的_createAndCache2方法:
- protected JsonDeserializer<Object> _createAndCache2(DeserializationContext ctxt,
- DeserializerFactory factory, JavaType type)
- throws JsonMappingException
- {
- JsonDeserializer<Object> deser;
- try {
- deser = _createDeserializer(ctxt, factory, type);
- } catch (IllegalArgumentException iae) {
- // We better only expose checked exceptions, since those
- // are what caller is expected to handle
- throw JsonMappingException.from(ctxt, ClassUtil.exceptionMessage(iae), iae);
- }
- if (deser == null) {
- return null;
- }
- /* cache resulting deserializer? always true for "plain" BeanDeserializer
- * (but can be re-defined for sub-classes by using @JsonCachable!)
- */
- // 27-Mar-2015, tatu: As per [databind#735], avoid caching types with custom value desers
- boolean addToCache = !_hasCustomHandlers(type) && deser.isCachable();
- /* we will temporarily hold on to all created deserializers (to
- * handle cyclic references, and possibly reuse non-cached
- * deserializers (list, map))
- */
- /* 07-Jun-2010, tatu: Danger: [JACKSON-296] was caused by accidental
- * resolution of a reference -- couple of ways to prevent this;
- * either not add Lists or Maps, or clear references eagerly.
- * Let's actually do both; since both seem reasonable.
- */
- /* Need to resolve? Mostly done for bean deserializers; required for
- * resolving cyclic references.
- */
- if (deser instanceof ResolvableDeserializer) {
- _incompleteDeserializers.put(type, deser);
- ((ResolvableDeserializer)deser).resolve(ctxt);
- _incompleteDeserializers.remove(type);
- }
- if (addToCache) {
- _cachedDeserializers.put(type, deser);
- }
- return deser;
- }
遍历每一个属性:
- for (SettableBeanProperty prop : _beanProperties) {
- if (!prop.hasValueDeserializer()) {
- // [databind#125]: allow use of converters
- JsonDeserializer<?> deser = findConvertingDeserializer(ctxt, prop);
- if (deser == null) {
- deser = ctxt.findNonContextualValueDeserializer(prop.getType());
- }
- SettableBeanProperty newProp = prop.withValueDeserializer(deser);
- _replaceProperty(_beanProperties, creatorProps, prop, newProp);
- }
- }
- type.isEnumType()这里是关键:
- protected JsonDeserializer<?> _createDeserializer2(DeserializationContext ctxt,
- DeserializerFactory factory, JavaType type, BeanDescription beanDesc)
- throws JsonMappingException
- {
- final DeserializationConfig config = ctxt.getConfig();
- // If not, let's see which factory method to use:
- if (type.isEnumType()) {
- return factory.createEnumDeserializer(ctxt, type, beanDesc);
- }
- 。。。
- }
- public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,
- JavaType type, BeanDescription beanDesc)
- throws JsonMappingException
- {
- final DeserializationConfig config = ctxt.getConfig();
- final Class<?> enumClass = type.getRawClass();
- // 23-Nov-2010, tatu: Custom deserializer?
- JsonDeserializer<?> deser = _findCustomEnumDeserializer(enumClass, config, beanDesc);
- if (deser == null) {
- ValueInstantiator valueInstantiator = _constructDefaultValueInstantiator(ctxt, beanDesc);
- SettableBeanProperty[] creatorProps = (valueInstantiator == null) ? null
- : valueInstantiator.getFromObjectArguments(ctxt.getConfig());
- // May have @JsonCreator for static factory method:
//这里是重点,如上面的注释,如果在enum中定义了工厂方法,找打上了JsonCreator的话,那就算指定了反序列化的方法了,会通过反射执行反序列化
- for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
- if (_hasCreatorAnnotation(ctxt, factory)) {
- if (factory.getParameterCount() == 0) { // [databind#960]
- deser = EnumDeserializer.deserializerForNoArgsCreator(config, enumClass, factory);
- break;
- }
- Class<?> returnType = factory.getRawReturnType();
- // usually should be class, but may be just plain Enum<?> (for Enum.valueOf()?)
- if (returnType.isAssignableFrom(enumClass)) {
- deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps);
- break;
- }
- }
- }
- // Need to consider @JsonValue if one found
- if (deser == null) {
- deser = new EnumDeserializer(constructEnumResolver(enumClass,
- config, beanDesc.findJsonValueAccessor()),
- config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
- }
- }
- // and then post-process it too
- if (_factoryConfig.hasDeserializerModifiers()) {
- for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) {
- deser = mod.modifyEnumDeserializer(config, type, beanDesc, deser);
- }
- }
- return deser;
- }
- _findCustomEnumDeserializer又云objectMapper中的配置中找:由于没有配置,所以返回null
- protected JsonDeserializer<?> _findCustomEnumDeserializer(Class<?> type,
- DeserializationConfig config, BeanDescription beanDesc)
- throws JsonMappingException
- {
- for (Deserializers d : _factoryConfig.deserializers()) {
- JsonDeserializer<?> deser = d.findEnumDeserializer(type, config, beanDesc);
- if (deser != null) {
- return deser;
- }
- }
- return null;
- }
由一没找到,所以就指定了EnumDeserializer为枚举的默认反序列化类了。
spring boot 是如何利用jackson进行反序列化的?的更多相关文章
- spring boot 是如何利用jackson进行序列化的?
接上一篇:spring boot 是如何利用jackson进行反序列化的? @RestController public class HelloController { @RequestMapping ...
- Spring Boot 多站点利用 Redis 实现 Session 共享
如何在不同站点(web服务进程)之间共享会话 Session 呢,原理很简单,就是把这个 Session 独立存储在一个地方,所有的站点都从这个地方读取 Session. 通常我们使用 Redis 来 ...
- springboot自定义消息转换器HttpMessageConverter Spring Boot - 使用Gson替换Jackson
Jackson一直是springframework默认的json库,从4.1开始,springframework支持通过配置GsonHttpMessageConverter的方式使用Gson. 在典型 ...
- Spring Boot 2.0 利用 Spring Security 实现简单的OAuth2.0认证方式2
0.前言 经过前面一小节已经基本配置好了基于SpringBoot+SpringSecurity+OAuth2.0的环境.这一小节主要对一些写固定InMemory的User和Client进行扩展.实现动 ...
- Spring Boot 2.0 利用 Spring Security 实现简单的OAuth2.0认证方式1
0. 前言 之前帐号认证用过自己写的进行匹配,现在要学会使用标准了.准备了解和使用这个OAuth2.0协议. 1. 配置 1.1 配置pom.xml 有些可能会用不到,我把我项目中用到的所有包都贴出来 ...
- spring boot 配置 fastjson 替代 Jackson (并解决返回字符串带双引号问题)
注:以我遇到的情况,只要发出的请求参数是map格式的,都会在前后多加一个双引号 以下代码有两个功能:1.FastJson 替换 Spring 自带的 Jackson 2.解决返回的字符串带双引号问题 ...
- Spring boot 集成hessian - LocalDateTime序列化和反序列化
- 反序列化 import com.caucho.hessian.HessianException; import com.caucho.hessian.io.AbstractDeserializer ...
- Spring Boot(四):利用devtools实现热部署,改动代码自动生效
一.前言 spring-boot-devtools是一个为开发者服务的一个模块,其中最重要的功能就是自动应用代码更改到最新的App上面去.原理是在发现代码有更改之后,重新启动应用,但是速度比手动停止后 ...
- Spring Boot + Redis实战-利用自定义注解+分布式锁实现接口幂等性
场景 不管是传统行业还是互联网行业,我们都需要保证大部分操作是幂等性的,简单点说,就是无论用户点击多少次,操作多少遍,产生的结果都是一样的,是唯一的.而今次公司的项目里,又被我遇到了这么一个幂等性的问 ...
随机推荐
- Codeforces 798D Mike and distribution (构造)
题目链接 http://codeforces.com/contest/798/problem/D 题解 前几天的模拟赛,居然出这种智商题..被打爆了QAQ 这个的话,考虑只有一个序列怎么做,把所有的排 ...
- 使用ElementUI创建项目
从 0 开始搭建 element 项目 第一步,安装 Nodejs/NPM https://nodejs.org/zh-cn/download/ 下载安装即可! 第二步,安装 vue-cli 打开 c ...
- C++入门经典-例5.8-使用指针函数进行运算
1:函数指针式指向函数内存的指针,一个函数在编译时被分配给一个入口地址,这个函数的入口地址就称为函数指针.可以用一个指针变量指向函数,然后通过该指针变量调用此函数. 一个函数可以返回一个整数型值.字符 ...
- Java多线程核心知识
多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关 ...
- [翻译]C#中异步方法的性能特点
翻译自一篇博文,原文:The performance characteristics of async methods in C# 异步系列 剖析C#中的异步方法 扩展C#中的异步方法 C#中异步方法 ...
- 第十一周Java学习总结。
java UI 图形界面知识梳理: ATM: 在整个AWT包中提供的所有工具类主要分为以下3种. (1)组件:Component. (2)容器:Container. (3)布局管理器:LayoutMa ...
- 常用IDE 教程(IntelliJ IDEA、Android Studio、Chrome)
1.IntelliJ IDEA 使用教程 http://wiki.jikexueyuan.com/project/intellij-idea-tutorial/ 2.Chrome 开发工具指南 htt ...
- Android 的四大组件都需要在清单文件中注册吗?
Activity . Service . ContentProvider 如 果 要 使 用 则 必 须 在 AndroidManifest.xml 中 进 行 注 册 , 而BroadcastRec ...
- c#端口扫描器wpf+socket
布局如下 <Window x:Class="PortTest.MainWindow" xmlns="http://schemas.microsoft.com/win ...
- golang入门案例之http client请求
发请求,接收接送,并解析 package main import ( "fmt" "net/http" "io/ioutil" " ...