#707: HibernateProxyConverter.java

projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/HibernateProxyConverter.java XStream converter — unwraps Hibernate lazy-loading proxies to their real entity instances during marshalling, projectforge-business/src/main/java/org/projectforge/framework/persistence/xstream/HibernateProxyConverter.java 63 lines · 31 code · 25 comments · 7 blank
Extends XStream's ReflectionConverter to intercept any object that implements org.hibernate.proxy.HibernateProxy before XStream's reflection-based serialization kicks in. When a Hibernate proxy is encountered, it calls getHibernateLazyInitializer().getImplementation() to extract the underlying entity instance, then delegates marshalling to the appropriate converter for the real class. Without this converter, XStream would serialize the Javassist/CGLIB-generated proxy object (a meaningless shell containing only a lazy initializer), resulting in empty or broken XML output for any lazily-loaded entity association.

Architecture

Class Hierarchy

HibernateProxyConverter extends com.thoughtworks.xstream.converters.reflection.ReflectionConverter, which is XStream's default converter for objects that don't have a more specific converter. It uses reflection to walk fields and serialize them. By extending this base class, the converter automatically inherits all the standard reflection-based serialization behavior — it only overrides the proxy-related interception points.

Fields

FieldTypePurpose
converterLookupConverterLookupReference to XStream's converter registry. Used at marshal time to delegate to the correct converter for the unwrapped entity's real class. Set via constructor injection.

canConvert(Class clazz)

Returns true for any class that HibernateProxy.class.isAssignableFrom(clazz). This matches all Javassist/CGLIB-generated Hibernate proxy classes (which implement the HibernateProxy interface). When this returns true, XStream routes marshalling through this converter instead of the default ReflectionConverter.

Important: This converter must be registered before the default ReflectionConverter in XStream's converter chain, otherwise the default converter would claim the proxy first (since proxies are regular classes) and serialize the proxy internals instead of the real entity.

marshal(Object, HierarchicalStreamWriter, MarshallingContext)

The core unwrapping logic:

  1. Type check: Tests item instanceof HibernateProxy.
  2. Unwrap: Calls ((HibernateProxy) arg0).getHibernateLazyInitializer().getImplementation() to get the real entity. The LazyInitializer.getImplementation() call forces initialization of the proxy — this triggers a database query if the entity hasn't been loaded yet.
  3. Delegate: Uses converterLookup.lookupConverterForType(item.getClass()) to find the appropriate converter for the real entity class, then calls converter.marshal(item, writer, context). This ensures the real entity is serialized using the correct converter (potentially this same HibernateProxyConverter if the real entity is also a proxy, or the standard reflection converter otherwise).

No unmarshal() Override

This converter does not override unmarshal(). During deserialization, XStream creates standard Java objects — there is no need to create Hibernate proxies from XML. The deserialized fields are set on the entity via reflection, and Hibernate wraps them when the entity is later persisted or merged.

Integration Points

Design Decisions & Gotchas

Git History

868d6abb7 2025 -> 2026
63081666f Source file headers: 2024-> 2025.
b6092df09 Copyright 2023 -> 2024
ab45d51fa Copyright 2001-2022 -> 2001-2023.
5f7ef41b8 Copyright 2021 -> 2022
ceb63e8a1 Source code header: (C) 2001-2021.
7c79f1922 Copyright of source header -> 2020.
32f634b88 Optimize imports
dd5ca38ac CopyRight of all java file-header updated or created.
9ebb88522 Initial commit