Java?Kryo,Protostuff,Hessian序列化方式對(duì)比
前言
前段時(shí)間在寫RPC框架的時(shí)候用到了Kryo、Hessian、Protostuff三種序列化方式。但是當(dāng)時(shí)因?yàn)榧庇趯?shí)現(xiàn)功能,就只是簡(jiǎn)單的的看了一下如何使用這三種序列化方式,并沒(méi)有去深入研究各自的特性,以及優(yōu)點(diǎn)和缺點(diǎn)。知道現(xiàn)在就將RPC框架寫完了之后,才有時(shí)間靜下心來(lái)對(duì)三種方式做一個(gè)對(duì)比,總結(jié)。
Kryo、Hessain、Protostuff都是第三方開(kāi)源的序列化/反序列化框架,要了解其各自的特性,我們首先需要知道序列化/反序列化是什么:
序列化:就是將對(duì)象轉(zhuǎn)化成字節(jié)序列的過(guò)程。
反序列化:就是講字節(jié)序列轉(zhuǎn)化成對(duì)象的過(guò)程。
seriallization 序列化 : 將對(duì)象轉(zhuǎn)化為便于傳輸?shù)母袷剑?常見(jiàn)的序列化格式:二進(jìn)制格式,字節(jié)數(shù)組,json字符串,xml字符串。
deseriallization 反序列化:將序列化的數(shù)據(jù)恢復(fù)為對(duì)象的過(guò)程
如果對(duì)序列化相關(guān)概念還不是很清楚的同學(xué)可以參照美團(tuán)技術(shù)團(tuán)隊(duì)的序列化和反序列化
筆記參考圖:

性能對(duì)比
前期準(zhǔn)備
- 我們先創(chuàng)建一個(gè)新的Maven項(xiàng)目
- 然后導(dǎo)入依賴
<dependency> ? ?<groupId>org.junit.jupiter</groupId> ? ?<artifactId>junit-jupiter-api</artifactId> ? ?<version>5.8.2</version> ? ?<scope>test</scope> </dependency> <!-- 代碼簡(jiǎn)化 --> <dependency> ? ?<groupId>org.projectlombok</groupId> ? ?<artifactId>lombok</artifactId> ? ?<version>1.18.20</version> </dependency> <!--kryo--> <dependency> ? ?<groupId>com.esotericsoftware</groupId> ? ?<artifactId>kryo-shaded</artifactId> ? ?<version>4.0.2</version> </dependency> <dependency> ? ?<groupId>commons-codec</groupId> ? ?<artifactId>commons-codec</artifactId> ? ?<version>1.10</version> </dependency> <!--protostuff--> <dependency> ? ?<groupId>io.protostuff</groupId> ? ?<artifactId>protostuff-core</artifactId> ? ?<version>1.7.2</version> </dependency> <dependency> ? ?<groupId>io.protostuff</groupId> ? ?<artifactId>protostuff-runtime</artifactId> ? ?<version>1.7.2</version> </dependency> <!--hessian2--> <dependency> ? ?<groupId>com.caucho</groupId> ? ?<artifactId>hessian</artifactId> ? ?<version>4.0.62</version> </dependency>
工具類:
kryo
package cuit.pymjl.utils;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.apache.commons.codec.binary.Base64;
import org.objenesis.strategy.StdInstantiatorStrategy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
/**
* @author Pymjl
* @version 1.0
* @date 2022/4/18 20:07
**/
@SuppressWarnings("all")
public class KryoUtils {
? ?private static final String DEFAULT_ENCODING = "UTF-8";
?
? ?//每個(gè)線程的 Kryo 實(shí)例
? ?private static final ThreadLocal<Kryo> KRYO_LOCAL = new ThreadLocal<Kryo>() {
? ? ? ?@Override
? ? ? ?protected Kryo initialValue() {
? ? ? ? ? ?Kryo kryo = new Kryo();
?
? ? ? ? ? ?/**
? ? ? ? ? ? * 不要輕易改變這里的配置!更改之后,序列化的格式就會(huì)發(fā)生變化,
? ? ? ? ? ? * 上線的同時(shí)就必須清除 Redis 里的所有緩存,
? ? ? ? ? ? * 否則那些緩存再回來(lái)反序列化的時(shí)候,就會(huì)報(bào)錯(cuò)
? ? ? ? ? ? */
? ? ? ? ? ?//支持對(duì)象循環(huán)引用(否則會(huì)棧溢出)
? ? ? ? ? ?kryo.setReferences(true); //默認(rèn)值就是 true,添加此行的目的是為了提醒維護(hù)者,不要改變這個(gè)配置
?
? ? ? ? ? ?//不強(qiáng)制要求注冊(cè)類(注冊(cè)行為無(wú)法保證多個(gè) JVM 內(nèi)同一個(gè)類的注冊(cè)編號(hào)相同;而且業(yè)務(wù)系統(tǒng)中大量的 Class 也難以一一注冊(cè))
? ? ? ? ? ?kryo.setRegistrationRequired(false); //默認(rèn)值就是 false,添加此行的目的是為了提醒維護(hù)者,不要改變這個(gè)配置
?
? ? ? ? ? ?//Fix the NPE bug when deserializing Collections.
? ? ? ? ? ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy())
? ? ? ? ? ? ? ? ? .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());
?
? ? ? ? ? ?return kryo;
? ? ? }
? };
? ?/**
? ? * 獲得當(dāng)前線程的 Kryo 實(shí)例
? ? *
? ? * @return 當(dāng)前線程的 Kryo 實(shí)例
? ? */
? ?public static Kryo getInstance() {
? ? ? ?return KRYO_LOCAL.get();
? }
?
? ?//-----------------------------------------------
? ?// ? ? ? ? 序列化/反序列化對(duì)象,及類型信息
? ?// ? ? ? ? 序列化的結(jié)果里,包含類型的信息
? ?// ? ? ? ? 反序列化時(shí)不再需要提供類型
? ?//-----------------------------------------------
?
? ?/**
? ? * 將對(duì)象【及類型】序列化為字節(jié)數(shù)組
? ? *
? ? * @param obj 任意對(duì)象
? ? * @param <T> 對(duì)象的類型
? ? * @return 序列化后的字節(jié)數(shù)組
? ? */
? ?public static <T> byte[] writeToByteArray(T obj) {
? ? ? ?ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
? ? ? ?Output output = new Output(byteArrayOutputStream);
?
? ? ? ?Kryo kryo = getInstance();
? ? ? ?kryo.writeClassAndObject(output, obj);
? ? ? ?output.flush();
?
? ? ? ?return byteArrayOutputStream.toByteArray();
? }
? ?/**
? ? * 將對(duì)象【及類型】序列化為 String
? ? * 利用了 Base64 編碼
? ? *
? ? * @param obj 任意對(duì)象
? ? * @param <T> 對(duì)象的類型
? ? * @return 序列化后的字符串
? ? */
? ?public static <T> String writeToString(T obj) {
? ? ? ?try {
? ? ? ? ? ?return new String(Base64.encodeBase64(writeToByteArray(obj)), DEFAULT_ENCODING);
? ? ? } catch (UnsupportedEncodingException e) {
? ? ? ? ? ?throw new IllegalStateException(e);
? ? ? }
? }
? ?/**
? ? * 將字節(jié)數(shù)組反序列化為原對(duì)象
? ? *
? ? * @param byteArray writeToByteArray 方法序列化后的字節(jié)數(shù)組
? ? * @param <T> ? ? ? 原對(duì)象的類型
? ? * @return 原對(duì)象
? ? */
? ?@SuppressWarnings("unchecked")
? ?public static <T> T readFromByteArray(byte[] byteArray) {
? ? ? ?ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
? ? ? ?Input input = new Input(byteArrayInputStream);
?
? ? ? ?Kryo kryo = getInstance();
? ? ? ?return (T) kryo.readClassAndObject(input);
? }
? ?/**
? ? * 將 String 反序列化為原對(duì)象
? ? * 利用了 Base64 編碼
? ? *
? ? * @param str writeToString 方法序列化后的字符串
? ? * @param <T> 原對(duì)象的類型
? ? * @return 原對(duì)象
? ? */
? ?public static <T> T readFromString(String str) {
? ? ? ?try {
? ? ? ? ? ?return readFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)));
? ? ? } catch (UnsupportedEncodingException e) {
? ? ? ? ? ?throw new IllegalStateException(e);
? ? ? }
? }
? ?//-----------------------------------------------
? ?// ? ? ? ? 只序列化/反序列化對(duì)象
? ?// ? ? ? ? 序列化的結(jié)果里,不包含類型的信息
? ?//-----------------------------------------------
?
? ?/**
? ? * 將對(duì)象序列化為字節(jié)數(shù)組
? ? *
? ? * @param obj 任意對(duì)象
? ? * @param <T> 對(duì)象的類型
? ? * @return 序列化后的字節(jié)數(shù)組
? ? */
? ?public static <T> byte[] writeObjectToByteArray(T obj) {
? ? ? ?ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
? ? ? ?Output output = new Output(byteArrayOutputStream);
? ? ? ?Kryo kryo = getInstance();
? ? ? ?kryo.writeObject(output, obj);
? ? ? ?output.flush();
? ? ? ?return byteArrayOutputStream.toByteArray();
? }
?
? ?/**
? ? * 將對(duì)象序列化為 String
? ? * 利用了 Base64 編碼
? ? *
? ? * @param obj 任意對(duì)象
? ? * @param <T> 對(duì)象的類型
? ? * @return 序列化后的字符串
? ? */
? ?public static <T> String writeObjectToString(T obj) {
? ? ? ?try {
? ? ? ? ? ?return new String(Base64.encodeBase64(writeObjectToByteArray(obj)), DEFAULT_ENCODING);
? ? ? } catch (UnsupportedEncodingException e) {
? ? ? ? ? ?throw new IllegalStateException(e);
? ? ? }
? }
? ?/**
? ? * 將字節(jié)數(shù)組反序列化為原對(duì)象
? ? *
? ? * @param byteArray writeToByteArray 方法序列化后的字節(jié)數(shù)組
? ? * @param clazz ? ? 原對(duì)象的 Class
? ? * @param <T> ? ? ? 原對(duì)象的類型
? ? * @return 原對(duì)象
? ? */
? ?@SuppressWarnings("unchecked")
? ?public static <T> T readObjectFromByteArray(byte[] byteArray, Class<T> clazz) {
? ? ? ?ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
? ? ? ?Input input = new Input(byteArrayInputStream);
?
? ? ? ?Kryo kryo = getInstance();
? ? ? ?return kryo.readObject(input, clazz);
? }
? ?/**
? ? * 將 String 反序列化為原對(duì)象
? ? * 利用了 Base64 編碼
? ? *
? ? * @param str ? writeToString 方法序列化后的字符串
? ? * @param clazz 原對(duì)象的 Class
? ? * @param <T> ? 原對(duì)象的類型
? ? * @return 原對(duì)象
? ? */
? ?public static <T> T readObjectFromString(String str, Class<T> clazz) {
? ? ? ?try {
? ? ? ? ? ?return readObjectFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)), clazz);
? ? ? } catch (UnsupportedEncodingException e) {
? ? ? ? ? ?throw new IllegalStateException(e);
? ? ? }
? }
}Hessian
package cuit.pymjl.utils;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @author Pymjl
* @version 1.0
* @date 2022/7/2 12:39
**/
public class HessianUtils {
? ?/**
? ? * 序列化
? ? *
? ? * @param obj obj
? ? * @return {@code byte[]}
? ? */
? ?public static byte[] serialize(Object obj) {
? ? ? ?Hessian2Output ho = null;
? ? ? ?ByteArrayOutputStream baos = null;
? ? ? ?try {
? ? ? ? ? ?baos = new ByteArrayOutputStream();
? ? ? ? ? ?ho = new Hessian2Output(baos);
? ? ? ? ? ?ho.writeObject(obj);
? ? ? ? ? ?ho.flush();
? ? ? ? ? ?return baos.toByteArray();
? ? ? } catch (Exception ex) {
? ? ? ? ? ?ex.printStackTrace();
? ? ? ? ? ?throw new RuntimeException("serialize failed");
? ? ? } finally {
? ? ? ? ? ?if (null != ho) {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?ho.close();
? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? ? ?if (null != baos) {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?baos.close();
? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? }
? ?/**
? ? * 反序列化
? ? *
? ? * @param bytes 字節(jié)
? ? * @param clazz clazz
? ? * @return {@code T}
? ? */
? ?public static ?<T> T deserialize(byte[] bytes, Class<T> clazz) {
? ? ? ?Hessian2Input hi = null;
? ? ? ?ByteArrayInputStream bais = null;
? ? ? ?try {
? ? ? ? ? ?bais = new ByteArrayInputStream(bytes);
? ? ? ? ? ?hi = new Hessian2Input(bais);
? ? ? ? ? ?Object o = hi.readObject();
? ? ? ? ? ?return clazz.cast(o);
? ? ? } catch (Exception ex) {
? ? ? ? ? ?throw new RuntimeException("deserialize failed");
? ? ? } finally {
? ? ? ? ? ?if (null != hi) {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?hi.close();
? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? ? ?if (null != bais) {
? ? ? ? ? ? ? ?try {
? ? ? ? ? ? ? ? ? ?bais.close();
? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? }
}Protostuff
package cuit.pymjl.utils;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Pymjl
* @version 1.0
* @date 2022/6/28 21:00
**/
public class ProtostuffUtils {
? ?/**
? ? * 避免每次序列化都重新申請(qǐng)Buffer空間
? ? * 這個(gè)字段表示,申請(qǐng)一個(gè)內(nèi)存空間用戶緩存,LinkedBuffer.DEFAULT_BUFFER_SIZE表示申請(qǐng)了默認(rèn)大小的空間512個(gè)字節(jié),
? ? * 我們也可以使用MIN_BUFFER_SIZE,表示256個(gè)字節(jié)。
? ? */
? ?private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
? ?/**
? ? * 緩存Schema
? ? * 這個(gè)字段表示緩存的Schema。那這個(gè)Schema是什么呢?就是一個(gè)組織結(jié)構(gòu),就好比是數(shù)據(jù)庫(kù)中的表、視圖等等這樣的組織機(jī)構(gòu),
? ? * 在這里表示的就是序列化對(duì)象的結(jié)構(gòu)。
? ? */
? ?private static final Map<Class<?>, Schema<?>> SCHEMA_CACHE = new ConcurrentHashMap<>();
?
? ?/**
? ? * 序列化方法,把指定對(duì)象序列化成字節(jié)數(shù)組
? ? *
? ? * @param obj 對(duì)象
? ? * @return byte[]
? ? */
? ?@SuppressWarnings("unchecked")
? ?public static <T> byte[] serialize(T obj) {
? ? ? ?Class<T> clazz = (Class<T>) obj.getClass();
? ? ? ?Schema<T> schema = getSchema(clazz);
? ? ? ?byte[] data;
? ? ? ?try {
? ? ? ? ? ?data = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
? ? ? } finally {
? ? ? ? ? ?BUFFER.clear();
? ? ? }
? ? ? ?return data;
? }
?
? ?/**
? ? * 反序列化方法,將字節(jié)數(shù)組反序列化成指定Class類型
? ? *
? ? * @param data 字節(jié)數(shù)組
? ? * @param clazz 字節(jié)碼
? ? * @return
? ? */
? ?public static <T> T deserialize(byte[] data, Class<T> clazz) {
? ? ? ?Schema<T> schema = getSchema(clazz);
? ? ? ?T obj = schema.newMessage();
? ? ? ?ProtostuffIOUtil.mergeFrom(data, obj, schema);
? ? ? ?return obj;
? }
? ?@SuppressWarnings("unchecked")
? ?private static <T> Schema<T> getSchema(Class<T> clazz) {
? ? ? ?Schema<T> schema = (Schema<T>) SCHEMA_CACHE.get(clazz);
? ? ? ?if (schema == null) {
? ? ? ? ? ?schema = RuntimeSchema.getSchema(clazz);
? ? ? ? ? ?if (schema == null) {
? ? ? ? ? ? ? ?SCHEMA_CACHE.put(clazz, schema);
? ? ? ? ? }
? ? ? }
? ? ? ?return schema;
? }
}創(chuàng)建一個(gè)實(shí)體類進(jìn)行測(cè)試:
package cuit.pymjl.entity;
?
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* @author Pymjl
* @version 1.0
* @date 2022/7/2 12:32
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
? ?@Serial
? ?private static final long serialVersionUID = -91809837793898L;
?
? ?private String name;
? ?private String password;
? ?private int age;
? ?private String address;
? ?private String phone;
}序列化后字節(jié)所占空間大小比較
編寫測(cè)試類:
public class MainTest {
? ?@Test
? ?void testLength() {
? ? ? ?Student student = new Student("pymjl", "123456", 18, "北京", "123456789");
? ? ? ?int kryoLength = KryoUtils.writeObjectToByteArray(student).length;
? ? ? ?int hessianLength = HessianUtils.serialize(student).length;
? ? ? ?int protostuffLength = ProtostuffUtils.serialize(student).length;
? ? ? ?System.out.println("kryoLength: " + kryoLength);
? ? ? ?System.out.println("hessianLength: " + hessianLength);
? ? ? ?System.out.println("protostuffLength: " + protostuffLength);
? }
}運(yùn)行截圖:

由圖可知,Hessian序列化后字節(jié)所占的空間都顯著比其他兩種方式要大得多
其他比較
- Hessian使用固定長(zhǎng)度存儲(chǔ)int和long,而kryo使用變長(zhǎng)的int和long保證這種基本數(shù)據(jù)類型序列化后盡量小,實(shí)際應(yīng)用中,很大的數(shù)據(jù)不會(huì)經(jīng)常出現(xiàn)。
- Kryo進(jìn)行序列化的時(shí)候,需要傳入完整類名或者利用 register() 提前將類注冊(cè)到Kryo上,其類與一個(gè)int型的ID相關(guān)聯(lián),序列中只存放這個(gè)ID,因此序列體積就更小,而Hessian則是將所有類字段信息都放入序列化字節(jié)數(shù)組中,直接利用字節(jié)數(shù)組進(jìn)行反序列化,不需要其他參與,因?yàn)榇娴臇|西多處理速度就會(huì)慢點(diǎn)
- Kryo使用不需要實(shí)現(xiàn)Serializable接口,Hessian則需實(shí)現(xiàn)
- Kryo數(shù)據(jù)類的字段增、減,序列化和反序列化時(shí)無(wú)法兼容,而Hessian則兼容,Protostuff是只能在末尾添加新字段才兼容
- Kryo和Hessian使用涉及到的數(shù)據(jù)類中必須擁有無(wú)參構(gòu)造函數(shù)
- Hessian會(huì)把復(fù)雜對(duì)象的所有屬性存儲(chǔ)在一個(gè)Map中進(jìn)行序列化。所以在父類、子類存在同名成員變量的情況下,Hessian序列化時(shí),先序列化子類,然后序列化父類,因此反序列化結(jié)果會(huì)導(dǎo)致子類同名成員變量被父類的值覆蓋
- Kryo不是線程安全的,要通過(guò)ThreadLocal或者創(chuàng)建Kryo線程池來(lái)保證線程安全,而Protostuff則是線程安全的
- Protostuff和Kryo序列化的格式有相似之處,都是利用一個(gè)標(biāo)記來(lái)記錄字段類型,因此序列化出來(lái)體積都比較小
小結(jié)
| 優(yōu)點(diǎn) | 缺點(diǎn) | |
|---|---|---|
| Kryo | 速度快,序列化后體積小 | 跨語(yǔ)言支持較復(fù)雜 |
| Hessian | 默認(rèn)支持跨語(yǔ)言 | 較慢 |
| Protostuff | 速度快,基于protobuf | 需靜態(tài)編譯 |
| Protostuff-Runtime | 無(wú)需靜態(tài)編譯,但序列化前需預(yù)先傳入schema | 不支持無(wú)默認(rèn)構(gòu)造函數(shù)的類,反序列化時(shí)需用戶自己初始化序列化后的對(duì)象,其只負(fù)責(zé)將該對(duì)象進(jìn)行賦值 |
| Java | 使用方便,可序列化所有類 | 速度慢,占空間 |
到此這篇關(guān)于Java Kryo,Protostuff,Hessian序列化方式對(duì)比的文章就介紹到這了,更多相關(guān)Java序列化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis-plus 自定義 Service Vo接口實(shí)現(xiàn)數(shù)據(jù)庫(kù)實(shí)體與 vo
這篇文章主要介紹了mybatis-plus 自定義 Service Vo接口實(shí)現(xiàn)數(shù)據(jù)庫(kù)實(shí)體與 vo 對(duì)象轉(zhuǎn)換返回功能,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08
Java AbstractMethodError原因案例詳解
這篇文章主要介紹了Java AbstractMethodError原因案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Java編程中的vector類用法學(xué)習(xí)筆記
Vector通常被用來(lái)實(shí)現(xiàn)動(dòng)態(tài)數(shù)組,即可實(shí)現(xiàn)自動(dòng)增長(zhǎng)的對(duì)象數(shù)組,和C++一樣vector類同樣被Java內(nèi)置,下面就來(lái)看一下vector類的基本用法.2016-05-05
idea在用Mybatis時(shí)xml文件sql不提示解決辦法(提示后背景顏色去除)
mybatis的xml文件配置的時(shí)候,有時(shí)候會(huì)沒(méi)有提示,這讓我們很頭疼,下面這篇文章主要給大家介紹了關(guān)于idea在用Mybatis時(shí)xml文件sql不提示的解決辦法,提示后背景顏色去除的相關(guān)資料,需要的朋友可以參考下2023-03-03
SpringCloud?Gateway之請(qǐng)求應(yīng)答日志打印方式
這篇文章主要介紹了SpringCloud?Gateway之請(qǐng)求應(yīng)答日志打印方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

