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

标题照着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

相关推荐
电子科技圈4 分钟前
为AR眼镜等多种智能可穿戴设备添加穿戴状态检测功能
经验分享·后端·嵌入式硬件·ar·restful·智能硬件·智能手表
白总Server6 分钟前
Web 架构之 GraphQL 与 REST API 对比与选型
java·大数据·前端·javascript·后端·架构·graphql
随想笔记11 分钟前
Continue索引构建总结
javascript·后端·ai编程
天天摸鱼的java工程师18 分钟前
Quarkus:轻量级 Java 的未来?
后端
汪子熙20 分钟前
HTTP 通信中 TCP 本地地址与远程地址的区别与角色
后端·架构
这里有鱼汤25 分钟前
Python小白入门量化交易:用机器学习预测股票涨跌(实操教学)
后端·机器学习
MikeWe30 分钟前
一文读懂Python re正则表达式模块
后端
MikeWe30 分钟前
Python多线程全面解析,从使用到GIL的底层逻辑
后端
七七&55635 分钟前
Spring Boot 常用注解整理
java·spring boot·后端