引言

前端传入的 json 数据如何被解析成 Java 对象作为 API入参,API 返回结果又如何将 Java 对象解析成 json 格式数据返回给前端,其实在整个数据流转过程中,HttpMessageConverter 起到了重要作用;另外在转换的过程我们可以加入哪些定制化内容?

源代碼

核心邏輯代碼:

// 对于String类型的,直接 append 返回,不转jsonif ("java.lang.String".equals(type.getTypeName())) {try {// 1.先解析json對象,如果不是json對象的,走catch邏輯Object jsonObject = JSON.parse(value.toString());objectWriter.writeValue(generator, jsonObject);} catch (Throwable e) {log.error("OvsHttpMessageConverter writeInternal,JSON.parse(value.toString()) = {}", value, e);// 2.不是json對象的,就原樣輸出stringobjectWriter.writeValue(generator, value);}}

自定義的 HttpMessageConverter :

package com.alibaba.ovs.operationcenter.config;import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
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.*;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.util.TypeUtils;import java.io.IOException;
import java.lang.reflect.Type;/*** @author: Jack* 2020-08-24 23:10*/
@Slf4j
public class OvsHttpMessageConverter extends MappingJackson2HttpMessageConverter {private static final MediaType TEXT_EVENT_STREAM = new MediaType("text", "event-stream");public OvsHttpMessageConverter() {}public OvsHttpMessageConverter(ObjectMapper objectMapper) {super(objectMapper);}@Overrideprotected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {log.info("OvsHttpMessageConverter writeInternal,type={},object={}", type.getTypeName(), JSON.toJSONString(object));MediaType contentType = outputMessage.getHeaders().getContentType();JsonEncoding encoding = getJsonEncoding(contentType);JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);try {Object value = object;Class<?> serializationView = null;FilterProvider filters = null;JavaType javaType = null;if (object instanceof MappingJacksonValue) {MappingJacksonValue container = (MappingJacksonValue) object;value = container.getValue();serializationView = container.getSerializationView();filters = container.getFilters();}ObjectWriter objectWriter = (serializationView != null ? this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());// 对于String类型的,直接 append 返回,不转jsonif ("java.lang.String".equals(type.getTypeName())) {try {// 1.先解析json對象,如果不是json對象的,走catch邏輯Object jsonObject = JSON.parse(value.toString());objectWriter.writeValue(generator, jsonObject);} catch (Throwable e) {log.error("OvsHttpMessageConverter writeInternal,JSON.parse(value.toString()) = {}", value, e);// 2.不是json對象的,就原樣輸出stringobjectWriter.writeValue(generator, value);}} else {writePrefix(generator, object);if (type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) {javaType = getJavaType(type, null);}if (filters != null) {objectWriter = objectWriter.with(filters);}if (javaType != null && javaType.isContainerType()) {objectWriter = objectWriter.forType(javaType);}SerializationConfig config = objectWriter.getConfig();if (contentType != null && contentType.isCompatibleWith(TEXT_EVENT_STREAM) &&config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();prettyPrinter.indentObjectsWith(new DefaultIndenter("  ", "\ndata:"));objectWriter = objectWriter.with(prettyPrinter);}objectWriter.writeValue(generator, value);writeSuffix(generator, object);}generator.flush();} catch (JsonProcessingException ex) {throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);}}}

WebMvcConfig代碼:

package com.alibaba.ovs.operationcenter.config;import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;/*** @author: Jack* 2020-08-14 18:53*/
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {// MappingJackson2HttpMessageConverter httpMessageConverter = new MappingJackson2HttpMessageConverter();OvsHttpMessageConverter httpMessageConverter = new OvsHttpMessageConverter();ObjectMapper objectMapper = new ObjectMapper();/*** 序列换成json时,将所有的long变成string, 因为js 中得 Number 数字类型不能包含所有的 java long 值 (js中会被截断)* 参考文章: https://blog.csdn.net/universsky2015/article/details/108010953*/SimpleModule simpleModule = new SimpleModule();simpleModule.addSerializer(Long.class, ToStringSerializer.instance);simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);objectMapper.registerModule(simpleModule);objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);httpMessageConverter.setObjectMapper(objectMapper);// supportedMediaTypesList<MediaType> supportedMediaTypes = new ArrayList<>(httpMessageConverter.getSupportedMediaTypes());supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);supportedMediaTypes.add(MediaType.TEXT_HTML);supportedMediaTypes.add(MediaType.TEXT_PLAIN);supportedMediaTypes.add(MediaType.TEXT_XML);supportedMediaTypes.add(new MediaType("application", "*+json", StandardCharsets.UTF_8));httpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);converters.add(httpMessageConverter);}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// Spring Boot自动配置本身不会自动把/swagger-ui.html这个路径映射到对应的目录META-INF/resources/下面。// 这个swagger-ui.html 相关的所有前端静态文件都在springfox-swagger-ui.jar里面。registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 多个拦截器组成一个拦截器链// addPathPatterns 用于添加拦截规则// excludePathPatterns 用户排除拦截//添加拦截器registry.addInterceptor(new CheckpreloadInterceptor());super.addInterceptors(registry);}}

HttpMessageConverter是什麼?

其中,

HttpMessageConverter 接口介绍
org.springframework.http.converter.HttpMessageConverter 是一个策略接口,接口说明如下:

/** Copyright 2002-2017 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.converter;import java.io.IOException;
import java.util.List;import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;/*** Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.** @author Arjen Poutsma* @author Juergen Hoeller* @since 3.0*/
public interface HttpMessageConverter<T> {/*** Indicates whether the given class can be read by this converter.* @param clazz the class to test for readability* @param mediaType the media type to read (can be {@code null} if not specified);* typically the value of a {@code Content-Type} header.* @return {@code true} if readable; {@code false} otherwise*/boolean canRead(Class<?> clazz, MediaType mediaType);/*** Indicates whether the given class can be written by this converter.* @param clazz the class to test for writability* @param mediaType the media type to write (can be {@code null} if not specified);* typically the value of an {@code Accept} header.* @return {@code true} if writable; {@code false} otherwise*/boolean canWrite(Class<?> clazz, MediaType mediaType);/*** Return the list of {@link MediaType} objects supported by this converter.* @return the list of supported media types*/List<MediaType> getSupportedMediaTypes();/*** Read an object of the given type from the given input message, and returns it.* @param clazz the type of object to return. This type must have previously been passed to the* {@link #canRead canRead} method of this interface, which must have returned {@code true}.* @param inputMessage the HTTP input message to read from* @return the converted object* @throws IOException in case of I/O errors* @throws HttpMessageNotReadableException in case of conversion errors*/T read(Class<? extends T> clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException;/*** Write an given object to the given output message.* @param t the object to write to the output message. The type of this object must have previously been* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.* @param contentType the content type to use when writing. May be {@code null} to indicate that the* default content type of the converter must be used. If not {@code null}, this media type must have* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have* returned {@code true}.* @param outputMessage the message to write to* @throws IOException in case of I/O errors* @throws HttpMessageNotWritableException in case of conversion errors*/void write(T t, MediaType contentType, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException;}

Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. 简单说就是 HTTP request (请求)和response (响应)的转换器。该接口有只有5个方法,简单来说就是获取支持的 MediaType(application/json之类),接收到请求时判断是否能读(canRead),能读则读(read);返回结果时判断是否能写(canWrite),能写则写(write)。

Spring Boot 缺省配置

我们写 Demo 没有配置任何 MessageConverter,但是数据前后传递依旧好用,是因为 SpringMVC 启动时会自动配置一些HttpMessageConverter,在 WebMvcConfigurationSupport 类中添加了缺省 MessageConverter:

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();stringConverter.setWriteAcceptCharset(false);messageConverters.add(new ByteArrayHttpMessageConverter());messageConverters.add(stringConverter);messageConverters.add(new ResourceHttpMessageConverter());messageConverters.add(new SourceHttpMessageConverter<Source>());messageConverters.add(new AllEncompassingFormHttpMessageConverter());if (romePresent) {messageConverters.add(new AtomFeedHttpMessageConverter());messageConverters.add(new RssChannelHttpMessageConverter());}if (jackson2XmlPresent) {ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));}else if (jaxb2Present) {messageConverters.add(new Jaxb2RootElementHttpMessageConverter());}if (jackson2Present) {ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));}else if (gsonPresent) {messageConverters.add(new GsonHttpMessageConverter());}}

我们看到很熟悉的 MappingJackson2HttpMessageConverter ,如果我们引入 jackson 相关包,Spring 就会为我们添加该 MessageConverter,但是我们通常在搭建框架的时候还是会手动添加配置 MappingJackson2HttpMessageConverter,为什么? 先思考一下:

当我们配置了自己的 MessageConverter, SpringMVC 启动过程就不会调用 addDefaultHttpMessageConverters 方法,且看下面代码 if 条件,这样做也是为了定制化我们自己的 MessageConverter

protected final List<HttpMessageConverter<?>> getMessageConverters() {if (this.messageConverters == null) {this.messageConverters = new ArrayList<HttpMessageConverter<?>>();configureMessageConverters(this.messageConverters);if (this.messageConverters.isEmpty()) {addDefaultHttpMessageConverters(this.messageConverters);}extendMessageConverters(this.messageConverters);}return this.messageConverters;}

数据流转解析

数据的请求和响应都要经过 DispatcherServlet 类的 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法的处理

请求过程解析

看 doDispatch 方法中的关键代码:

// 这里的 Adapter 实际上是 RequestMappingHandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
// 实际处理的handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());            mappedHandler.applyPostHandle(processedRequest, response, mv);

从进入handle之后我先将调用栈粘贴在此处,希望小伙伴可以按照调用栈路线动手跟踪尝试:

readWithMessageConverters:192, AbstractMessageConverterMethodArgumentResolver (org.springframework.web.servlet.mvc.method.annotation)
readWithMessageConverters:150, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:128, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
resolveArgument:121, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
getMethodArgumentValues:158, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:128, InvocableHandlerMethod (org.springframework.web.method.support)

// 下面的调用栈重点关注,处理请求和返回值的分叉口就在这里

invokeAndHandle:97, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:849, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:760, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:85, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:967, DispatcherServlet (org.springframework.web.servlet)

这里重点说明调用栈最顶层 readWithMessageConverters 方法中内容:

// 遍历 messageConverters
for (HttpMessageConverter<?> converter : this.messageConverters) {Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();// 上文类关系图处要重点记住的地方,主要判断 MappingJackson2HttpMessageConverter 是否是 GenericHttpMessageConverter 类型if (converter instanceof GenericHttpMessageConverter) {GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;if (genericConverter.canRead(targetType, contextClass, contentType)) {if (logger.isDebugEnabled()) {logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");}if (inputMessage.getBody() != null) {inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);body = genericConverter.read(targetType, contextClass, inputMessage);body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);}break;}}else if (targetClass != null) {if (converter.canRead(targetClass, contentType)) {if (logger.isDebugEnabled()) {logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");}if (inputMessage.getBody() != null) {inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);}break;}}
}

然后就判断是否canRead,能读就read,最终走到下面代码处将输入的内容反序列化出来:

protected Object _readMapAndClose(JsonParser p0, JavaType valueType)throws IOException{try (JsonParser p = p0) {Object result;JsonToken t = _initForReading(p);if (t == JsonToken.VALUE_NULL) {// Ask JsonDeserializer what 'null value' to use:DeserializationContext ctxt = createDeserializationContext(p,getDeserializationConfig());result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {result = null;} else {DeserializationConfig cfg = getDeserializationConfig();DeserializationContext ctxt = createDeserializationContext(p, cfg);JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);if (cfg.useRootWrapping()) {result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);} else {result = deser.deserialize(p, ctxt);}ctxt.checkUnresolvedObjectId();}// Need to consume the token toop.clearCurrentToken();return result;}}

到这里从请求中解析参数过程就到此结束了,趁热打铁来看将响应结果返回给前端的过程

返回过程解析

在上面调用栈请求和返回结果分叉口处同样处理返回的内容:

writeWithMessageConverters:224, AbstractMessageConverterMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:174, RequestResponseBodyMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)
handleReturnValue:81, HandlerMethodReturnValueHandlerComposite (org.springframework.web.method.support)
// 分叉口
invokeAndHandle:113, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)

重点关注调用栈顶层内容,是不是很熟悉的样子,完全一样的逻辑, 判断是否能写canWrite,能写则write:

for (HttpMessageConverter<?> messageConverter : this.messageConverters) {if (messageConverter instanceof GenericHttpMessageConverter) {if (((GenericHttpMessageConverter) messageConverter).canWrite(declaredType, valueType, selectedMediaType)) {outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),inputMessage, outputMessage);if (outputValue != null) {addContentDispositionHeader(inputMessage, outputMessage);((GenericHttpMessageConverter) messageConverter).write(outputValue, declaredType, selectedMediaType, outputMessage);if (logger.isDebugEnabled()) {logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +"\" using [" + messageConverter + "]");}}return;}}else if (messageConverter.canWrite(valueType, selectedMediaType)) {outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),inputMessage, outputMessage);if (outputValue != null) {addContentDispositionHeader(inputMessage, outputMessage);((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);if (logger.isDebugEnabled()) {logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +"\" using [" + messageConverter + "]");}}return;}
}

我们看到有这样一行代码:

outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),inputMessage, outputMessage);

我们在设计 RESTful API 接口的时候通常会将返回的数据封装成统一格式,通常我们会实现 ResponseBodyAdvice<T> 接口来处理所有 API 的返回值,在真正 write 之前将数据进行统一的封装

@RestControllerAdvice()
public class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {if (body instanceof CommonResult) {return body;}return new CommonResult<Object>(body);}}

整个处理流程就是这样。

将各种常用 HttpMessageConverter 支持的MediaType 和 JavaType 以及对应关系总结在此处:

參考資料:

https://www.jianshu.com/p/3e1de3d02dd8
https://blog.csdn.net/BryantLmm/article/details/85163590


Kotlin开发者社区

专注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函数式编程、编程思想、"高可用,高性能,高实时"大型分布式系统架构设计主题。

High availability, high performance, high real-time large-scale distributed system architecture design

分布式框架:Zookeeper、分布式中间件框架等
分布式存储:GridFS、FastDFS、TFS、MemCache、redis等
分布式数据库:Cobar、tddl、Amoeba、Mycat
云计算、大数据、AI算法
虚拟化、云原生技术
分布式计算框架:MapReduce、Hadoop、Storm、Flink等
分布式通信机制:Dubbo、RPC调用、共享远程数据、消息队列等
消息队列MQ:Kafka、MetaQ,RocketMQ
怎样打造高可用系统:基于硬件、软件中间件、系统架构等一些典型方案的实现:HAProxy、基于Corosync+Pacemaker的高可用集群套件中间件系统
Mycat架构分布式演进
大数据Join背后的难题:数据、网络、内存和计算能力的矛盾和调和
Java分布式系统中的高性能难题:AIO,NIO,Netty还是自己开发框架?
高性能事件派发机制:线程池模型、Disruptor模型等等。。。

合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。不积跬步,无以至千里;不积小流,无以成江河。

Kotlin 简介

Kotlin是一门非研究性的语言,它是一门非常务实的工业级编程语言,它的使命就是帮助程序员们解决实际工程实践中的问题。使用Kotlin 让 Java程序员们的生活变得更好,Java中的那些空指针错误,浪费时间的冗长的样板代码,啰嗦的语法限制等等,在Kotlin中统统消失。Kotlin 简单务实,语法简洁而强大,安全且表达力强,极富生产力。

Java诞生于1995年,至今已有23年历史。当前最新版本是 Java 9。在 JVM 生态不断发展繁荣的过程中,也诞生了Scala、Groovy、Clojure 等兄弟语言。

Kotlin 也正是 JVM 家族中的优秀一员。Kotlin是一种现代语言(版本1.0于2016年2月发布)。它最初的目的是像Scala那样,优化Java语言的缺陷,提供更加简单实用的编程语言特性,并且解决了性能上的问题,比如编译时间。 JetBrains在这些方面做得非常出色。

Kotlin语言的特性

用 Java 开发多年以后,能够尝试一些新的东西真是太棒了。如果您是 Java 开发人员,使用 Kotlin 将会非常自然流畅。如果你是一个Swift开发者,你将会感到似曾相识,比如可空性(Nullability)。 Kotlin语言的特性有:

1.简洁

大幅减少样板代码量。

2.与Java的100%互操作性

Kotlin可以直接与Java类交互,反之亦然。这个特性使得我们可以直接重用我们的代码库,并将其迁移到 Kotlin中。由于Java的互操作性几乎无处不在。我们可以直接访问平台API以及现有的代码库,同时仍然享受和使用 Kotlin 的所有强大的现代语言功能。

3.扩展函数

Kotlin 类似于 C# 和 Gosu, 它提供了为现有类提供新功能扩展的能力,而不必从该类继承或使用任何类型的设计模式 (如装饰器模式)。

4.函数式编程

Kotlin 语言一等支持函数式编程,就像Scala一样。具备高阶函数、Lambda 表达式等函数式基本特性。

5.默认和命名参数

在Kotlin中,您可以为函数中的参数设置一个默认值,并给每个参数一个名称。这有助于编写易读的代码。

6.强大的开发工具支持

而由于是JetBrains出品,我们拥有很棒的IDE支持。虽然Java到Kotlin的自动转换并不是100% OK 的,但它确实是一个非常好的工具。使用 IDEA 的工具转换Java代码为 Kotlin 代码时,可以轻松地重用60%-70%的结果代码,而且修改成本很小。

Kotlin 除了简洁强大的语法特性外,还有实用性非常强的API以及围绕它构建的生态系统。例如:集合类 API、IO 扩展类、反射API 等。同时 Kotlin 社区也提供了丰富的文档和大量的学习资料,还有在线REPL。

A modern programming language that makes developers happier. Open source forever

图来自《Kotlin从入门到进阶实战》 (陈光剑,清华大学出版社)

图来自《Kotlin从入门到进阶实战》 (陈光剑,清华大学出版社)

https://kotlinlang.org/


http://www.taodudu.cc/news/show-2355956.html

相关文章:

  • 【Java 并发编程】Java 创建线程池的正确姿势: Executors 和 ThreadPoolExecutor 详解...
  • Java 并发编程:轻量级锁和偏向锁详解
  • Kotlin 1.4 版本正式发布:新功能一覽
  • 怎样写好一篇高质量的技术文章?
  • 一切皆是文件:UNIX,Linux 操作系統的設計哲學
  • UML类图详细介绍
  • 川土微电子 | CA-IS3050U隔离式CAN收发器
  • 土木学matlab还是python_五行属土的字大全
  • 合抱之木,生于毫末。九层之台,起于累土。千里之行,始于足下
  • UML - 类图的关系总结
  • JAVA基础之异常
  • 九尺之台,始于垒土
  • Android 设计素材积累(九层之台起于垒土)
  • 初识Python必看基础知识~ 续(6)九层之台,起于垒土,肝肝肝~
  • 九层之台,起于垒土
  • 文学de垒土
  • 九层之台,始于垒土;合抱之木,始于毫末;千里之行,始于足下!
  • 合抱之木,生于毫末; 九层之台,起于垒土。
  • Android APP安全测试
  • 【电子商务法】北邮国际学院大三上期末复习
  • 大数据架构和模式
  • 2020上半年DeFi行业研究报告-Part2 发行 | TokenInsight
  • 独家研究 I 某新一线城市中高端养老社区项目(CCRC)入住客户画像深度洞察研究报告
  • Java基础:第5-6章(重点)
  • 刑事实务办案中疑难问题
  • 正确认识P2P,从容面对风暴
  • 当前网络安全风险及举例
  • 互联网金融学习总结(1)——互联网金融(ITFIN)概念相关学习
  • Chainlink的77种用法
  • 合同法律风险管理 被骗者刑事风险

Spring Boot 自定義 HttpMessageConverter 解決 String 類型返回JSON對象問題相关推荐

  1. Spring Boot的启动器Starter详解

    Spring Boot的启动器Starter详解 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs Spring Boot ...

  2. ElasticSearch——Spring Boot 集成 ES 操作详解

    文章目录 ElasticSearch--Spring Boot 集成 ES 操作详解 1.SpringBoot 集成 ES 2.索引的API操作详解 3.文档的API操作详解 ElasticSearc ...

  3. 轻量级数据库sqlite,spring boot+sqlite的配置详解 (一)

    spring boot+sqlite的配置,及成功运行详解 sqlite数据库的安装与调试 首先,通过sqlite官方地址下载对应的安装包 https://www.sqlite.org/downloa ...

  4. 轻量级数据库sqlite,spring boot+sqlite的配置详解 (二)

    轻量级数据库sqlite,spring boot+sqlite的配置详解 (二) 轻量级数据库sqlite,spring boot+sqlite的配置详解 (一) 首先,需要创建一个spring bo ...

  5. Spring Boot 项目中Java对象的字符串类型属性值转换为JSON对象的布尔类型键值的解决方法及过程

    文章目录 场景描述 示例说明 解决历程 @JsonFormat是否能解决问题? 万能方案-调试 替代方案 补充知识 Java对象与JSON对象的序列化与反序列化 相关注解说明 后记 场景描述 在Spr ...

  6. Spring Boot jackson配置使用详解

    Spring Boot系列-json框架jackson配置详解 T1 - 前言 目前Java最常见的3中JSON操作框架分别为Gson.Jackson.FastJson,该篇文章主要讲解jackson ...

  7. Spring Boot的SpringApplication类详解

    相信使用过Spring Boot的开发人员,都对Spring Boot的核心模块中提供的SpringApplication类不陌生.SpringApplication类的run()方法往往在Sprin ...

  8. Spring Boot的Tomcat 启动详解

    https://juejin.im/post/5a3273a451882575d42f68f9 在解读embeddedTomcat容器启动之前有几个要点需读懂 Spring Boot @Configu ...

  9. spring boot application.properties 属性详解

    2019年3月21日17:09:59 英文原版: https://docs.spring.io/spring-boot/docs/current/reference/html/common-appli ...

  10. Spring boot中使用aop详解

    aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案. 现在就以springboot中aop的使用来了解一下aop. 一:使用aop来完成全局请求日志处理 创建一个sp ...

最新文章

  1. linux内核二当家,Linux PWN从入门到熟练(二)
  2. 自己做回CA 给别人发证
  3. 从0到1,了解NLP中的文本相似度 1
  4. Changes in Android 7 Nougat
  5. php和java之间rsa加密互通
  6. python httplib2的安装
  7. vue 安装指定版本swiper_Vue中的runtime-only和runtime-compiler
  8. 06 使用VS2012开发简单控制器程序 1214
  9. C++文件读写函数之——fopen、fread和fwrite、fgetc和fputc、fgets和fputs、ftellf和fseek、rewind...
  10. element 动态加载下拉框_记一次很坑的需求:给element-cascader添加一个重置和确定按钮...
  11. ios 裁剪框大小_IOS UIimageView裁剪
  12. windows server2016 如何安装IIS
  13. DIY面试题 for AI产品经理 | “智能音箱半夜诡异笑声”的原因分析及建议方案
  14. 使用 SpringMail +163 邮箱 发送邮件的方法
  15. finder个人收藏和前往文件夹
  16. 聊聊 SAP 产品 UI 上的消息显示机制
  17. 电脑硬盘右击计算机就卡死,电脑右键就卡死是什么原因?
  18. 深度学习思维导图(基于TensorFlow框架)
  19. 前端学习第一天--编程软件vscode使用、HTML标签
  20. 工业ccd视觉检测四大功能带你走进机器视觉

热门文章

  1. 【ArchSummit干货分享】个推大数据金融风控算法实践
  2. 【操作系统】结合哲学家进餐问题分析如何预防死锁
  3. lol全队消息怎么发_英雄联盟如何发全部消息,LOL怎样发送消息给全部
  4. 802.11电源管理模式
  5. 前端web设计师_Web设计师的时尚Web设计主题
  6. 做正确的事和正确的做事的区别
  7. qpython3手机版怎么运行不了_QPython3手机版
  8. threejs第十三用 简单堆积木
  9. 《请停止无效的社交》第一章七问七答读后感作文2300字
  10. 【CentOS8.0开启防火墙放行8081端口】