从设计者的角度出发理解源码--FastJson

从设计者的角度出发理解源码--FastJson

引言

本篇,作为《从..理解源码》的第二篇,延续前一篇的思路,笔者视图尝试从作者的角度触发分析作者的思路历程,和上篇相比,FastJson的知识可能略多。这篇文章,对fastjson只能算是浅尝即止,我认为最厉害的未过于,作者的持续习惯,包括但不限于:

  • 基于Benchmark搜集高性能代码的习惯

  • 阅读原始文档获得第一手资料,对于JDK各种版本特性了然于胸。

  • 喜欢探索代码优雅的编码方式。

  • 产品思维:先发现问题,先用简单的方式实现,后面在迭代更好的方案。

  • 自我高要求:把自己的代码拉出来被鞭尸,虚心接受建议,持续迭代,即便有其他框架在开发,即便自己已经是P9。

这个框架的阅读过程中,感受到作者的拼劲全力,感受到:一个极客,孜孜不倦,为当年立下的Flag,将自己空闲时间,都投入到少年时的一个梦:极致的性能

写本篇文章时,由于fastjson2已发布,本篇文章基于fastjson2

为什么要写FastJson

哎,以往的框架,Jakson,Goson,最大的问题其实是使用繁琐,和在不同环境下,兼容性不足

我需要给别人一个足够的理由,做俩个选择

  1. 选择放弃原本的实用。
  2. 选择使用我的东西。

有了

  • 以性能为突破口

    我从性能上出发,应该是一个比较好的突破口,因为一个逻辑一旦涉及到序列化操作,该序列化操作的频次应该不会低。

    如果我可以真正做到序列化性能有比较大的提升,对他们来说很有吸引力。

  • 上手体验为突破口:

    1.使用API设计均使用静态方法思路,减少编码步骤,

    2.提供充足的注解类工具,使用注解天生体验好的特性。

    框架 写法 上手代码量
    Fastjson String mapString = JSON.toJSONString(map); 1 行
    Jackson ObjectMapper mapper = new ObjectMapper();<br />String beanString = mapper.toJson(map); 2 行
    Gson Gson gson = new Gson();<br />String beanString = gson.toJson(map); 2行

    FastJSON提供的注解如下:

    // 字段注解
    com.alibaba.fastjson2.annotation.JSONField
    com.alibaba.fastjson2.annotation.JSONType
    // 
    com.alibaba.fastjson2.annotation.JSONBuilder
    com.alibaba.fastjson2.annotation.JSONCompiled
    com.alibaba.fastjson2.annotation.JSONCompiler
    
  • 先在公司内部(阿里)获得一定的认可度,后面推广可以借助公司的力量。

其实,做框架设计起初和写PPT有点类似,你得先有一个能引人入胜的思路,最好能有个最简陋的版本,先将人吸引过来,然后你逐步去完善它,持之以恒的迭代,再产生新的思路,再迭代。

哎,光能证明你性能好也不行呀,稳定性怎么保证,市面上相似的太多了。

有了

  • 持续关注issues及时修复,把每个issues的修复单元测试及时提交修改完成之后,让他们随意查看,逐步建立起信任。
  • 保持初心,持之以恒,一个框架能被大面积使用,不在于它开始bug的多少,而在于立意远大,且有人维护。
  • 这个东西如果做好了,是能反向推动我成长的,因为它的适用范围足够广(安卓的,Web,大数据等等)

序列化设计(Bean->Json)

序列化流程,如果再细分的话,有如下流程:

  1. 获取所有需要提取的属性列表。

  2. 依次遍历属性列表获取属性,向一段JSON容器里面放置key,val。

  3. 在放置key,val的时候,可能涉及某一些转义符的处理,json结构的处理等等。

不同场景的优化

哎,我如何基于不同情况做出不同的处理方式呢,这样我可以针对每一种情况去做具体的优化

有了

  • 我可以使用模板方法写一个ObjectWriter抽象类完成主逻辑。
  • 将已知情况细分清楚,基于不同情况,各自情况完成各自的实现。
// ObjectWriter抽象类,以及部分具体实现
com.alibaba.fastjson2.writer.ObjectWriter
com.alibaba.fastjson2.util.JodaSupport.LocalDateTimeWriter
com.alibaba.fastjson2.util.GuavaSupport.AsMapWriter
....

哎,总不能把所有情况都穷举出来,写出各自的实现,这种优化思路还是不太灵活。

有了:Bean转JSON,本质上,就是从一个Bean里面取内容,然后往一个JSON里面塞么。

我可以为每一个Bean的,读写逻辑,分别依据各自的情况生成相应的字节码读写器。

  • 依据每一个Bean的情况去做更具体的优化。
  • 后期扩展空间也大(毕竟是动态生成的代码)。
  • 需要通过一个抽象类来约束核心逻辑,使用ASM框架运行时编写Class。
// 如下摘自:com.alibaba.fastjson2.JSON#toJSONString(java.lang.Object),部分逻辑省略

//1:获取ObjectWriter
com.alibaba.fastjson2.writer.ObjectWriterProvider#getObjectWriter
 //1.1:使用 ASM 来创建 ObjectWriter 对象   
 1.1 creator = ObjectWriterCreatorASM.INSTANCE; // 
 //1.2:通过字节码编写代码,继承ObjectWriterAdapter,创建Class。 
 1.2 com.alibaba.fastjson2.reader.ObjectWriterCreatorASM#createObjectWriter

//2:完成读序列化   
objectWriter.write(writer, object, null, null, 0); 
//3:返回
return writer.toString();

ASM生成的代码

如下我列出了FastJSON生成的ObjectReaderAdapter:

不能使用ASM的环境

哎,可能部分使用者的场景不能使用字节码,可能由于环境问题,比如Android,GraalVM环境等,或者出于安全上面考量,使用者主观不想用字节码实现

有了:

  • 支持让使用者指定使用的实现方式。
  • 默认使用ASM字节码实现,如果发现是安卓等不能够使用的环境,自动使用反射实现,且支持手动指定。
// 核心源码如下: 1.优先支持手动指定方式,2.如果不指定,默认使用ASM方式,3.如果当前环境不支持ASM,则自动转为MH,或者反射。
switch (JSONFactory.CREATOR) { 
  case "reflect":
  case "lambda":
    creator = ObjectWriterCreator.INSTANCE; 
    break;
  case "asm":
  default:
    try {
      if (!JDKUtils.ANDROID && !JDKUtils.GRAAL) { 
        creator = ObjectWriterCreatorASM.INSTANCE; 
      }
    } catch (Throwable ignored) {}
    if (creator == null) { 
      // 如果 creator 仍然为 null,则使用反射或 Lambda 表达式来创建 ObjectWriter 对象
      creator = ObjectWriterCreator.INSTANCE; 
    }
    break;
}

哎,反射好像性能不太好,我如何在这个基础之上优化一点呢?

有了:

  • 我可以使用:Lambda表达式+MethodHandler,来替代反射获取属性的调用。
  • JDK自带了很多FunctionInterface接口比如Customer ,这样我不仅可以提升性能,还可以在调用处统一写法。

invokedynamic指令与MethodHandle的功能简单说:就是之前的动态转发的逻辑都是在字节码层面去完成的,某个方法调用,转发给哪个具体实现类去执行,都是字节码自己安排好了,基于一段固定逻辑自己走下来

而invokedynamic指令与MethodHandle则可以将“转发给谁”,这个谁,在应用代码层面去指定。

Lambda表达式主要就是基于如下几点

1.通过ASM字节码技术,生成最轻量代码(比如static方法)

2.基于指令invokedynamic与MethodHandle特性实现的,将转发能力赋予了业务代码。

// P1:创建set函数的Lambda对象
BiConsumer function = (BiConsumer) lambdaSetter(objectClass, fieldClass, method); 
return createFieldReader(objectClass, objectType, fieldName, fieldType, fieldClass, ordinal, features, format, locale, defaultValue, jsonSchema, method, function, initReader);

// P2:将set函数的Lambda对象放到缓存fieldReaders中
putIfAbsent(fieldReaders, fieldName, fieldReader, objectClass);

// P3:使用function读取值
public void readFieldValue(JSONReader jsonReader, T object) {  
   function.accept(object, (V) fieldValue);
}

知识补习:

安全性方面

哎,如何防止JSON注入呢?毕竟用户输入什么我又不可控制?

有了:

  • Json-schema不就就是干这个的,我可以用Json-schema约束输入值,防止被注入。

    Json-schema是一个特殊的JSON,用来约束JSON的数据结构。

  • 使用模板方法设计模式,实现一个基础模板类,基于基础类型(int,long,string,object)来实现具体的验证逻辑(用户通过注解的形式作用在某个具体字段上)

    -- 基础模板

    com.alibaba.fastjson2.schema.JSONSchema

    -- 核心方法(字段验证)

    com.alibaba.fastjson2.schema.JSONSchema#validate

    -- 具体类型实现

    com.alibaba.fastjson2.schema.BooleanSchema

    com.alibaba.fastjson2.schema.ObjectSchema

    -- 组合规则

    com.alibaba.fastjson2.schema.AnyOf

    com.alibaba.fastjson2.schema.AllOf

  • 其中,object,组合类(AnyOf,AllOf)方法可能有一些特殊实现,因为object类型需要考虑递归的情况。组合类的需要考虑,多条规则之间的验证关系。

知识补习:

哎,自己通过字节码生成Class,比如如果生成的Class里面有while循环的行为呢?安全性方面如何保证呢?

有了:

  • 使用一个自定义的ClassLoader加载动态生成的Class。
  • 对自定义的ClassLoader做安全配置,限制代码行为。
//P1 DynamicClassLoader
// ProtectionDomain表示代码源和权限的集合,里面封装了代码源(即代码从哪里来,来源是哪里)和权限(即代码有什么权限,能干嘛)的信息。
public class DynamicClassLoader extends ClassLoader {
    private static final DynamicClassLoader instance = new DynamicClassLoader();
    private static final java.security.ProtectionDomain DOMAIN;
    static {
      DOMAIN = (java.security.ProtectionDomain) java.security.AccessController.doPrivileged(
        (PrivilegedAction<Object>) DynamicClassLoader.class::getProtectionDomain
      );
    }
}

//P2 ObjectReaderCreatorASM
//使用DynamicClassLoader,初始化ObjectReaderCreatorASM的classLoader属性。
public ObjectReaderCreatorASM(ClassLoader classLoader) {
    this.classLoader = classLoader instanceof DynamicClassLoader ? (DynamicClassLoader) classLoader : new DynamicClassLoader(classLoader);
  }

//P3 ObjectReaderCreatorASM.jitObjectReader
//调用classLoader的defineClassPublic方法,将code中的数据定义为一个新的类,并返回这个类的Class对象,赋值给readerClass。
byte[] code = cw.toByteArray();
Class<?> readerClass = classLoader.defineClassPublic(classNameFull, code, 0, code.length);

//P4 com.alibaba.fastjson2.util.DynamicClassLoader#defineClassPublic
// 定义类时候使用DOMAIN作为限制。
public Class<?> defineClassPublic(String name, byte[] b, int off, int len) throws ClassFormatError {
  return defineClass(name, b, off, len, DOMAIN);
}

知识补习:

属性读写顺序

哎,读取和写入Key的顺序如何保证呢?先写哪些属性,后写哪些属性?如何将性能最大化?

有了:我可以在生成字节码阶段做一些事情

  • 在生成字节码阶段,获取JavaBean所有属性列表,且将这些属性按照Hash值排序,ListA。

  • 在生成字节码阶段,遍历ListA属性,生成字节码写入逻辑,这样我的代码里从上至下的编码顺序,即是正确的读取写入顺序。

  • 在执行字节码类阶段,通过一个全局作用域的Byte数组,来维护JSON信息,不断在Byte数组后追加信息。

  • 在执行字节码类阶段,使用Unsafe类操作内存设置值,避免频繁安全检查。

// P1 com.alibaba.fastjson2.writer.ObjectWriterCreatorASM#createObjectWriter
    // 1.遍历JavaBean的属性,获取fieldWriters
  BeanUtils.declaredFields(objectClass, field -> { 
    FieldWriter fieldWriter = creteFieldWriter(objectClass, writerFieldFeatures, provider, beanInfo, fieldInfo, field); 
    ...
    fieldWriterMap.put(fieldWriter.fieldName, fieldWriter);
  }); 
  fieldWriters = new ArrayList<>(fieldWriterMap.values()); 

// P2 com.alibaba.fastjson2.writer.ObjectWriterCreatorASM#genMethodWrite
  // 2.遍历fieldWriters,按先后次序生成字节码
  for (int i = 0; i < fieldWriters.size(); i++) {
    FieldWriter fieldWriter = fieldWriters.get(i);
    gwFieldValue(mwc, fieldWriter, OBJECT, i);
  }

// P3 https://note.youdao.com/s/W9gi9fRD,(生成的WriterAdapter字节码)
  // 3.如下为生成的字节码的编码顺序,
  if ((var14 = ((ExcelTransformReq)var2).getColumn1()) != null)
  ...  
  if ((var14 = ((ExcelTransformReq)var2).getColumn10()) != null) 
  
// P4 设置byte数值
  // 4.写入数据
  com.wang.track.analysis.OWG_1_25_ExcelTransformReq#write  
    // 4.2 具体filed写入,其中JSONWriter里面维护全局作用域,以及全局Byte[]
    com.alibaba.fastjson2.writer.FieldWriter#writeFieldName
        com.alibaba.fastjson2.JSONWriter#writeName 
        // 4.2.1 使用unsafe直接操作数组
        UNSAFE.putLong(bytes, ARRAY_BYTE_BASE_OFFSET + off, name);

知识补习:

多种编码格式的适配

哎,字符串的编码格式种类,我如何适配到多种编码格式呢?

有了:其实不同编码格式的区别无非是,存储容器,拼接与位移策略,在不同的情况下的不同。

  • 定义抽象一个类叫JSONWriter,这个类里面不做具体实现,只做方法的定义,与核心解析逻辑编写。(模板方法设计模式)
  • 继承JSONWriter实现多种编码格式的实现类,比如:JSONWriterJSONB,JSONWriterUTF16,JSONWriterUTF8。
  • 生成JSON时,根据情况选中具体JSONWriter实现。
  • 将JSONWriter与WriterAdapter,使用桥接模式进行组装执行。(桥接设计模式)
// 如下列出了JSONWriter,已经它的具体实现
com.alibaba.fastjson2.JSONWriter
com.alibaba.fastjson2.JSONWriterUTF16
com.alibaba.fastjson2.JSONWriterUTF8
com.alibaba.fastjson2.JSONWriterJSONB  

// 根据配置项,或者环境情况,初始化对应的实现。
// com.alibaba.fastjson2.JSONWriter#of
...  
if (FIELD_STRING_VALUE != null && !ANDROID && !OPENJ9) {
  jsonWriter = new JSONWriterUTF16JDK8UF(context);
} else {
  jsonWriter = new JSONWriterUTF16JDK8(context);
}  
...

// 将JSONWriter作为入参,传入WriterAdapter,使用桥接的思路。
com.wang.track.analysis.OWG_1_25_ExcelTransformReq#write(JSONWriter var1, Object var2, Object var3, Type var4, long var5)
this.fieldWriter0.writeFieldName(var1);  

知识补习:

哎,复杂对象(一个class里有另一个class),该如何处理?

有了:

  • 我可以使用懒加载的思路,在读取每个属性值的时候,先判断一下该属性类型,是否是复杂类型,如果是,则递归使用创建复杂类型的Reader,思路和如上类似。

    // com.wang.track.analysis.ORG_1_25_ExcelTransformReq
    // ObjectReader 类里封装了递归生成reader的逻辑
    public FieldReader fieldReader0;
    public ObjectReader objectReader0;
    
    // com.alibaba.fastjson2.reader.ObjectReader1#readJSONBObject
    
    long hashCode = jsonReader.readFieldNameHashCode();
    if (hashCode == getTypeKeyHash() && i == 0) {
      long typeHash = jsonReader.readTypeHashCode();
      JSONReader.Context context = jsonReader.getContext();
      ObjectReader autoTypeObjectReader = autoType(context, typeHash);
    }
    
  • 使用一个容器(map结构),将本次流程中所创建的reader,使用,classA-xxxReader,的形式缓存。

    
    // com.alibaba.fastjson2.reader.ObjectReaderProvider
    // 如下是各种查询的缓存Map
    final ConcurrentMap<Type, ObjectReader> cache = new ConcurrentHashMap<>();
    final ConcurrentMap<Type, ObjectReader> cacheFieldBased = new ConcurrentHashMap<>();
    final ConcurrentMap<Integer, ConcurrentHashMap<Long, ObjectReader>> tclHashCaches = new ConcurrentHashMap<>();
    final ConcurrentMap<Long, ObjectReader> hashCache = new ConcurrentHashMap<>();
    final ConcurrentMap<Class, Class> mixInCache = new ConcurrentHashMap<>();
    

代码结构图

如下是最终的架构图:

[图片上传失败...(image-dc35c6-1707987987703)]

1.入口

JSON.toJSONString(testBean);

2.初始化ObjectWriterProvider(默认ASM实现),全局上下文(context)

final ObjectWriterProvider provider = defaultObjectWriterProvider;
final JSONWriter.Context context = new JSONWriter.Context(provider);

3.初始化JSONWriter(判断当前所处环境,以及参数,自动选择合适的JSONWriter)

JSONWriter writer = JSONWriter.of(context)

3.初始化getObjectWriter

// 初始化getObjectWriter
ObjectWriter<?> objectWriter = provider.getObjectWriter(valueClass,valueClass,(defaultWriterFeatures & JSONWriter.Feature.FieldBased.mask) != 0); 

4.解析

// 此时objectWriter默认的类型为....OWG_1_25_ExcelTransformReq,为ASM生成的class
objectWriter.write(writer, object, null, null, 0);

5.具体的字符串输出

return writer.toString();

反序列化设计(Json->Bean)

由于parseObject方法的代表性,如下均以com.alibaba.fastjson2.JSON#parseObject方法作为思路解析的依据

其实作者对序列化,反序列化的思路是差不多的。只不过反序列化有一些区别,这部分只针对有区分的部分做推倒。

  • Json转为Bean时,key的顺序不可预测。
  • JSON->Bean,只需要调用Bean的set方法,所以不需要考虑反射性能差的原因。

key无序问题

哎,JSON的key的顺序是不可预测的,我该如何处理?

有了:相同字符串的Hash值是一样的,我可以基于这个规律,在写字节码的时候,按顺序写不就行了,通过Hash来作为IF的判断依据。

可能有同学有疑问,使用Map结构足够了,为什么还有生成这样的代码

因为在key的数量相当有限的情况下,逻辑分支的性能(经过JVM指令优化)远比Map寻址的性能高。

// 根据Name的Hash值,使用switch编写逻辑分支代码。
var9 = var1.readFieldNameHashCode()
int var11 = (int)(var9 ^ var9 >>> 32);  
switch(var11) {
  case 1079836942:
    if (var9 == 3832966174269534051L) {
      var10001 = var1.readString();
      ((ExcelTransformReq)var6).setColumn15(var10001);
      continue;
    }
    break;
  case 1079902478:
    if (var9 == 3833247649246244707L) {
      var10001 = var1.readString();
      ((ExcelTransformReq)var6).setColumn25(var10001);
      continue;
    }
    break;
    ...

代码结构图

[图片上传失败...(image-c9fc78-1707987987703)]

1.入口

com.alibaba.fastjson2.JSON#parseObject

2.初始化ObjectReaderProvider,与Context(全局上下文)

ObjectReaderProvider provider = JSONFactory.getDefaultObjectReaderProvider();
JSONReader.Context context = new JSONReader.Context(provider);

3.初始化objectReader

ObjectReader<T> objectReader = provider.getObjectReader(clazz,(defaultReaderFeatures & JSONReader.Feature.FieldBased.mask) != 0);

4.初始化JSONReader

JSONReader reader = JSONReader.of(text, context);

5.解析

T object = objectReader.readObject(reader, clazz, null, 0);

环境变量设计

环境变量信息搜集

哎,我需要面对不同的环境,如果有一个全局视野知道当前环境的情况那些功能可用?

有了,我可以使用一个JDKUtils,所有当前环境的信息,都通过各种方式取出来,然后放到它里面统一维护,用这个类来解决环境变量问题。

  • JVM版本等,通过系统参数获取。(类加载阶段,static代码块)

    System.getProperty("java.vm.name")

  • 判断当前环境(安卓还是JavaWeb),通过寻找Class的方式获取(类加载阶段,static代码块)

    Class<?> factorClass = Class.forName("java.lang.management.ManagementFactory");

  • 框架公共方法,通过MethodHandles获取(运行阶段,方法首次调用时)

    MethodHandles.Lookup lookup = trustedLookup(classStringCoding);
    CallSite callSite = LambdaMetafactory.metafactory(..);
    isAscii = (Predicate<byte[]>) callSite.getTarget().invokeExact();

    PREDICATE_IS_ASCII = isAscii;

// P1
// 环境工具类
com.alibaba.fastjson2.util.JDKUtils
// static阶段获取
public static final Unsafe UNSAFE;
public static final long ARRAY_BYTE_BASE_OFFSET;
public static final long ARRAY_CHAR_BASE_OFFSET;
public static final int JVM_VERSION;
public static final Field FIELD_STRING_VALUE;  
// 如下是通过BiFunction(函数式编程),将不同环境中的能力统一出口
public static final ToIntFunction<String> STRING_CODER;
public static final Function<String, byte[]> STRING_VALUE;

// P2,如下是获取安卓SDK版本号
android_sdk_int = Class.forName("android.os.Build$VERSION").getField("SDK_INT").getInt(null);
// p3,获取Java版本号
String javaSpecVer = System.getProperty("java.specification.version");
// P4,判断当前环境是否有sql的jar包
dataSourceClass = Class.forName("javax.sql.DataSource");

// P5,根据不同版本通过Lambda表达式封装实现
if (JVM_VERSION >= 17) {
  handle = trustedLookup.findStatic(classStringCoding = 
                                    String.class,"isASCII",MethodType.methodType(boolean.class,byte[].class);
}
....
if (handle == null && JVM_VERSION >= 11) {
  classStringCoding = Class.forName("java.lang.StringCoding");
  handle = trustedLookup.findStatic(classStringCoding,"isASCII",MethodType.methodType(boolean.class, byte[].class)
}                               
MethodHandles.Lookup lookup = trustedLookup(classStringCoding);
CallSite callSite = LambdaMetafactory.metafactory(..);
isAscii = (Predicate<byte[]>) callSite.getTarget().invokeExact();
PREDICATE_IS_ASCII = isAscii;

流程数据维护和流程能力扩展

哎,全局视野如何保证?比如现在多层嵌套的JSON,由于我使用Byte[]维护。

有了:

在解析开始时候,设置一个全局Context,这个Context贯穿流程始终。

  • 维护当前进行中的值,或者解析状态值,比如JSON数据,当前写入偏移量等。
  • 维护一些全局配置,比如用户配置的功能属性开关。
  • 使用Long值+位移,来实现不同能力的开关。

哎,功能参数和扩展性如何设计呢?

  • 配置项比较多,目前有四五十个,后面可能还会拓展到百个。
  • 需要再解析前后加一些钩子。

有了:

  • 配置项可以统一使用Long+位移的方式,这样一个Long值,就能代表63个配置项,极大减少空间。

    // com.alibaba.fastjson2.JSONReader.Feature
    public enum Feature {
    FieldBased(1),
    IgnoreNoneSerializable(1 << 1),
    ErrorOnNoneSerializable(1 << 2),
    SupportArrayToBean(1 << 3),
    InitStringFieldAsEmpty(1 << 4)
    }
    
  • 其实扩展性无非是俩种类型,一种是影响主逻辑的,一种是不影响主逻辑(单纯是生命周期回调),第1种我可以使用Lambda表达式,第2种我可以使用Processor思路去做(统一维护触发对象,在特定实际触发)。

    1.生命周期触发

    2.置入代码调整主逻辑。

  • 使用全局Context,维护所有的扩展配置,使这些配置在全流程中可见,被动触发不受限制。

// 分别是序列化,反序列化,的Context
com.alibaba.fastjson2.JSONWriter.Context
com.alibaba.fastjson2.JSONReader.Context
  
// 如下是JSONReader.Context的部分定义,其中objectSupplier,arraySupplier为
public static final class com.alibaba.fastjson2.JSONReader.Context {
   long features;
   // 扩展点:第1种
   Supplier<Map> objectSupplier;
   Supplier<List> arraySupplier;
   // 扩展点:第2种
   AutoTypeBeforeHandler autoTypeBeforeHandler;
   ExtraProcessor extraProcessor;   
}
// 如下是JSONWriter.Context的部分定义,
public static final class com.alibaba.fastjson2.JSONWriter.Context {
    long features;
    boolean hasFilter;
    // 扩展点:第2种
    PropertyPreFilter propertyPreFilter;
    PropertyFilter propertyFilter;
    NameFilter nameFilter;
    ValueFilter valueFilter;
    BeforeFilter beforeFilter;
    AfterFilter afterFilter;
    ...
}

哎,这些扩展点,需要通过不同的入参传入,体验不太友好,因为这些配置项到底是哪个层面的,没有办法客观区分出来?

答:有了,使用参数注解,属性注解天生就具备一个能力:该注解生效在哪个属性上!

高低版本JDK如何兼容

哎,不同环境可能有一些能力的实现,或者方法名称不一样?我是否可以借力其他框架的某一些能力?

有了:上面好像用过 Lambda表达式的一种能力,将不同环境的实现,统一入口。

CallSite callSite = LambdaMetafactory.metafactory(..);

我可以基于这个思路,通过Class.forName,寻找出当前环境能提供的内容,甚至可以查找一些三方包。

只要符合要求的,我都包装进来

  • 这样即便某些低版本的JDK也可以使用高版本的一些能力(如果它引用的一些好的三方包)。
  • 模板方法的核心逻辑封装不需要考虑环境问题(调用入口已经统一)。

方法的传递,其实使用MethodHandle已经足够了,是否再将MethodHandle包装为Lambda,取决于是否要统一调用入口。

所以如下,既有MethodHandle,也有Lambda(BiFunction,ToIntFunction)

public static final BiFunction<char[], Boolean, String> STRING_CREATOR_JDK8; // JDK 8 的 String 创建函数
public static final BiFunction<byte[], Byte, String> STRING_CREATOR_JDK11; // JDK 11 的 String 创建函数
public static final ToIntFunction<String> STRING_CODER; // String 的编码函数
public static final Function<String, byte[]> STRING_VALUE; // String 的值函数

public static final MethodHandle METHOD_HANDLE_HAS_NEGATIVE; // 是否有负数的方法句柄
public static final Predicate<byte[]> PREDICATE_IS_ASCII; // 是否为 ASCII 的谓词

如下则是通过其他框架来扩展自身能力:

// com.alibaba.fastjson2.writer.ObjectWriterProvider#getObjectWriterInternal

switch (className) {
  case "com.google.common.collect.HashMultimap":
  case "com.google.common.collect.LinkedListMultimap":
  case "com.google.common.collect.LinkedHashMultimap":
  case "com.google.common.collect.ArrayListMultimap":
  case "com.google.common.collect.TreeMultimap":
    objectWriter = GuavaSupport.createAsMapWriter(objectClass);
    break;
  case "com.google.common.collect.AbstractMapBasedMultimap$RandomAccessWrappedList":
    objectWriter = ObjectWriterImplList.INSTANCE;
    break;
  case "com.alibaba.fastjson.JSONObject":
    objectWriter = ObjectWriterImplMap.of(objectClass);
    break;
  case "android.net.Uri$OpaqueUri":
  case "android.net.Uri$HierarchicalUri":
  case "android.net.Uri$StringUri":
    objectWriter = ObjectWriterImplToString.INSTANCE;
    break;
  default:
    break;
}

性能提升的思路

  • Lambada表达式+MethodHandler,替换反射调用。
  • Unsafe方法,替换反射调用set方法。
  • 多维度缓存,确保在高频调用逻辑均由缓存承载。
  • 使用全局上下文,维护复用全局变量,减少重复对象的生成。
  • 编码相关:位移操作代替加减乘除;数组索引代替map中的key;减少容器自动扩容行为;尽量在类加载阶段获取信息;尽可能的使用IdentityHashMap。
  • CodeGen算法使用。
  • 使用字节码提升JSONPath性能。
  • 使用Fnv算法代替原生的hash算法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容