This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory-site.git
The following commit(s) were added to refs/heads/main by this push:
new 9c6eb9d3b6 🔄 synced local 'docs/guide/' with remote 'docs/guide/'
9c6eb9d3b6 is described below
commit 9c6eb9d3b6a4e4c0f528910ba40c01b080854501
Author: chaokunyang <[email protected]>
AuthorDate: Mon Apr 6 03:18:07 2026 +0000
🔄 synced local 'docs/guide/' with remote 'docs/guide/'
---
docs/guide/java/configuration.md | 50 +--
docs/guide/java/custom-serializers.md | 725 +++++++---------------------------
docs/guide/java/schema-evolution.md | 18 +-
docs/guide/java/troubleshooting.md | 2 +-
docs/guide/java/type-registration.md | 8 +-
docs/guide/kotlin/fory-creation.md | 14 +-
docs/guide/scala/fory-creation.md | 15 +-
7 files changed, 198 insertions(+), 634 deletions(-)
diff --git a/docs/guide/java/configuration.md b/docs/guide/java/configuration.md
index ece66d5080..9071d423b6 100644
--- a/docs/guide/java/configuration.md
+++ b/docs/guide/java/configuration.md
@@ -23,31 +23,31 @@ This page documents all configuration options available
through `ForyBuilder`.
## ForyBuilder Options
-| Option Name | Description
[...]
-| ----------------------------------- |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[...]
-| `timeRefIgnored` | Whether to ignore reference tracking
of all time types registered in `TimeSerializers` and subclasses of those types
when ref tracking is enabled. If ignored, ref tracking of every time type can
be enabled by invoking `Fory#registerSerializer(Class, Serializer)`. For
example, `fory.registerSerializer(Date.class, new DateSerializer(fory, true))`.
Note that enabling ref tracking should happen before serializer codegen of any
types which contain time [...]
-| `compressInt` | Enables or disables int compression
for smaller size.
[...]
-| `compressLong` | Enables or disables long compression
for smaller size.
[...]
-| `compressIntArray` | Enables or disables SIMD-accelerated
compression for int arrays when values can fit in smaller data types. Requires
Java 16+.
[...]
-| `compressLongArray` | Enables or disables SIMD-accelerated
compression for long arrays when values can fit in smaller data types. Requires
Java 16+.
[...]
-| `compressString` | Enables or disables string compression
for smaller size.
[...]
-| `classLoader` | The classloader is fixed when the
`Fory` or `ThreadSafeFory` instance is built. If you need a different
classloader, build a different instance.
[...]
-| `compatibleMode` | Type forward/backward compatibility
config. Also Related to `checkClassVersion` config. `SCHEMA_CONSISTENT`: Class
schema must be consistent between serialization peer and deserialization peer.
`COMPATIBLE`: Class schema can be different between serialization peer and
deserialization peer. They can add/delete fields independently. [See
more](schema-evolution.md).
[...]
-| `checkClassVersion` | Determines whether to check the
consistency of the class schema. If enabled, Fory checks, writes, and checks
consistency using the `classVersionHash`. It will be automatically disabled
when `CompatibleMode#COMPATIBLE` is enabled. Disabling is not recommended
unless you can ensure the class won't evolve.
[...]
-| `checkJdkClassSerializable` | Enables or disables checking of
`Serializable` interface for classes under `java.*`. If a class under `java.*`
is not `Serializable`, Fory will throw an `UnsupportedOperationException`.
[...]
-| `registerGuavaTypes` | Whether to pre-register Guava types
such as `RegularImmutableMap`/`RegularImmutableList`. These types are not
public API, but seem pretty stable.
[...]
-| `requireClassRegistration` | Disabling may allow unknown classes to
be deserialized, potentially causing security risks.
[...]
-| `maxDepth` | Set max depth for deserialization,
when depth exceeds, an exception will be thrown. This can be used to refuse
deserialization DDOS attack.
[...]
-| `suppressClassRegistrationWarnings` | Whether to suppress class registration
warnings. The warnings can be used for security audit, but may be annoying,
this suppression will be enabled by default.
[...]
-| `metaShareEnabled` | Enables or disables meta share mode.
[...]
-| `scopedMetaShareEnabled` | Scoped meta share focuses on a single
serialization process. Metadata created or identified during this process is
exclusive to it and is not shared with by other serializations.
[...]
-| `metaCompressor` | Set a compressor for meta compression.
Note that the passed MetaCompressor should be thread-safe. By default, a
`Deflater` based compressor `DeflaterMetaCompressor` will be used. Users can
pass other compressor such as `zstd` for better compression rate.
[...]
-| `deserializeUnknownClass` | Enables or disables
deserialization/skipping of data for non-existent or unknown classes.
[...]
-| `codeGenEnabled` | Disabling may result in faster initial
serialization but slower subsequent serializations.
[...]
-| `asyncCompilationEnabled` | If enabled, serialization uses
interpreter mode first and switches to JIT serialization after async serializer
JIT for a class is finished.
[...]
-| `scalaOptimizationEnabled` | Enables or disables Scala-specific
serialization optimization.
[...]
-| `copyRef` | When disabled, the copy performance
will be better. But fory deep copy will ignore circular and shared reference.
Same reference of an object graph will be copied into different objects in one
`Fory#copy`.
[...]
-| `serializeEnumByName` | When Enabled, fory serialize enum by
name instead of ordinal.
[...]
+| Option Name | Description
[...]
+| ----------------------------------- |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[...]
+| `timeRefIgnored` | Whether to ignore reference tracking
of all time types registered in `TimeSerializers` and subclasses of those types
when ref tracking is enabled. If ignored, ref tracking of every time type can
be enabled by invoking `Fory#registerSerializer(Class, Serializer)`. For
example, `fory.registerSerializer(Date.class, new
DateSerializer(fory.getConfig(), true))`. Note that enabling ref tracking
should happen before serializer codegen of any types which c [...]
+| `compressInt` | Enables or disables int compression
for smaller size.
[...]
+| `compressLong` | Enables or disables long compression
for smaller size.
[...]
+| `compressIntArray` | Enables or disables SIMD-accelerated
compression for int arrays when values can fit in smaller data types. Requires
Java 16+.
[...]
+| `compressLongArray` | Enables or disables SIMD-accelerated
compression for long arrays when values can fit in smaller data types. Requires
Java 16+.
[...]
+| `compressString` | Enables or disables string compression
for smaller size.
[...]
+| `classLoader` | The classloader is fixed per `Fory`
instance because Fory caches class metadata. To use a different loader, create
a new `Fory` or `ThreadSafeFory` configured with that loader, or rely on the
thread context classloader before first class resolution.
[...]
+| `compatibleMode` | Type forward/backward compatibility
config. Also Related to `checkClassVersion` config. `SCHEMA_CONSISTENT`: Class
schema must be consistent between serialization peer and deserialization peer.
`COMPATIBLE`: Class schema can be different between serialization peer and
deserialization peer. They can add/delete fields independently. [See
more](schema-evolution.md).
[...]
+| `checkClassVersion` | Determines whether to check the
consistency of the class schema. If enabled, Fory checks, writes, and checks
consistency using the `classVersionHash`. It will be automatically disabled
when `CompatibleMode#COMPATIBLE` is enabled. Disabling is not recommended
unless you can ensure the class won't evolve.
[...]
+| `checkJdkClassSerializable` | Enables or disables checking of
`Serializable` interface for classes under `java.*`. If a class under `java.*`
is not `Serializable`, Fory will throw an `UnsupportedOperationException`.
[...]
+| `registerGuavaTypes` | Whether to pre-register Guava types
such as `RegularImmutableMap`/`RegularImmutableList`. These types are not
public API, but seem pretty stable.
[...]
+| `requireClassRegistration` | Disabling may allow unknown classes to
be deserialized, potentially causing security risks.
[...]
+| `maxDepth` | Set max depth for deserialization,
when depth exceeds, an exception will be thrown. This can be used to refuse
deserialization DDOS attack.
[...]
+| `suppressClassRegistrationWarnings` | Whether to suppress class registration
warnings. The warnings can be used for security audit, but may be annoying,
this suppression will be enabled by default.
[...]
+| `metaShareEnabled` | Enables or disables meta share mode.
[...]
+| `scopedMetaShareEnabled` | Scoped meta share focuses on a single
serialization process. Metadata created or identified during this process is
exclusive to it and is not shared with by other serializations.
[...]
+| `metaCompressor` | Set a compressor for meta compression.
Note that the passed MetaCompressor should be thread-safe. By default, a
`Deflater` based compressor `DeflaterMetaCompressor` will be used. Users can
pass other compressor such as `zstd` for better compression rate.
[...]
+| `deserializeUnknownClass` | Enables or disables
deserialization/skipping of data for non-existent or unknown classes.
[...]
+| `codeGenEnabled` | Disabling may result in faster initial
serialization but slower subsequent serializations.
[...]
+| `asyncCompilationEnabled` | If enabled, serialization uses
interpreter mode first and switches to JIT serialization after async serializer
JIT for a class is finished.
[...]
+| `scalaOptimizationEnabled` | Enables or disables Scala-specific
serialization optimization.
[...]
+| `copyRef` | When disabled, the copy performance
will be better. But fory deep copy will ignore circular and shared reference.
Same reference of an object graph will be copied into different objects in one
`Fory#copy`.
[...]
+| `serializeEnumByName` | When Enabled, fory serialize enum by
name instead of ordinal.
[...]
## Example Configuration
diff --git a/docs/guide/java/custom-serializers.md
b/docs/guide/java/custom-serializers.md
index 2afcac066b..a1aee2f601 100644
--- a/docs/guide/java/custom-serializers.md
+++ b/docs/guide/java/custom-serializers.md
@@ -19,641 +19,212 @@ license: |
limitations under the License.
---
-This page covers how to implement custom serializers for your types.
+This page covers the current Java custom serializer API.
-## Basic Custom Serializer
+## Constructor Inputs
-In some cases, you may want to implement a serializer for your type,
especially for classes that customize serialization using JDK
`writeObject/writeReplace/readObject/readResolve`, which is very inefficient.
+Custom serializers should not retain `Fory`.
-For example, if you don't want the following `Foo#writeObject` to be invoked,
you can implement a custom serializer:
+- Use `Config` when the serializer only depends on immutable configuration and
can be shared.
+- Use `TypeResolver` when the serializer needs type metadata, generics, or
nested dynamic dispatch.
+- If a serializer retains `TypeResolver`, it is usually not shareable and
should keep the default
+ `shareable() == false`.
-```java
-class Foo {
- public long f1;
+## Basic Serializer
- private void writeObject(ObjectOutputStream s) throws IOException {
- System.out.println(f1);
- s.defaultWriteObject();
- }
-}
+Use `WriteContext` and `ReadContext` for runtime state. Only get the buffer
into a local variable
+when you perform multiple reads or writes.
-class FooSerializer extends Serializer<Foo> {
- public FooSerializer(Fory fory) {
- super(fory, Foo.class);
+```java
+import org.apache.fory.config.Config;
+import org.apache.fory.context.ReadContext;
+import org.apache.fory.context.WriteContext;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.serializer.Serializer;
+
+public final class FooSerializer extends Serializer<Foo> {
+ public FooSerializer(Config config) {
+ super(config, Foo.class);
}
@Override
- public void write(MemoryBuffer buffer, Foo value) {
- buffer.writeInt64(value.f1);
+ public void write(WriteContext writeContext, Foo value) {
+ writeContext.getBuffer().writeInt64(value.f1);
+ writeContext.writeString(value.f2);
}
@Override
- public Foo read(MemoryBuffer buffer) {
+ public Foo read(ReadContext readContext) {
+ MemoryBuffer buffer = readContext.getBuffer();
Foo foo = new Foo();
foo.f1 = buffer.readInt64();
+ foo.f2 = readContext.readString(buffer);
return foo;
}
+
+ @Override
+ public boolean shareable() {
+ return true;
+ }
}
```
-### Register the Serializer
+Register it with a `Config`-based constructor when the serializer is shareable:
```java
-Fory fory = getFory();
-fory.registerSerializer(Foo.class, new FooSerializer(fory));
+Fory fory = Fory.builder().build();
+fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig()));
```
-Besides registering serializers, you can also implement
`java.io.Externalizable` for a class to customize serialization logic. Such
types will be serialized by Fory's `ExternalizableSerializer`.
-
-## Collection Serializer
-
-When implementing a serializer for a custom Collection type, you must extend
`CollectionSerializer` or `CollectionLikeSerializer`. The key difference is
that `CollectionLikeSerializer` can serialize a class which has a
collection-like structure but is not a Java Collection subtype.
-
-### supportCodegenHook Parameter
-
-This special parameter controls serialization behavior:
-
-**When `true`:**
-
-- Enables optimized access to collection elements and JIT compilation for
better performance
-- Direct serialization invocation and inline for collection items without
dynamic serializer dispatch cost
-- Better performance for standard collection types
-- Recommended for most collections
-
-**When `false`:**
-
-- Uses interface-based element access and dynamic serializer dispatch for
elements (higher cost)
-- More flexible for custom collection types
-- Required when collection has special serialization needs
-- Handles complex collection implementations
-
-### Collection Serializer with JIT Support
+## Nested Objects
-When implementing a Collection serializer with JIT support, leverage Fory's
existing binary format and collection serialization infrastructure:
+If your serializer needs to write or read nested objects, use the context
helpers instead of
+retaining `Fory`:
```java
-public class CustomCollectionSerializer<T extends Collection> extends
CollectionSerializer<T> {
- public CustomCollectionSerializer(Fory fory, Class<T> cls) {
- // supportCodegenHook controls whether to use JIT compilation
- super(fory, cls, true);
- }
-
- @Override
- public Collection onCollectionWrite(MemoryBuffer buffer, T value) {
- // Write collection size
- buffer.writeVarUint32Small7(value.size());
- // Write any additional collection metadata
- return value;
- }
-
- @Override
- public Collection newCollection(MemoryBuffer buffer) {
- // Create new collection instance
- Collection collection = super.newCollection(buffer);
- // Read and set collection size
- int numElements = getAndClearNumElements();
- setNumElements(numElements);
- return collection;
- }
-}
-```
-
-Note: Invoke `setNumElements` when implementing `newCollection` to let Fory
know how many elements to deserialize.
-
-### Custom Collection Serializer without JIT
-
-For collections that use primitive arrays or have special requirements,
implement a serializer with JIT disabled:
+import org.apache.fory.config.Config;
+import org.apache.fory.context.ReadContext;
+import org.apache.fory.context.WriteContext;
+import org.apache.fory.serializer.Serializer;
+
+public final class EnvelopeSerializer extends Serializer<Envelope> {
+ public EnvelopeSerializer(Config config) {
+ super(config, Envelope.class);
+ }
-```java
-class IntList extends AbstractCollection<Integer> {
- private final int[] elements;
- private final int size;
-
- public IntList(int size) {
- this.elements = new int[size];
- this.size = size;
- }
-
- public IntList(int[] elements, int size) {
- this.elements = elements;
- this.size = size;
- }
-
- @Override
- public Iterator<Integer> iterator() {
- return new Iterator<Integer>() {
- private int index = 0;
-
- @Override
- public boolean hasNext() {
- return index < size;
- }
-
- @Override
- public Integer next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- return elements[index++];
- }
- };
- }
-
- @Override
- public int size() {
- return size;
- }
-
- public int get(int index) {
- if (index >= size) {
- throw new IndexOutOfBoundsException();
- }
- return elements[index];
- }
-
- public void set(int index, int value) {
- if (index >= size) {
- throw new IndexOutOfBoundsException();
- }
- elements[index] = value;
- }
-
- public int[] getElements() {
- return elements;
- }
-}
+ @Override
+ public void write(WriteContext writeContext, Envelope value) {
+ writeContext.writeRef(value.header);
+ writeContext.writeRef(value.payload);
+ }
-class IntListSerializer extends CollectionLikeSerializer<IntList> {
- public IntListSerializer(Fory fory) {
- // Disable JIT since we're handling serialization directly
- super(fory, IntList.class, false);
- }
-
- @Override
- public void write(MemoryBuffer buffer, IntList value) {
- // Write size
- buffer.writeVarUint32Small7(value.size());
-
- // Write elements directly as primitive ints
- int[] elements = value.getElements();
- for (int i = 0; i < value.size(); i++) {
- buffer.writeVarInt32(elements[i]);
- }
- }
-
- @Override
- public IntList read(MemoryBuffer buffer) {
- // Read size
- int size = buffer.readVarUint32Small7();
-
- // Create array and read elements
- int[] elements = new int[size];
- for (int i = 0; i < size; i++) {
- elements[i] = buffer.readVarInt32();
- }
-
- return new IntList(elements, size);
- }
-
- // These methods are not used when JIT is disabled
- @Override
- public Collection onCollectionWrite(MemoryBuffer buffer, IntList value) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Collection newCollection(MemoryBuffer buffer) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public IntList onCollectionRead(Collection collection) {
- throw new UnsupportedOperationException();
- }
+ @Override
+ public Envelope read(ReadContext readContext) {
+ Envelope envelope = new Envelope();
+ envelope.header = (Header) readContext.readRef();
+ envelope.payload = readContext.readRef();
+ return envelope;
+ }
}
```
-**When to use this approach:**
+This serializer can be shareable because it retains no runtime-local mutable
state.
-- Working with primitive types
-- Need maximum performance
-- Want to minimize memory overhead
-- Have special serialization requirements
+## Collection Serializers
-### Collection-like Type Serializer
+For Java collections, extend `CollectionSerializer` or
`CollectionLikeSerializer`.
-For types that behave like collections but aren't standard Java Collections:
+- Use `CollectionSerializer` for real `Collection` implementations.
+- Use `CollectionLikeSerializer` for collection-shaped types that do not
implement `Collection`.
+- Keep `supportCodegenHook == true` when the collection can use the standard
element codegen path.
+- Set `supportCodegenHook == false` only when you need to fully control
element IO.
+
+Example:
```java
-class CustomCollectionLike {
- private final Object[] elements;
- private final int size;
-
- public CustomCollectionLike(int size) {
- this.elements = new Object[size];
- this.size = 0;
- }
-
- public CustomCollectionLike(Object[] elements, int size) {
- this.elements = elements;
- this.size = size;
- }
-
- public Object get(int index) {
- if (index >= size) {
- throw new IndexOutOfBoundsException();
- }
- return elements[index];
- }
-
- public int size() {
- return size;
- }
-
- public Object[] getElements() {
- return elements;
- }
-}
+import java.util.ArrayList;
+import java.util.Collection;
+import org.apache.fory.context.ReadContext;
+import org.apache.fory.context.WriteContext;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.resolver.TypeResolver;
+import org.apache.fory.serializer.collection.CollectionSerializer;
+
+public final class CustomCollectionSerializer<T extends Collection<?>>
+ extends CollectionSerializer<T> {
+ public CustomCollectionSerializer(TypeResolver typeResolver, Class<T> type) {
+ super(typeResolver, type, true);
+ }
-class CollectionView extends AbstractCollection<Object> {
- private final Object[] elements;
- private final int size;
- private int writeIndex;
-
- public CollectionView(CustomCollectionLike collection) {
- this.elements = collection.getElements();
- this.size = collection.size();
- }
-
- public CollectionView(int size) {
- this.size = size;
- this.elements = new Object[size];
- }
-
- @Override
- public Iterator<Object> iterator() {
- return new Iterator<Object>() {
- private int index = 0;
-
- @Override
- public boolean hasNext() {
- return index < size;
- }
-
- @Override
- public Object next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- return elements[index++];
- }
- };
- }
-
- @Override
- public boolean add(Object element) {
- if (writeIndex >= size) {
- throw new IllegalStateException("Collection is full");
- }
- elements[writeIndex++] = element;
- return true;
- }
-
- @Override
- public int size() {
- return size;
- }
-
- public Object[] getElements() {
- return elements;
- }
-}
+ @Override
+ public Collection onCollectionWrite(WriteContext writeContext, T value) {
+ writeContext.getBuffer().writeVarUint32Small7(value.size());
+ return value;
+ }
+
+ @Override
+ public T onCollectionRead(Collection collection) {
+ return (T) collection;
+ }
-class CustomCollectionSerializer extends
CollectionLikeSerializer<CustomCollectionLike> {
- public CustomCollectionSerializer(Fory fory) {
- super(fory, CustomCollectionLike.class, true);
- }
-
- @Override
- public Collection onCollectionWrite(MemoryBuffer buffer,
CustomCollectionLike value) {
- buffer.writeVarUint32Small7(value.size());
- return new CollectionView(value);
- }
-
- @Override
- public Collection newCollection(MemoryBuffer buffer) {
- int numElements = buffer.readVarUint32Small7();
- setNumElements(numElements);
- return new CollectionView(numElements);
- }
-
- @Override
- public CustomCollectionLike onCollectionRead(Collection collection) {
- CollectionView view = (CollectionView) collection;
- return new CustomCollectionLike(view.getElements(), view.size());
- }
+ @Override
+ public Collection newCollection(ReadContext readContext) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numElements = buffer.readVarUint32Small7();
+ setNumElements(numElements);
+ return new ArrayList(numElements);
+ }
}
```
-## Map Serializer
+## Map Serializers
-When implementing a serializer for a custom Map type, extend `MapSerializer`
or `MapLikeSerializer`. The key difference is that `MapLikeSerializer` can
serialize a class which has a map-like structure but is not a Java Map subtype.
-
-### Map Serializer with JIT Support
+For Java maps, extend `MapSerializer` or `MapLikeSerializer`.
```java
-public class CustomMapSerializer<T extends Map> extends MapSerializer<T> {
- public CustomMapSerializer(Fory fory, Class<T> cls) {
- // supportCodegenHook is a critical parameter that determines
serialization behavior
- super(fory, cls, true);
- }
-
- @Override
- public Map onMapWrite(MemoryBuffer buffer, T value) {
- // Write map size
- buffer.writeVarUint32Small7(value.size());
- // Write any additional map metadata here
- return value;
- }
-
- @Override
- public Map newMap(MemoryBuffer buffer) {
- // Read map size
- int numElements = buffer.readVarUint32Small7();
- setNumElements(numElements);
- // Create and return new map instance
- T map = (T) new HashMap(numElements);
- fory.getRefResolver().reference(map);
- return map;
- }
-}
-```
-
-Note: Invoke `setNumElements` when implementing `newMap` to let Fory know how
many elements to deserialize.
-
-### Custom Map Serializer without JIT
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.context.ReadContext;
+import org.apache.fory.context.WriteContext;
+import org.apache.fory.resolver.TypeResolver;
+import org.apache.fory.serializer.collection.MapSerializer;
+
+public final class CustomMapSerializer<T extends Map<?, ?>> extends
MapSerializer<T> {
+ public CustomMapSerializer(TypeResolver typeResolver, Class<T> type) {
+ super(typeResolver, type, true);
+ }
-For complete control over the serialization process:
+ @Override
+ public Map onMapWrite(WriteContext writeContext, T value) {
+ writeContext.getBuffer().writeVarUint32Small7(value.size());
+ return value;
+ }
-```java
-class FixedValueMap extends AbstractMap<String, Integer> {
- private final Set<String> keys;
- private final int fixedValue;
-
- public FixedValueMap(Set<String> keys, int fixedValue) {
- this.keys = keys;
- this.fixedValue = fixedValue;
- }
-
- @Override
- public Set<Entry<String, Integer>> entrySet() {
- Set<Entry<String, Integer>> entries = new HashSet<>();
- for (String key : keys) {
- entries.add(new SimpleEntry<>(key, fixedValue));
- }
- return entries;
- }
-
- @Override
- public Integer get(Object key) {
- return keys.contains(key) ? fixedValue : null;
- }
-
- public Set<String> getKeys() {
- return keys;
- }
-
- public int getFixedValue() {
- return fixedValue;
- }
-}
+ @Override
+ public T onMapRead(Map map) {
+ return (T) map;
+ }
-class FixedValueMapSerializer extends MapLikeSerializer<FixedValueMap> {
- public FixedValueMapSerializer(Fory fory) {
- // Disable codegen since we're handling serialization directly
- super(fory, FixedValueMap.class, false);
- }
-
- @Override
- public void write(MemoryBuffer buffer, FixedValueMap value) {
- // Write the fixed value
- buffer.writeInt32(value.getFixedValue());
- // Write the number of keys
- buffer.writeVarUint32Small7(value.getKeys().size());
- // Write each key
- for (String key : value.getKeys()) {
- buffer.writeString(key);
- }
- }
-
- @Override
- public FixedValueMap read(MemoryBuffer buffer) {
- // Read the fixed value
- int fixedValue = buffer.readInt32();
- // Read the number of keys
- int size = buffer.readVarUint32Small7();
- Set<String> keys = new HashSet<>(size);
- for (int i = 0; i < size; i++) {
- keys.add(buffer.readString());
- }
- return new FixedValueMap(keys, fixedValue);
- }
-
- // These methods are not used when supportCodegenHook is false
- @Override
- public Map onMapWrite(MemoryBuffer buffer, FixedValueMap value) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public FixedValueMap onMapRead(Map map) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public FixedValueMap onMapCopy(Map map) {
- throw new UnsupportedOperationException();
- }
+ @Override
+ public Map newMap(ReadContext readContext) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numElements = buffer.readVarUint32Small7();
+ setNumElements(numElements);
+ return new LinkedHashMap(numElements);
+ }
}
```
-### Map-like Type Serializer
-
-For types that behave like maps but aren't standard Java Maps:
+## Registration
```java
-class CustomMapLike {
- private final Object[] keyArray;
- private final Object[] valueArray;
- private final int size;
-
- public CustomMapLike(int initialCapacity) {
- this.keyArray = new Object[initialCapacity];
- this.valueArray = new Object[initialCapacity];
- this.size = 0;
- }
-
- public CustomMapLike(Object[] keyArray, Object[] valueArray, int size) {
- this.keyArray = keyArray;
- this.valueArray = valueArray;
- this.size = size;
- }
-
- public Integer get(String key) {
- for (int i = 0; i < size; i++) {
- if (key.equals(keyArray[i])) {
- return (Integer) valueArray[i];
- }
- }
- return null;
- }
-
- public int size() {
- return size;
- }
-
- public Object[] getKeyArray() {
- return keyArray;
- }
-
- public Object[] getValueArray() {
- return valueArray;
- }
-}
-
-class MapView extends AbstractMap<Object, Object> {
- private final Object[] keyArray;
- private final Object[] valueArray;
- private final int size;
- private int writeIndex;
-
- public MapView(CustomMapLike mapLike) {
- this.size = mapLike.size();
- this.keyArray = mapLike.getKeyArray();
- this.valueArray = mapLike.getValueArray();
- }
-
- public MapView(int size) {
- this.size = size;
- this.keyArray = new Object[size];
- this.valueArray = new Object[size];
- }
-
- @Override
- public Set<Entry<Object, Object>> entrySet() {
- return new AbstractSet<Entry<Object, Object>>() {
- @Override
- public Iterator<Entry<Object, Object>> iterator() {
- return new Iterator<Entry<Object, Object>>() {
- private int index = 0;
-
- @Override
- public boolean hasNext() {
- return index < size;
- }
-
- @Override
- public Entry<Object, Object> next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- final int currentIndex = index++;
- return new SimpleEntry<>(
- keyArray[currentIndex],
- valueArray[currentIndex]
- );
- }
- };
- }
-
- @Override
- public int size() {
- return size;
- }
- };
- }
-
- @Override
- public Object put(Object key, Object value) {
- if (writeIndex >= size) {
- throw new IllegalStateException("Map is full");
- }
- keyArray[writeIndex] = key;
- valueArray[writeIndex] = value;
- writeIndex++;
- return null;
- }
-
- public Object[] getKeyArray() {
- return keyArray;
- }
-
- public Object[] getValueArray() {
- return valueArray;
- }
-
- public int size() {
- return size;
- }
-}
-
-class CustomMapLikeSerializer extends MapLikeSerializer<CustomMapLike> {
- public CustomMapLikeSerializer(Fory fory) {
- super(fory, CustomMapLike.class, true);
- }
-
- @Override
- public Map onMapWrite(MemoryBuffer buffer, CustomMapLike value) {
- buffer.writeVarUint32Small7(value.size());
- return new MapView(value);
- }
-
- @Override
- public Map newMap(MemoryBuffer buffer) {
- int numElements = buffer.readVarUint32Small7();
- setNumElements(numElements);
- return new MapView(numElements);
- }
-
- @Override
- public CustomMapLike onMapRead(Map map) {
- MapView view = (MapView) map;
- return new CustomMapLike(view.getKeyArray(), view.getValueArray(),
view.size());
- }
-
- @Override
- public CustomMapLike onMapCopy(Map map) {
- MapView view = (MapView) map;
- return new CustomMapLike(view.getKeyArray(), view.getValueArray(),
view.size());
- }
-}
+Fory fory = Fory.builder().build();
+
+fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig()));
+fory.registerSerializer(
+ CustomMap.class, new CustomMapSerializer<>(fory.getTypeResolver(),
CustomMap.class));
+fory.registerSerializer(
+ CustomCollection.class,
+ new CustomCollectionSerializer<>(fory.getTypeResolver(),
CustomCollection.class));
```
-## Registering Custom Serializers
+If you want Fory to construct the serializer lazily, register a factory:
```java
-Fory fory = Fory.builder()
- .withLanguage(Language.JAVA)
- .build();
-
-// Register map serializer
-fory.registerSerializer(CustomMap.class, new CustomMapSerializer<>(fory,
CustomMap.class));
-
-// Register collection serializer
-fory.registerSerializer(CustomCollection.class, new
CustomCollectionSerializer<>(fory, CustomCollection.class));
+fory.registerSerializer(
+ CustomMap.class, resolver -> new CustomMapSerializer<>(resolver,
CustomMap.class));
```
-## Key Points
-
-When implementing custom map or collection serializers:
+## Shareability
-1. Always extend the appropriate base class
(`MapSerializer`/`MapLikeSerializer` for maps,
`CollectionSerializer`/`CollectionLikeSerializer` for collections)
-2. Consider the impact of `supportCodegenHook` on performance and functionality
-3. Properly handle reference tracking if needed
-4. Implement proper size management using `setNumElements` and
`getAndClearNumElements` when `supportCodegenHook` is `true`
+Override `shareable()` only when the serializer can be safely reused across
equivalent runtimes and
+concurrent operations. In practice, that means the serializer must not retain
operation state,
+runtime-local mutable state, or mutable scratch buffers that are shared across
calls.
-## Related Topics
+In practice:
-- [Type Registration](type-registration.md) - Register serializers
-- [Schema Evolution](schema-evolution.md) - Compatible mode considerations
-- [Configuration](configuration.md) - Serialization options
+- `Config`-only serializers are often shareable.
+- `TypeResolver`-based serializers are usually not shareable.
+- Operation state belongs in `WriteContext`, `ReadContext`, and `CopyContext`,
not in serializer
+ fields.
diff --git a/docs/guide/java/schema-evolution.md
b/docs/guide/java/schema-evolution.md
index c03b52d9c2..0ea207fa27 100644
--- a/docs/guide/java/schema-evolution.md
+++ b/docs/guide/java/schema-evolution.md
@@ -76,13 +76,13 @@ Fory supports sharing type metadata (class name, field
name, final field type in
// .withMetaShare(true)
// Not thread-safe fory.
-MetaContext context = xxx;
-fory.getSerializationContext().setMetaContext(context);
+MetaWriteContext writeContext = xxx;
+fory.setMetaWriteContext(writeContext);
byte[] bytes = fory.serialize(o);
// Not thread-safe fory.
-MetaContext context = xxx;
-fory.getSerializationContext().setMetaContext(context);
+MetaReadContext readContext = xxx;
+fory.setMetaReadContext(readContext);
fory.deserialize(bytes);
```
@@ -92,7 +92,7 @@ fory.deserialize(bytes);
// Thread-safe fory
byte[] serialized = fory.execute(
f -> {
- f.getSerializationContext().setMetaContext(context);
+ f.setMetaWriteContext(writeContext);
return f.serialize(beanA);
}
);
@@ -100,13 +100,17 @@ byte[] serialized = fory.execute(
// Thread-safe fory
Object newObj = fory.execute(
f -> {
- f.getSerializationContext().setMetaContext(context);
+ f.setMetaReadContext(readContext);
return f.deserialize(serialized);
}
);
```
-**Note**: `MetaContext` is not thread-safe and cannot be reused across
instances of Fory or multiple threads. `buildThreadSafeFory()` is pooled, so
create a fresh `MetaContext` for each borrow unless you keep working with the
same raw `Fory` instance inside one `execute(...)` call. If you need to reuse
one `MetaContext` across multiple calls on the same worker thread, prefer
`buildThreadLocalFory()`.
+**Note**: `MetaWriteContext` and `MetaReadContext` are not thread-safe and
cannot be reused across
+instances of Fory or multiple threads. In cases of multi-threading, a separate
pair of meta
+contexts must be created for each Fory instance. If you need a different
classloader, create a
+separate `Fory` or `ThreadSafeFory` configured with that loader instead of
switching loaders on an
+existing instance.
For more details, please refer to the [Meta Sharing
specification](https://fory.apache.org/docs/specification/fory_java_serialization_spec#meta-share).
diff --git a/docs/guide/java/troubleshooting.md
b/docs/guide/java/troubleshooting.md
index 7d49973cc0..ec0e778bed 100644
--- a/docs/guide/java/troubleshooting.md
+++ b/docs/guide/java/troubleshooting.md
@@ -147,7 +147,7 @@ Fory fory = Fory.builder()
**Solution**: Register a custom serializer:
```java
-fory.registerSerializer(MyClass.class, new MyClassSerializer(fory));
+fory.registerSerializer(MyClass.class, new
MyClassSerializer(fory.getTypeResolver()));
```
## Performance Issues
diff --git a/docs/guide/java/type-registration.md
b/docs/guide/java/type-registration.md
index d24cbda5bd..7e82572da2 100644
--- a/docs/guide/java/type-registration.md
+++ b/docs/guide/java/type-registration.md
@@ -76,12 +76,8 @@ Fory provides a `org.apache.fory.resolver.AllowListChecker`
which is an allowed/
```java
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.STRICT);
-ThreadSafeFory fory = new ThreadLocalFory(builder -> {
- Fory f = builder.requireClassRegistration(true).build();
- f.getTypeResolver().setTypeChecker(checker);
- checker.addListener((ClassResolver) f.getTypeResolver());
- return f;
-});
+ThreadSafeFory fory =
Fory.builder().requireClassRegistration(false).buildThreadSafeFory();
+fory.setTypeChecker(checker);
checker.allowClass("org.example.*");
```
diff --git a/docs/guide/kotlin/fory-creation.md
b/docs/guide/kotlin/fory-creation.md
index 9139346532..bb82ac8728 100644
--- a/docs/guide/kotlin/fory-creation.md
+++ b/docs/guide/kotlin/fory-creation.md
@@ -63,18 +63,14 @@ For multi-threaded applications, use `ThreadSafeFory`:
```kotlin
import org.apache.fory.Fory
import org.apache.fory.ThreadSafeFory
-import org.apache.fory.ThreadLocalFory
import org.apache.fory.serializer.kotlin.KotlinSerializers
object ForyHolder {
- val fory: ThreadSafeFory = ThreadLocalFory { classLoader ->
- Fory.builder()
- .withClassLoader(classLoader)
- .requireClassRegistration(true)
- .build().also {
- KotlinSerializers.registerSerializers(it)
- }
- }
+ val fory: ThreadSafeFory = Fory.builder()
+ .requireClassRegistration(true)
+ .buildThreadSafeFory().also {
+ KotlinSerializers.registerSerializers(it)
+ }
}
```
diff --git a/docs/guide/scala/fory-creation.md
b/docs/guide/scala/fory-creation.md
index 91473f1507..fecfd02241 100644
--- a/docs/guide/scala/fory-creation.md
+++ b/docs/guide/scala/fory-creation.md
@@ -98,19 +98,16 @@ object ForyHolder {
For multi-threaded applications, use `ThreadSafeFory`:
```scala
+import org.apache.fory.Fory
import org.apache.fory.ThreadSafeFory
-import org.apache.fory.ThreadLocalFory
import org.apache.fory.serializer.scala.ScalaSerializers
object ForyHolder {
- val fory: ThreadSafeFory = new ThreadLocalFory(classLoader => {
- val f = Fory.builder()
- .withScalaOptimizationEnabled(true)
- .withClassLoader(classLoader)
- .build()
- ScalaSerializers.registerSerializers(f)
- f
- })
+ val fory: ThreadSafeFory = Fory.builder()
+ .withScalaOptimizationEnabled(true)
+ .buildThreadSafeFory()
+
+ ScalaSerializers.registerSerializers(fory)
}
```
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]