Skip to content

Commit ae1a7e0

Browse files
feat(java): implement FinalFieldReplaceResolveSerializer for final fields with writeReplace/readResolve methods (#2917)
<!-- **Thanks for contributing to Apache Fory™.** **If this is your first time opening a PR on fory, you can refer to [CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).** Contribution Checklist - The **Apache Fory™** community has requirements on the naming of pr titles. You can also find instructions in [CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md). - Apache Fory™ has a strong focus on performance. If the PR you submit will have an impact on performance, please benchmark it first and provide the benchmark result here. --> ## Why? Based on the discussion #2786. We can optimize the payload in cases where we have final fields in some Object. re-created, closed PR #2904 ## What does this PR do? Introduce `FinalFieldReplaceResolver` which does not write the classname into the payload. ## Related issues <!-- Is there any related issue? If this PR closes them you say say fix/closes: - #xxxx0 - #xxxx1 - Fixes #xxxx2 --> ## Does this PR introduce any user-facing change? <!-- If any user-facing interface changes, please [open an issue](https://github.com/apache/fory/issues/new/choose) describing the need to do so and update the document if necessary. Delete section if not applicable. --> - [x] Does this PR introduce any public API change? - [x] Does this PR introduce any binary protocol compatibility change? ## Benchmark <!-- When the PR has an impact on performance (if you don't know whether the PR will have an impact on performance, you can submit the PR first, and if it will have impact on performance, the code reviewer will explain it), be sure to attach a benchmark data here. Delete section if not applicable. --> --------- Co-authored-by: Shawn Yang <[email protected]>
1 parent f384e4f commit ae1a7e0

File tree

9 files changed

+835
-79
lines changed

9 files changed

+835
-79
lines changed

java/fory-core/src/main/java/org/apache/fory/Fory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,10 @@ public boolean isCompatible() {
17121712
return config.getCompatibleMode() == CompatibleMode.COMPATIBLE;
17131713
}
17141714

1715+
public boolean isShareMeta() {
1716+
return shareMeta;
1717+
}
1718+
17151719
public boolean trackingRef() {
17161720
return refTracking;
17171721
}

java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java

Lines changed: 207 additions & 44 deletions
Large diffs are not rendered by default.

java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,7 @@ private Expression serializeGroup(
207207
// `bean` will be replaced by `Reference` to cut-off expr dependency.
208208
Expression fieldValue = getFieldValue(bean, d);
209209
walkPath.add(d.getDeclaringClass() + d.getName());
210-
boolean nullable = d.isNullable();
211-
Expression fieldExpr =
212-
serializeForNullable(fieldValue, buffer, d.getTypeRef(), nullable);
210+
Expression fieldExpr = serializeField(fieldValue, buffer, d);
213211
walkPath.removeLast();
214212
groupExpressions.add(fieldExpr);
215213
}
@@ -555,17 +553,15 @@ protected Expression deserializeGroup(
555553
for (Descriptor d : group) {
556554
ExpressionVisitor.ExprHolder exprHolder = ExpressionVisitor.ExprHolder.of("bean", bean);
557555
walkPath.add(d.getDeclaringClass() + d.getName());
558-
boolean nullable = d.isNullable();
559556
Expression action =
560-
deserializeForNullable(
557+
deserializeField(
561558
buffer,
562-
d.getTypeRef(),
559+
d,
563560
// `bean` will be replaced by `Reference` to cut-off expr
564561
// dependency.
565562
expr ->
566563
setFieldValue(
567-
exprHolder.get("bean"), d, tryInlineCast(expr, d.getTypeRef())),
568-
nullable);
564+
exprHolder.get("bean"), d, tryInlineCast(expr, d.getTypeRef())));
569565
walkPath.removeLast();
570566
groupExpressions.add(action);
571567
}

java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
import org.apache.fory.serializer.CompatibleSerializer;
111111
import org.apache.fory.serializer.EnumSerializer;
112112
import org.apache.fory.serializer.ExternalizableSerializer;
113+
import org.apache.fory.serializer.FinalFieldReplaceResolveSerializer;
113114
import org.apache.fory.serializer.ForyCopyableSerializer;
114115
import org.apache.fory.serializer.JavaSerializer;
115116
import org.apache.fory.serializer.JdkProxySerializer;
@@ -789,8 +790,8 @@ public void clearSerializer(Class<?> cls) {
789790
}
790791
}
791792

792-
/** Ass serializer for specified class. */
793-
private void addSerializer(Class<?> type, Serializer<?> serializer) {
793+
/** Add serializer for specified class. */
794+
public void addSerializer(Class<?> type, Serializer<?> serializer) {
794795
Preconditions.checkNotNull(serializer);
795796
// 1. Try to get ClassInfo from `registeredId2ClassInfo` and
796797
// `classInfoMap` or create a new `ClassInfo`.
@@ -801,7 +802,8 @@ private void addSerializer(Class<?> type, Serializer<?> serializer) {
801802
if (registered) {
802803
classInfo = registeredId2ClassInfo[classId];
803804
} else {
804-
if (serializer instanceof ReplaceResolveSerializer) {
805+
if (serializer instanceof ReplaceResolveSerializer
806+
&& !(serializer instanceof FinalFieldReplaceResolveSerializer)) {
805807
classId = REPLACE_STUB_ID;
806808
} else {
807809
classId = NO_CLASS_ID;

java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ public <T> Serializer<T> getSerializer(Class<T> cls) {
706706
return (Serializer) getClassInfo(cls).serializer;
707707
}
708708

709+
@Override
709710
public Serializer<?> getRawSerializer(Class<?> cls) {
710711
return getClassInfo(cls).serializer;
711712
}

java/fory-core/src/main/java/org/apache/fory/serializer/AbstractObjectSerializer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,12 @@ private FinalTypeField(Fory fory, Descriptor d) {
10351035
classInfo = null;
10361036
} else {
10371037
classInfo = SerializationUtils.getClassInfo(fory, typeRef.getRawType());
1038+
if (!fory.isShareMeta()
1039+
&& !fory.isCompatible()
1040+
&& classInfo.getSerializer() instanceof ReplaceResolveSerializer) {
1041+
// overwrite replace resolve serializer for final field
1042+
classInfo.setSerializer(new FinalFieldReplaceResolveSerializer(fory, classInfo.getCls()));
1043+
}
10381044
}
10391045
}
10401046
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.fory.serializer;
21+
22+
import org.apache.fory.Fory;
23+
import org.apache.fory.config.CompatibleMode;
24+
import org.apache.fory.memory.MemoryBuffer;
25+
26+
/**
27+
* Serializer for class which: - has jdk `writeReplace`/`readResolve` method defined, - is a final
28+
* class. Main advantage of this serializer is that it does not write class name to the payload.
29+
* NOTE: this serializer is used only with {@link CompatibleMode#SCHEMA_CONSISTENT} mode.
30+
*/
31+
@SuppressWarnings({"unchecked", "rawtypes"})
32+
public class FinalFieldReplaceResolveSerializer extends ReplaceResolveSerializer {
33+
34+
public FinalFieldReplaceResolveSerializer(Fory fory, Class type) {
35+
// the serializer does not write class info
36+
// and does not set itself for the provided class
37+
// see checks in ReplaceResolveSerializer constructor
38+
super(fory, type, true, false);
39+
}
40+
41+
@Override
42+
protected void writeObject(
43+
MemoryBuffer buffer, Object value, MethodInfoCache jdkMethodInfoCache) {
44+
jdkMethodInfoCache.objectSerializer.write(buffer, value);
45+
}
46+
47+
@Override
48+
protected Object readObject(MemoryBuffer buffer) {
49+
MethodInfoCache jdkMethodInfoCache = getMethodInfoCache(type);
50+
Object o = jdkMethodInfoCache.objectSerializer.read(buffer);
51+
ReplaceResolveInfo replaceResolveInfo = jdkMethodInfoCache.info;
52+
if (replaceResolveInfo.readResolveMethod == null) {
53+
return o;
54+
}
55+
return replaceResolveInfo.readResolve(o);
56+
}
57+
}

java/fory-core/src/main/java/org/apache/fory/serializer/ReplaceResolveSerializer.java

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ public class ReplaceResolveSerializer extends Serializer {
5353
*/
5454
public static class ReplaceStub {}
5555

56-
private static final byte ORIGINAL = 0;
57-
private static final byte REPLACED_NEW_TYPE = 1;
58-
private static final byte REPLACED_SAME_TYPE = 2;
56+
protected static final byte ORIGINAL = 0;
57+
protected static final byte REPLACED_NEW_TYPE = 1;
58+
protected static final byte REPLACED_SAME_TYPE = 2;
5959

6060
// Extract Method Info to cache for graalvm build time lambda generation and avoid
6161
// generate function repeatedly too.
62-
private static class ReplaceResolveInfo {
63-
private final Method writeReplaceMethod;
64-
private final Method readResolveMethod;
62+
protected static class ReplaceResolveInfo {
63+
protected final Method writeReplaceMethod;
64+
protected final Method readResolveMethod;
6565
private final Function writeReplaceFunc;
6666
private final Function readResolveFunc;
6767

@@ -162,10 +162,10 @@ protected ReplaceResolveInfo computeValue(Class<?> type) {
162162
}
163163
};
164164

165-
private static class MethodInfoCache {
166-
private final ReplaceResolveInfo info;
165+
protected static class MethodInfoCache {
166+
protected final ReplaceResolveInfo info;
167167

168-
private Serializer objectSerializer;
168+
protected Serializer objectSerializer;
169169

170170
public MethodInfoCache(ReplaceResolveInfo info) {
171171
this.info = info;
@@ -210,26 +210,38 @@ private static Serializer createDataSerializer(
210210
return serializer;
211211
}
212212

213-
private final RefResolver refResolver;
214-
private final ClassResolver classResolver;
215-
private final MethodInfoCache jdkMethodInfoWriteCache;
216-
private final ClassInfo writeClassInfo;
217-
private final Map<Class<?>, MethodInfoCache> classClassInfoHolderMap = new HashMap<>();
213+
protected final RefResolver refResolver;
214+
protected final ClassResolver classResolver;
215+
protected final MethodInfoCache jdkMethodInfoWriteCache;
216+
protected final ClassInfo writeClassInfo;
217+
protected final Map<Class<?>, MethodInfoCache> classClassInfoHolderMap = new HashMap<>();
218218

219219
public ReplaceResolveSerializer(Fory fory, Class type) {
220+
this(fory, type, false, true);
221+
}
222+
223+
public ReplaceResolveSerializer(
224+
Fory fory, Class type, boolean isFinalField, boolean setSerializer) {
220225
super(fory, type);
221226
refResolver = fory.getRefResolver();
222227
classResolver = fory.getClassResolver();
223-
// `setSerializer` before `newJDKMethodInfoCache` since it query classinfo from `classResolver`,
224-
// which create serializer in turn.
225-
// ReplaceResolveSerializer is used as data serializer for ImmutableList/Map,
226-
// which serializer is already set.
227-
classResolver.setSerializerIfAbsent(type, this);
228+
if (setSerializer) {
229+
// `setSerializer` before `newJDKMethodInfoCache` since it query classinfo from
230+
// `classResolver`,
231+
// which create serializer in turn.
232+
// ReplaceResolveSerializer is used as data serializer for ImmutableList/Map,
233+
// which serializer is already set.
234+
classResolver.setSerializerIfAbsent(type, this);
235+
}
228236
if (type != ReplaceStub.class) {
229237
jdkMethodInfoWriteCache = newJDKMethodInfoCache(type, fory);
230238
classClassInfoHolderMap.put(type, jdkMethodInfoWriteCache);
231-
// FIXME new classinfo may miss serializer update in async compilation mode.
232-
writeClassInfo = classResolver.newClassInfo(type, this, ClassResolver.NO_CLASS_ID);
239+
if (isFinalField) {
240+
writeClassInfo = null;
241+
} else {
242+
// FIXME new classinfo may miss serializer update in async compilation mode.
243+
writeClassInfo = classResolver.newClassInfo(type, this, ClassResolver.NO_CLASS_ID);
244+
}
233245
} else {
234246
jdkMethodInfoWriteCache = null;
235247
writeClassInfo = null;
@@ -280,7 +292,8 @@ public void write(MemoryBuffer buffer, Object value) {
280292
}
281293
}
282294

283-
private void writeObject(MemoryBuffer buffer, Object value, MethodInfoCache jdkMethodInfoCache) {
295+
protected void writeObject(
296+
MemoryBuffer buffer, Object value, MethodInfoCache jdkMethodInfoCache) {
284297
classResolver.writeClassInternal(buffer, writeClassInfo);
285298
jdkMethodInfoCache.objectSerializer.write(buffer, value);
286299
}
@@ -319,7 +332,7 @@ public Object read(MemoryBuffer buffer) {
319332
}
320333
}
321334

322-
private Object readObject(MemoryBuffer buffer) {
335+
protected Object readObject(MemoryBuffer buffer) {
323336
Class cls = classResolver.readClassInternal(buffer);
324337
MethodInfoCache jdkMethodInfoCache = getMethodInfoCache(cls);
325338
Object o = jdkMethodInfoCache.objectSerializer.read(buffer);
@@ -350,7 +363,7 @@ public Object copy(Object originObj) {
350363
return newObj;
351364
}
352365

353-
private MethodInfoCache getMethodInfoCache(Class<?> cls) {
366+
protected MethodInfoCache getMethodInfoCache(Class<?> cls) {
354367
MethodInfoCache jdkMethodInfoCache = classClassInfoHolderMap.get(cls);
355368
if (jdkMethodInfoCache == null) {
356369
jdkMethodInfoCache = newJDKMethodInfoCache(cls, fory);

0 commit comments

Comments
 (0)