在开源项目中看到一个判断对象 序列化类型方法,现在它是你的了。

标题照着why哥 copy一份,哈哈,希望大佬来点好运气

你好呀,我是雨夜。

在 fury 的官网中看到一个 '判断是不是jdk序列化' 的方法。

看明白之后,我觉得还是有点意思的,结合自己的理解和代码,加上画几张图,给你拆解一下 fury 里面的"判断序列化类型"。

虽然是在 fury 里面看到的,但是本篇文章的内容和 fury 框架没有太多关系,反而和基础知识有关。

所以,即使你不了解 fury 框架,也不影响你阅读。

当你理解了这个类的工作原理之后,你完全可以把这个只有 10 多行的类搬运到你的项目里面,然后就变成你的了。

你懂我意思吧。

jdk 序列化

怎么判断的

vbnet 复制代码
public static short getShortB(byte[] b, int off) {
  return (short) ((b[off + 1] & 0xFF) + (b[off] << 8));
}

里面的具体 带入值为 (short) ((-19 & 0xFF) + (-84 << 8));

(short) ((237) + (-21504));

(short)-21267

然后和 0xaced 进行判断 是否相等,相等的话就认为 是 jdk序列化

jdk 序列化代码

ini 复制代码
public ObjectOutputStream(OutputStream out) throws IOException {
   verifySubclass();
   bout = new BlockDataOutputStream(out);
   handles = new HandleTable(10, (float) 3.00);
   subs = new ReplaceTable(10, (float) 3.00);
   enableOverride = false;
   writeStreamHeader();
   bout.setBlockDataMode(true);
   if (extendedDebugInfo) {
       debugInfoStack = new DebugTraceInfoStack();
   } else {
       debugInfoStack = null;
   }
}
csharp 复制代码
protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}
arduino 复制代码
final static short STREAM_MAGIC = (short)0xaced;

往里面先写了一个 0xaced 作为标识,就是根据这个字段 判断是不是jdk序列化

为什么(short) ((-19 & 0xFF) + (-84 << 8)); 就能取出0xaced呢?

0xaced 十进制代表44269

进制转换结果为:1010001110101000

首先我们看下 jdk序列化的方法

java 复制代码
public void writeShort(int v) throws IOException {
    if (pos + 2 <= MAX_BLOCK_SIZE) {
        Bits.putShort(buf, pos, (short) v);
        pos += 2;
    } else {
        dout.writeShort(v);
    }
}

v是 -21267,pos = 0,MAX_BLOCK_SIZE = 1024

scss 复制代码
static void putShort(byte[] b, int off, short val) {
    b[off + 1] = (byte) (val      );
    b[off    ] = (byte) (val >>> 8);
}

带入值

scss 复制代码
static void putShort(byte[] b, int off, short val) {
    b[1] = (byte) (-21267      );
    b[0] = (byte) (-21267 >>> 8);
}

所以我们取 b[1]就是 0xaced

0xaced是序列化的头信息字段。在Java的序列化机制中,当一个对象被序列化时,会先写入一个表示序列化信息的头,其中包含了魔术数字(magic number)和版本号。0xaced就是表示序列化的魔术数字,它表示序列化的格式和版本号。

停一下 ,你看到0xaced 还能想起什么?

停个几秒 想下


常用的BIOS中断有这些:

1、向屏幕输出:int 0x10

2、读写软盘:int 0x13

3、系统调用:int 0x80

性能比较

我第一次测试的结果

问了下大佬

但是后面具体看的时候,发现不是这块的事

这块我用的是 arthas

结果你告诉我这

执行过一次 方法之后 就可以了,因为里面用了 JIT 生成了一些类

复制代码
fury 序列化消耗的时间 353
jdk 序列化消耗的时间 69
fury 序列化消耗的时间 3
jdk 序列化消耗的时间 23
fury 序列化消耗的时间 5
jdk 序列化消耗的时间 22

这个时候 已经卡住了,要想别的办法 确定是哪部分耗时了

我就把源码下载下来,直接添加了一个java agent 直接打印每一行的耗时

确定

ini 复制代码
byte[] bytes = fury.serialize(object);

耗时比较多,它第一次执行消耗了100ms

scss 复制代码
public Class<? extends Serializer> getSerializerClass(Class<?> cls) {
  boolean codegen =
      supportCodegenForJavaSerialization(cls) && fury.getConfig().isCodeGenEnabled();
  return getSerializerClass(cls, codegen);
}
bash 复制代码
io.fury.resolver.ClassResolver#addSerializer

真实JIT 生成代码的代码

swift 复制代码
public static <T> Class<Serializer<T>> loadCodegenSerializer(Fury fury, Class<T> cls) {
  try {
    return (Class<Serializer<T>>) CodecUtils.loadOrGenObjectCodecClass(cls, fury);
  } catch (Exception e) {
    String msg = String.format("Create sequential serializer failed, \nclass: %s", cls);
    throw new RuntimeException(msg, e);
  }
}

JIT 生成的class 名字怎么获取的

scss 复制代码
public String codecClassName(Class<?> beanClass) {
  String name = ReflectionUtils.getClassNameWithoutPackage(beanClass).replace("$", "_");
  StringBuilder nameBuilder = new StringBuilder(name);
  if (fury.trackingRef()) {
    // Generated classes are different when referenceTracking is switched.
    // So we need to use a different name.
    nameBuilder.append("FuryRef");
  } else {
    nameBuilder.append("Fury");
  }
  nameBuilder.append(codecSuffix()).append("Codec");
  nameBuilder.append('_').append(fury.getConfig().getConfigHash());
  String classUniqueId = CodeGenerator.getClassUniqueId(beanClass);
  if (StringUtils.isNotBlank(classUniqueId)) {
    nameBuilder.append('_').append(classUniqueId);
  }
  return nameBuilder.toString();
}

他是怎么让uuid 不重复的呢? 看TODO 他们 现在没有根据 configHash 区别,而是依靠 classUniqueId 字段保证的唯一,下面是一个例子

SomeClassFuryCodec_97705842_414493378_242131142

生成之后的 代码

ini 复制代码
package io.fury.test;

import java.util.List;
import java.util.Map;
import java.util.Set;
import io.fury.Fury;
import io.fury.memory.MemoryBuffer;
import io.fury.resolver.NoRefResolver;
import io.fury.resolver.ClassInfo;
import io.fury.resolver.ClassInfoCache;
import io.fury.resolver.ClassResolver;
import io.fury.builder.Generated;
import io.fury.serializer.CodegenSerializer.LazyInitBeanSerializer;
import io.fury.serializer.Serializers.EnumSerializer;
import io.fury.serializer.Serializer;
import io.fury.serializer.StringSerializer;
import io.fury.serializer.ObjectSerializer;
import io.fury.serializer.CompatibleSerializer;
import io.fury.serializer.CollectionSerializers.CollectionSerializer;
import io.fury.serializer.MapSerializers.MapSerializer;
import io.fury.builder.Generated.GeneratedObjectSerializer;

public final class SomeClassFuryCodec_97705842_414493378_242131142 extends GeneratedObjectSerializer {

  private final NoRefResolver refResolver;
  private final ClassResolver classResolver;
  private final StringSerializer strSerializer;
  private final Fury fury;

  public SomeClassFuryCodec_97705842_414493378_242131142(Fury fury, Class classType) {
      super(fury, classType);
      this.fury = fury;
      fury.getClassResolver().setSerializerIfAbsent(classType, this);
  
      io.fury.resolver.RefResolver refResolver0 = fury.getRefResolver();
      refResolver = ((NoRefResolver)refResolver0);
      classResolver = fury.getClassResolver();
      strSerializer = fury.getStringSerializer();
  }

  @Override public final void write(MemoryBuffer buffer, Object obj) {
      io.fury.test.SomeClass someClass20 = (io.fury.test.SomeClass)obj;
      buffer.writeInt(-1300032068);
      Object object1 = io.fury.util.Platform.getObject(someClass20, 12L);
      String name = (String)object1;
      if ((name == null)) {
          buffer.writeByte(((byte)-3));
      } else {
          buffer.writeByte(((byte)0));
          strSerializer.writeJava8StringCompressed(buffer, name);
      }
  }

  @Override public final Object read(MemoryBuffer buffer) {
      ObjectSerializer.checkClassVersion(fury, buffer.readInt(), -1300032068);
      io.fury.test.SomeClass someClass3 = new io.fury.test.SomeClass();
      refResolver.reference(someClass3);
      if ((buffer.readByte() != ((byte)-3))) {
          io.fury.util.Platform.putObject(someClass3, 12L, strSerializer.readJava8CompressedString(buffer));
      } else {
          io.fury.util.Platform.putObject(someClass3, 12L, null);
      }
      return someClass3;
  }

}

使用的时候 肯定是 把beanClass 和 生成的新class 进行map 关联,然后序列化之前 看下map 是否存在,如果存在就用新的

小配置

如果你想关闭 代码生成的开关怎么办?

scss 复制代码
Fury fury = Fury.builder().withLanguage(Language.JAVA)
    .withCodegen(false)
    // .withSecureMode(false)
    .build();

其中 .withCodegen(false) true为生成代码,false是不生成,默认 true

这个时候 你可能发现withCodegen 赋值的是Fury.class 类中的属性,而最后判断的时候 是

scss 复制代码
fury.getConfig().isCodeGenEnabled()
scss 复制代码
Config(Fury.FuryBuilder builder) {
  // 省略
  codeGenEnabled = builder.codeGenEnabled;
  // 省略
}

就是为了代码 结构的清晰

如果想判断类是不是 非静态内部类

typescript 复制代码
public static boolean supportCodegenForJavaSerialization(Class<?> cls) {
  // bean class can be static nested class, but can't be a non-static inner class
  return cls.getEnclosingClass() == null || Modifier.isStatic(cls.getModifiers());
}

判断class 是不是jdk代理的对象

typescript 复制代码
public static boolean isJdkProxy(Class<?> clz) {
  return Proxy.isProxyClass(clz);
}

包名 java 开头的,不知道为什么报错

less 复制代码
if (fury.getConfig().checkJdkClassSerializable()) {
  if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))) {
    throw new UnsupportedOperationException(
        String.format("Class %s doesn't support serialization.", cls));
  }
}

如果是A.isAssignableFrom(B) 确定一个类(B)是不是继承来自于另一个父类(A),一个接口(A)是不是实现了另外一个接口(B),或者两个类相同。主要,这里比较的维度不是实例对象,而是类本身,因为这个方法本身就是Class类的方法,判断的肯定是和类信息相关的。

获取当前jdk 版本

ini 复制代码
String property = System.getProperty("java.specification.version");

序列化的原理

io.fury.resolver.ClassResolver#createSerializer 方法是具体方法

彩蛋

我当时买的房子 群里有人2023年3月收到房子就装修,然后租了几个月,还要继续往外租

这种房子一定不要租,都是甲醛,他们从来不住,就为了多赚点房租,也不告诉人家

这里有的人可能要说了,'谁让你不问了,你问了我就告诉你了',靠,租房子,没有行情价么,还让人家问,人家刚看你房子一次,就知道有没有什么问题,你怎么不上天呢?

今天看到就来气,我往外租房子都是先告诉人家 问题在哪,能接受就接受,接受不了就算了,哈哈,但是我这个人不接受砍价,就是实实在在的,别搞事,我们先把不好的地方都说了,后面的沟通就好多了。

你告诉我,新房子采用好材料,可以没有甲醛的,我不否认,但是我不认为他会用好的材料,对别人好点,就是对自己好点

这也不是聪明人不聪明人,租房经验丰富的事,算了,做好自己

1

相关推荐
devlei4 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑5 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3566 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3566 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁6 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp6 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴8 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友8 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒9 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan10 小时前
Go 内存回收-GC 源码1-触发与阶段
后端