網(wǎng)站首頁 編程語言 正文
最近在項目當中遇到一個問題,就是使用Dubbo進行調(diào)用服務時,實體類中使用 jackson 的JsonNode進行數(shù)據(jù)傳送時,導致序列化失敗的問題。這里記錄一下Dubbo是如何進行自定義反序列化的。
1. 自定義序列化器
借鑒于Dubbo自帶JavaSerializer器,自己修改了源碼
public class CustomizeDataDeserializer extends AbstractMapDeserializer {
private static final Logger LOG = Logger.getLogger(CustomizeDataDeserializer.class.getName());
//序列化對象的類型
private final Class<?> type;
//實體類中字段所需要的類型
private final Map<String, FieldDeserializer> fieldMap;
//獲取實體類中readResolve方法
private final Method readResolve;
//獲取構(gòu)造函數(shù)
private Constructor constructor;
//獲取構(gòu)造函數(shù)的參數(shù)
private Object[] constructorArgs;
static final Map<String, Boolean> PRIMITIVE_TYPE = new HashMap<String, Boolean>(9) {
private static final long serialVersionUID = -3972519966755913466L;
{
put(Boolean.class.getName(), true);
put(Character.class.getName(), true);
put(Byte.class.getName(), true);
put(Short.class.getName(), true);
put(Integer.class.getName(), true);
put(Long.class.getName(), true);
put(Float.class.getName(), true);
put(Double.class.getName(), true);
put(Void.class.getName(), true);
}
};
public CustomizeDataDeserializer(Class<?> cl) {
type = cl;
//解析字段中所需要的反序列化器
this.fieldMap = new HashMap<>();
getFieldMap(cl, this.fieldMap);
//解析readReolve方法
readResolve = getReadResolve(cl);
if (readResolve != null) {
readResolve.setAccessible(true);
}
//獲取所有的構(gòu)造函數(shù)
Constructor[] constructors = cl.getDeclaredConstructors();
long bestCost = Long.MAX_VALUE;
for (int i = 0; i < constructors.length; i++) {
Class[] param = constructors[i].getParameterTypes();
long cost = 0;
for (int j = 0; j < param.length; j++) {
cost = 4 * cost;
if (Object.class.equals(param[j])) {
cost += 1;
} else if (String.class.equals(param[j])) {
cost += 2;
} else if (int.class.equals(param[j])) {
cost += 3;
} else if (long.class.equals(param[j])) {
cost += 4;
} else if (param[j].isPrimitive()) {
cost += 5;
} else {
cost += 6;
}
}
if (cost < 0 || cost > (1 << 48)) {
cost = 1 << 48;
}
cost += (long) param.length << 48;
if (cost < bestCost) {
constructor = constructors[i];
bestCost = cost;
}
}
//設(shè)置構(gòu)造函數(shù)的權(quán)限以及獲取參數(shù)
if (constructor != null) {
constructor.setAccessible(true);
Class[] params = constructor.getParameterTypes();
constructorArgs = new Object[params.length];
for (int i = 0; i < params.length; i++) {
constructorArgs[i] = getParamArg(params[i]);
}
}
}
protected static Object getParamArg(Class cl) {
if (!cl.isPrimitive()) {
return null;
} else if (boolean.class.equals(cl)) {
return Boolean.FALSE;
} else if (byte.class.equals(cl)) {
return new Byte((byte) 0);
} else if (short.class.equals(cl)) {
return new Short((short) 0);
} else if (char.class.equals(cl)) {
return new Character((char) 0);
} else if (int.class.equals(cl)) {
return Integer.valueOf(0);
} else if (long.class.equals(cl)) {
return Long.valueOf(0);
} else if (float.class.equals(cl)) {
return Float.valueOf(0);
} else if (double.class.equals(cl)) {
return Double.valueOf(0);
} else {
throw new UnsupportedOperationException();
}
}
static void logDeserializeError(Field field, Object obj, Object value,
Throwable e)
throws IOException {
String fieldName = (field.getDeclaringClass().getName()
+ "." + field.getName());
if (e instanceof HessianFieldException) {
throw (HessianFieldException) e;
} else if (e instanceof IOException) {
throw new HessianFieldException(fieldName + ": " + e.getMessage(), e);
}
if (value != null) {
throw new HessianFieldException(fieldName + ": " + value.getClass().getName() + " (" + value + ")"
+ " cannot be assigned to '" + field.getType().getName() + "'", e);
} else {
throw new HessianFieldException(fieldName + ": " + field.getType().getName() + " cannot be assigned from null", e);
}
}
@Override
public Class<?> getType() {
return type;
}
@Override
public Object readMap(AbstractHessianInput in)
throws IOException {
try {
//實例化對象出來
Object obj = instantiate();
return readMap(in, obj);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(type.getName() + ":" + e.getMessage(), e);
}
}
@Override
public Object readObject(AbstractHessianInput in, String[] fieldNames)
throws IOException {
try {
//實例化對象
Object obj = instantiate();
return readObject(in, obj, fieldNames);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(type.getName() + ":" + e.getMessage(), e);
}
}
protected Method getReadResolve(Class<?> cl) {
for (; cl != null; cl = cl.getSuperclass()) {
Method[] methods = cl.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getName().equals("readResolve") && method.getParameterTypes().length == 0) {
return method;
}
}
}
return null;
}
public Object readMap(AbstractHessianInput in, Object obj)
throws IOException {
try {
int ref = in.addRef(obj);
while (!in.isEnd()) {
Object key = in.readObject();
FieldDeserializer deser = fieldMap.get(key);
if (deser != null) {
deser.deserialize(in, obj);
} else {
in.readObject();
}
}
in.readMapEnd();
Object resolve = resolve(obj);
if (obj != resolve) {
in.setRef(ref, resolve);
}
return resolve;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
}
public Object readObject(AbstractHessianInput in,
Object obj,
String[] fieldNames)
throws IOException {
try {
//將實例化的對象添加到HessianInput引用中去
int ref = in.addRef(obj);
//編譯反序列化器,根據(jù)對應的數(shù)據(jù)類型取出
for (int i = 0; i < fieldNames.length; i++) {
String name = fieldNames[i];
FieldDeserializer deser = fieldMap.get(name);
//如果緩存的map中沒有找到反序列化器,直接到序列化工廠中查詢序列化器
if (deser != null) {
deser.deserialize(in, obj);
} else {
//序列化工廠中查詢反序列化器(還是當前類,并且調(diào)用readObject()方法)
in.readObject();
}
}
Object resolve = resolve(obj);
if (obj != resolve) {
in.setRef(ref, resolve);
}
return resolve;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOExceptionWrapper(obj.getClass().getName() + ":" + e, e);
}
}
private Object resolve(Object obj)
throws Exception {
try {
if (readResolve != null) {
return readResolve.invoke(obj, new Object[0]);
}
} catch (InvocationTargetException e) {
if (e.getTargetException() != null) {
throw e;
}
}
return obj;
}
protected Object instantiate()
throws Exception {
try {
if (constructor != null) {
//通過構(gòu)造函數(shù)創(chuàng)建實例對象
return constructor.newInstance(constructorArgs);
} else {
return type.newInstance();
}
} catch (Exception e) {
throw new HessianProtocolException("'" + type.getName() + "' could not be instantiated", e);
}
}
//讀取實體類中字段的類型,并且創(chuàng)建對應的解析器
protected void getFieldMap(Class<?> cl, Map<String, FieldDeserializer> fieldMap) {
for (; cl != null; cl = cl.getSuperclass()) {
Field[] fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) {
continue;
} else if (fieldMap.get(field.getName()) != null) {
continue;
}
try {
field.setAccessible(true);
} catch (Throwable e) {
LOG.warning("字段權(quán)限設(shè)置失敗");
}
Class<?> type = field.getType();
FieldDeserializer deser;
if (String.class.equals(type)) {
deser = new StringFieldDeserializer(field);
} else if (byte.class.equals(type)) {
deser = new ByteFieldDeserializer(field);
} else if (short.class.equals(type)) {
deser = new ShortFieldDeserializer(field);
} else if (int.class.equals(type)) {
deser = new IntFieldDeserializer(field);
} else if (long.class.equals(type)) {
deser = new LongFieldDeserializer(field);
} else if (float.class.equals(type)) {
deser = new FloatFieldDeserializer(field);
} else if (double.class.equals(type)) {
deser = new DoubleFieldDeserializer(field);
} else if (boolean.class.equals(type)) {
deser = new BooleanFieldDeserializer(field);
} else if (java.sql.Date.class.equals(type)) {
deser = new SqlDateFieldDeserializer(field);
} else if (java.sql.Timestamp.class.equals(type)) {
deser = new SqlTimestampFieldDeserializer(field);
} else if (java.sql.Time.class.equals(type)) {
deser = new SqlTimeFieldDeserializer(field);
} else if (Map.class.equals(type)
&& field.getGenericType() != field.getType()) {
deser = new ObjectMapFieldDeserializer(field);
} else if (List.class.equals(type)
&& field.getGenericType() != field.getType()) {
deser = new ObjectListFieldDeserializer(field);
} else if (Set.class.equals(type)
&& field.getGenericType() != field.getType()) {
deser = new ObjectSetFieldDeserializer(field);
} else if (JsonNode.class.equals(type)) {
//如果是JsonNode,使用對應的反序列化方式
deser = new JsonNodeDeserializer(field);
} else {
deser = new ObjectFieldDeserializer(field);
}
fieldMap.putIfAbsent(field.getName(), deser);
}
}
}
//抽象反序列化器
abstract static class FieldDeserializer {
abstract void deserialize(AbstractHessianInput in, Object obj)
throws IOException;
}
//對象反序列化器
static class ObjectFieldDeserializer extends FieldDeserializer {
private final Field field;
ObjectFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
Object value = null;
try {
value = in.readObject(field.getType());
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class BooleanFieldDeserializer extends FieldDeserializer {
private final Field field;
BooleanFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
boolean value = false;
try {
value = in.readBoolean();
field.setBoolean(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class ByteFieldDeserializer extends FieldDeserializer {
private final Field field;
ByteFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
int value = 0;
try {
value = in.readInt();
field.setByte(obj, (byte) value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class ShortFieldDeserializer extends FieldDeserializer {
private final Field field;
ShortFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
int value = 0;
try {
value = in.readInt();
field.setShort(obj, (short) value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class ObjectMapFieldDeserializer extends FieldDeserializer {
private final Field field;
ObjectMapFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
Object value = null;
try {
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
value = in.readObject(field.getType(),
isPrimitive(types[0]) ? (Class<?>) types[0] : null,
isPrimitive(types[1]) ? (Class<?>) types[1] : null
);
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class ObjectListFieldDeserializer extends FieldDeserializer {
private final Field field;
ObjectListFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
Object value = null;
try {
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
value = in.readObject(field.getType(),
isPrimitive(types[0]) ? (Class<?>) types[0] : null
);
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class ObjectSetFieldDeserializer extends FieldDeserializer {
private final Field field;
ObjectSetFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
Object value = null;
try {
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
value = in.readObject(field.getType(),
isPrimitive(types[0]) ? (Class<?>) types[0] : null
);
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class IntFieldDeserializer extends FieldDeserializer {
private final Field field;
IntFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
int value = 0;
try {
value = in.readInt();
field.setInt(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class LongFieldDeserializer extends FieldDeserializer {
private final Field field;
LongFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
long value = 0;
try {
value = in.readLong();
field.setLong(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class FloatFieldDeserializer extends FieldDeserializer {
private final Field field;
FloatFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
double value = 0;
try {
value = in.readDouble();
field.setFloat(obj, (float) value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class DoubleFieldDeserializer extends FieldDeserializer {
private final Field field;
DoubleFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
double value = 0;
try {
value = in.readDouble();
field.setDouble(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class StringFieldDeserializer extends FieldDeserializer {
private final Field field;
StringFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
String value = null;
try {
value = in.readString();
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class SqlDateFieldDeserializer extends FieldDeserializer {
private final Field field;
SqlDateFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
java.sql.Date value = null;
try {
java.util.Date date = (java.util.Date) in.readObject();
if (date != null) {
value = new java.sql.Date(date.getTime());
}
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class SqlTimestampFieldDeserializer extends FieldDeserializer {
private final Field field;
SqlTimestampFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
java.sql.Timestamp value = null;
try {
java.util.Date date = (java.util.Date) in.readObject();
if (date != null) {
value = new java.sql.Timestamp(date.getTime());
}
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
static class SqlTimeFieldDeserializer extends FieldDeserializer {
private final Field field;
SqlTimeFieldDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj)
throws IOException {
java.sql.Time value = null;
try {
java.util.Date date = (java.util.Date) in.readObject();
if (date != null) {
value = new java.sql.Time(date.getTime());
}
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
//JsonNode 數(shù)據(jù)反序列化方式
static class JsonNodeDeserializer extends FieldDeserializer {
private final Field field;
JsonNodeDeserializer(Field field) {
this.field = field;
}
@Override
void deserialize(AbstractHessianInput in, Object obj) throws IOException {
JsonNode value = null;
try {
//直接將數(shù)據(jù)讀取出來后強制轉(zhuǎn)換成JsonNode,因為Dubbo序列化時會把類型一起寫如到流中,讀取出來數(shù)據(jù)會轉(zhuǎn)換成指定的類型
value = (JsonNode) in.readObject();
field.set(obj, value);
} catch (Exception e) {
logDeserializeError(field, obj, value, e);
}
}
}
private static boolean isPrimitive(Type type) {
try {
if (type != null) {
if (type instanceof Class<?>) {
Class<?> clazz = (Class<?>) type;
return clazz.isPrimitive() || PRIMITIVE_TYPE.containsKey(clazz.getName());
}
}
} catch (Exception e) {
// ignore exception
}
return false;
}
}
2. 創(chuàng)建序列化工廠
public class DubboSerializerFactory extends SerializerFactory {
/** SERIALIZER_FACTORY */
public static final SerializerFactory SERIALIZER_FACTORY = new DubboSerializerFactory();
/**
* Fkh dubbo serializer factory
*
* @since 1.3.0
*/
public DubboSerializerFactory() {
}
/**
* Gets default deserializer *
*
* @param cl cl
* @return the default deserializer
* @since 1.3.0
*/
@Override
public Deserializer getDefaultDeserializer(Class cl) {
return new CustomizeDataDeserializer(cl);
}
}
3. 創(chuàng)建序列化對象輸入流
public class DubboSeiralizerObjectInput implements ObjectInput {
/** H 2 i */
private final Hessian2Input h2i;
/**
* Fkh dubbo seiralizer object input
*
* @param is is
* @since 1.3.0
*/
public DubboSeiralizerObjectInput(InputStream is) {
h2i = new Hessian2Input(is);
//設(shè)置反序列化工廠
h2i.setSerializerFactory(DubboSerializerFactory.SERIALIZER_FACTORY);
}
/**
* Read bool
*
* @return the boolean
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public boolean readBool() throws IOException {
return this.h2i.readBoolean();
}
/**
* Read byte
*
* @return the byte
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public byte readByte() throws IOException {
return (byte) h2i.readInt();
}
/**
* Read short
*
* @return the short
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public short readShort() throws IOException {
return (short) h2i.readInt();
}
/**
* Read int
*
* @return the int
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public int readInt() throws IOException {
return h2i.readInt();
}
/**
* Read long
*
* @return the long
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public long readLong() throws IOException {
return h2i.readLong();
}
/**
* Read float
*
* @return the float
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public float readFloat() throws IOException {
return (float) h2i.readDouble();
}
/**
* Read double
*
* @return the double
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public double readDouble() throws IOException {
return h2i.readDouble();
}
/**
* Read bytes
*
* @return the byte [ ]
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public byte[] readBytes() throws IOException {
return h2i.readBytes();
}
/**
* Read utf
*
* @return the string
* @throws IOException io exception
* @since 1.3.0
*/
@Override
@SuppressWarnings({"checkstyle:LowerCamelCaseVariableNamingRule",
"PMD.LowerCamelCaseVariableNamingRule"})
public String readUTF() throws IOException {
return h2i.readString();
}
/**
* Read object
*
* @return the object
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public Object readObject() throws IOException {
return h2i.readObject();
}
/**
* Read object
*
* @param <T> parameter
* @param cls cls
* @return the t
* @throws IOException io exception
* @since 1.3.0
*/
@Override
@SuppressWarnings("unchecked")
public <T> T readObject(Class<T> cls) throws IOException {
return (T) h2i.readObject(cls);
}
/**
* Read object
*
* @param <T> parameter
* @param cls cls
* @param type type
* @return the t
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public <T> T readObject(Class<T> cls, Type type) throws IOException {
return readObject(cls);
}
}
4. 自定義協(xié)議序列化器
這里只實現(xiàn)了反序列化方式,并沒有自定義序列化方式
public class CustomizeDataSerialization implements Serialization {
/**
* 每個序列化器都有一個自己的編號
*
* @return the content type id
* @since 1.3.0
*/
@Override
public byte getContentTypeId() {
return 26;
}
/**
* Gets content type *
*
* @return the content type
* @since 1.3.0
*/
@Override
public String getContentType() {
return "x-application/customize";
}
/**
* 使用hessian的序列化方式,暫時不需要自定義
*
* @param url url
* @param output output
* @return the object output
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public ObjectOutput serialize(URL url, OutputStream output) throws IOException {
return new Hessian2ObjectOutput(output);
}
/**
* Deserialize
*
* @param url url
* @param input input
* @return the object input
* @throws IOException io exception
* @since 1.3.0
*/
@Override
public ObjectInput deserialize(URL url, InputStream input) throws IOException {
return new DubboSeiralizerObjectInput(input);
}
}
5. 創(chuàng)建Spi文件
在resources文件下創(chuàng)建路徑:
內(nèi)容:序列化名稱+類路徑
customize=com.xxx.client.dubbo.serialization.CustomizeDataSerialization
配置文件中引用就可以了
server:
port: 18104
dubbo:
protocol:
port: 28033
provider:
serialization: customize #自定義序列化方式
原文鏈接:https://blog.csdn.net/weixin_43915643/article/details/122430346
相關(guān)推薦
- 2022-08-04 Go?slice切片make生成append追加copy復制示例_Golang
- 2022-11-14 Go語言Goroutinue和管道效率詳解_Golang
- 2022-06-29 Python容器類型轉(zhuǎn)換的3種方法實例_python
- 2022-06-24 windows服務器修改遠程登錄的端口以及防火墻配置_win服務器
- 2023-02-06 C語言實現(xiàn)文件讀寫功能流程_C 語言
- 2022-07-19 Swagger導出html或者PDF
- 2022-08-17 yolov5中anchors設(shè)置實例詳解_python
- 2022-08-07 C#實現(xiàn)關(guān)機功能_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支