Skip to content

Fixes #5093: make BeanDescription construction lazy to defer/avoid some introspection #5095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Versions: 3.x (for earlier see VERSION-2.x)
=== Releases ===
------------------------------------------------------------------------

3.0.0-rc4 (not yet released)

#5093: Change the way `BeanDescription` passed during serializer construction
to use `Supplier`

3.0.0-rc3 (13-Apr-2025)

#4603: Keep full stacktrace when re-throwing exception with
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/tools/jackson/databind/BeanDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,39 @@ public AnnotatedMember findJsonKeyAccessor() {
* global default settings.
*/
public abstract Class<?>[] findDefaultViews();


/**
* Base implementation for lazily-constructed suppliers for {@link BeanDescription} instances.
*/
public static abstract class Supplier implements java.util.function.Supplier<BeanDescription>
{
private final JavaType _type;

private transient BeanDescription _beanDesc;

protected Supplier(JavaType type) {
_type = type;
}

public JavaType getType() { return _type; }

public Class<?> getBeanClass() { return _type.getRawClass(); }

public boolean isRecordType() { return _type.isRecordType(); }

public AnnotatedClass getClassInfo() {
return get().getClassInfo();
}

@Override
public BeanDescription get() {
if (_beanDesc == null) {
_beanDesc = _construct(_type);
}
return _beanDesc;
}

protected abstract BeanDescription _construct(JavaType forType);
}
}
15 changes: 15 additions & 0 deletions src/main/java/tools/jackson/databind/DatabindContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,15 @@ protected abstract DatabindException invalidTypeIdException(JavaType baseType, S
*/
public abstract BeanDescription introspectBeanDescription(JavaType type);

public BeanDescription.Supplier lazyIntrospectBeanDescription(JavaType type) {
return new BeanDescription.Supplier(type) {
@Override
public BeanDescription _construct(JavaType forType) {
return introspectBeanDescription(forType);
}
};
}

public AnnotatedClass introspectClassAnnotations(JavaType type) {
return classIntrospector().introspectClassAnnotations(type);
}
Expand Down Expand Up @@ -430,6 +439,12 @@ public abstract <T> T reportBadTypeDefinition(BeanDescription bean,
String msg, Object... msgArgs)
throws DatabindException;

public <T> T reportBadTypeDefinition(BeanDescription.Supplier beanDescRef,
String msg, Object... msgArgs)
throws DatabindException {
return reportBadTypeDefinition(beanDescRef.get(), msg, msgArgs);
}

/*
/**********************************************************************
/* Helper methods
Expand Down
28 changes: 14 additions & 14 deletions src/main/java/tools/jackson/databind/SerializationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -959,13 +959,13 @@ protected ValueSerializer<Object> _createAndCacheUntypedSerializer(Class<?> rawT
JavaType fullType)
{
// Important: must introspect all annotations, not just class
BeanDescription beanDesc = introspectBeanDescription(fullType);
BeanDescription.Supplier beanDescRef = lazyIntrospectBeanDescription(fullType);
ValueSerializer<Object> ser;
try {
ser = _serializerFactory.createSerializer(this, fullType, beanDesc, null);
ser = _serializerFactory.createSerializer(this, fullType, beanDescRef, null);
} catch (IllegalArgumentException iae) {
// We better only expose checked exceptions, since those are what caller is expected to handle
reportBadTypeDefinition(beanDesc, ClassUtil.exceptionMessage(iae));
reportBadTypeDefinition(beanDescRef, ClassUtil.exceptionMessage(iae));
ser = null; // never gets here
}
// Always cache -- and in this case both for raw and full type
Expand All @@ -976,10 +976,10 @@ protected ValueSerializer<Object> _createAndCacheUntypedSerializer(Class<?> rawT
protected ValueSerializer<Object> _createAndCacheUntypedSerializer(JavaType type)
{
// Important: must introspect all annotations, not just class
BeanDescription beanDesc = introspectBeanDescription(type);
BeanDescription.Supplier beanDescRef = lazyIntrospectBeanDescription(type);
ValueSerializer<Object> ser;
try {
ser = _serializerFactory.createSerializer(this, type, beanDesc, null);
ser = _serializerFactory.createSerializer(this, type, beanDescRef, null);
} catch (IllegalArgumentException iae) {
// We better only expose checked exceptions, since those are what caller is expected to handle
throw _mappingProblem(iae, ClassUtil.exceptionMessage(iae));
Expand All @@ -996,10 +996,10 @@ protected ValueSerializer<Object> _createAndCacheUntypedSerializer(JavaType type
protected ValueSerializer<Object> _createAndCachePropertySerializer(Class<?> rawType,
JavaType fullType, BeanProperty prop)
{
BeanDescription beanDesc = introspectBeanDescription(fullType);
BeanDescription.Supplier beanDescRef = lazyIntrospectBeanDescription(fullType);
ValueSerializer<Object> ser;
try {
ser = _serializerFactory.createSerializer(this, fullType, beanDesc, null);
ser = _serializerFactory.createSerializer(this, fullType, beanDescRef, null);
} catch (IllegalArgumentException iae) {
throw _mappingProblem(iae, ClassUtil.exceptionMessage(iae));
}
Expand All @@ -1008,7 +1008,7 @@ protected ValueSerializer<Object> _createAndCachePropertySerializer(Class<?> raw
if (prop == null) {
return ser;
}
return _checkShapeShifting(fullType, beanDesc, prop, ser);
return _checkShapeShifting(fullType, beanDescRef, prop, ser);
}

/**
Expand All @@ -1018,10 +1018,10 @@ protected ValueSerializer<Object> _createAndCachePropertySerializer(Class<?> raw
protected ValueSerializer<Object> _createAndCachePropertySerializer(JavaType type,
BeanProperty prop)
{
BeanDescription beanDesc = introspectBeanDescription(type);
BeanDescription.Supplier beanDescRef = lazyIntrospectBeanDescription(type);
ValueSerializer<Object> ser;
try {
ser = _serializerFactory.createSerializer(this, type, beanDesc, null);
ser = _serializerFactory.createSerializer(this, type, beanDescRef, null);
} catch (IllegalArgumentException iae) {
throw _mappingProblem(iae, ClassUtil.exceptionMessage(iae));
}
Expand All @@ -1030,12 +1030,12 @@ protected ValueSerializer<Object> _createAndCachePropertySerializer(JavaType typ
if (prop == null) {
return ser;
}
return _checkShapeShifting(type, beanDesc, prop, ser);
return _checkShapeShifting(type, beanDescRef, prop, ser);
}

@SuppressWarnings("unchecked")
private ValueSerializer<Object> _checkShapeShifting(JavaType type, BeanDescription beanDesc,
BeanProperty prop, ValueSerializer<?> ser)
private ValueSerializer<Object> _checkShapeShifting(JavaType type,
BeanDescription.Supplier beanDescRef, BeanProperty prop, ValueSerializer<?> ser)
{
JsonFormat.Value overrides = prop.findFormatOverrides(_config);
if (overrides != null) {
Expand All @@ -1045,7 +1045,7 @@ private ValueSerializer<Object> _checkShapeShifting(JavaType type, BeanDescripti
ser = ser2;
} else {
// But if not, we need to re-create it via factory
ser = _serializerFactory.createSerializer(this, type, beanDesc, overrides);
ser = _serializerFactory.createSerializer(this, type, beanDescRef, overrides);
}
}
return (ValueSerializer<Object>) ser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public JavaTimeSerializerModifier() { }

@Override
public ValueSerializer<?> modifyEnumSerializer(SerializationConfig config, JavaType valueType,
BeanDescription beanDesc, ValueSerializer<?> serializer) {
BeanDescription.Supplier beanDesc, ValueSerializer<?> serializer) {
if (valueType.hasRawClass(Month.class)) {
return new OneBasedMonthSerializer(serializer);
}
Expand Down
30 changes: 19 additions & 11 deletions src/main/java/tools/jackson/databind/module/SimpleSerializers.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import tools.jackson.databind.type.CollectionType;
import tools.jackson.databind.type.MapLikeType;
import tools.jackson.databind.type.MapType;
import tools.jackson.databind.type.ReferenceType;

/**
* Simple implementation {@link Serializers} which allows registration of
Expand Down Expand Up @@ -102,7 +103,7 @@ public SimpleSerializers addSerializers(List<ValueSerializer<?>> sers) {

@Override
public ValueSerializer<?> findSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc, JsonFormat.Value formatOverrides)
JavaType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides)
{
Class<?> cls = type.getRawClass();
ClassKey key = new ClassKey(cls);
Expand Down Expand Up @@ -163,39 +164,46 @@ public ValueSerializer<?> findSerializer(SerializationConfig config,

@Override
public ValueSerializer<?> findArraySerializer(SerializationConfig config,
ArrayType type, BeanDescription beanDesc, JsonFormat.Value formatOverrides,
ArrayType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer) {
return findSerializer(config, type, beanDesc, formatOverrides);
return findSerializer(config, type, beanDescRef, formatOverrides);
}

@Override
public ValueSerializer<?> findCollectionSerializer(SerializationConfig config,
CollectionType type, BeanDescription beanDesc, JsonFormat.Value formatOverrides,
CollectionType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer) {
return findSerializer(config, type, beanDesc, formatOverrides);
return findSerializer(config, type, beanDescRef, formatOverrides);
}

@Override
public ValueSerializer<?> findCollectionLikeSerializer(SerializationConfig config,
CollectionLikeType type, BeanDescription beanDesc, JsonFormat.Value formatOverrides,
CollectionLikeType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer) {
return findSerializer(config, type, beanDesc, formatOverrides);
return findSerializer(config, type, beanDescRef, formatOverrides);
}

@Override
public ValueSerializer<?> findMapSerializer(SerializationConfig config,
MapType type, BeanDescription beanDesc, JsonFormat.Value formatOverrides,
MapType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
ValueSerializer<Object> keySerializer,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer) {
return findSerializer(config, type, beanDesc, formatOverrides);
return findSerializer(config, type, beanDescRef, formatOverrides);
}

@Override
public ValueSerializer<?> findMapLikeSerializer(SerializationConfig config,
MapLikeType type, BeanDescription beanDesc, JsonFormat.Value formatOverrides,
MapLikeType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
ValueSerializer<Object> keySerializer,
TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer) {
return findSerializer(config, type, beanDesc, formatOverrides);
return findSerializer(config, type, beanDescRef, formatOverrides);
}

@Override
public ValueSerializer<?> findReferenceSerializer(SerializationConfig config,
ReferenceType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
TypeSerializer contentTypeSerializer, ValueSerializer<Object> contentValueSerializer) {
return findSerializer(config, type, beanDescRef, formatOverrides);
}

/*
Expand Down
Loading