This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch 3900-immutable.config
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 1f37ddb7d7113f16240816607651119d7b879825
Author: Andi Huber <[email protected]>
AuthorDate: Fri Aug 1 14:55:53 2025 +0200

    CAUSEWAY-3900: lazy spec loader binding - apps back to green
---
 .../causeway/applib/services/jaxb/JaxbService.java | 52 ++++++------
 .../value/semantics/ValueSemanticsAbstract.java    | 15 ++--
 .../bootstrap/BSMenuBars_roundtrip_Test.java       |  2 +-
 .../applib/services/jaxb/JaxbServiceTest.java      |  4 +-
 .../services/grid/GridSystemServiceAbstract.java   |  7 +-
 .../grid/bootstrap/GridSystemServiceBootstrap.java | 12 +--
 .../metamodel/MetaModelServiceDefault.java         | 40 ++++-----
 .../valuesemantics/BigDecimalValueSemantics.java   |  6 +-
 .../valuesemantics/EnumValueSemantics.java         |  4 +-
 .../services/menubars/BSMenuBarsTest.java          |  3 +-
 ...anticsProvider_configureDecimalFormat_Test.java |  6 +-
 .../mmtestsupport/MetaModelContext_forTesting.java |  5 +-
 .../bookmarks/BookmarkServiceDefault.java          | 22 +++--
 .../factory/FactoryServiceDefault.java             | 16 ++--
 .../runtimeservices/jaxb/JaxbServiceDefault.java   | 94 +++++++++++-----------
 .../session/InteractionServiceDefault.java         |  8 +-
 16 files changed, 155 insertions(+), 141 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
index 4ffbbd8373a..366253f22b2 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/services/jaxb/JaxbService.java
@@ -19,11 +19,14 @@
 package org.apache.causeway.applib.services.jaxb;
 
 import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 import jakarta.xml.bind.JAXBContext;
 import jakarta.xml.bind.Marshaller;
 import jakarta.xml.bind.Unmarshaller;
 
+import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
 
 import org.apache.causeway.applib.domain.DomainObjectList;
@@ -31,7 +34,6 @@
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.io.JaxbUtils;
 
-import org.jspecify.annotations.NonNull;
 import lombok.SneakyThrows;
 
 /**
@@ -100,7 +102,23 @@ Map<String, String> toXsd(
             CausewaySchemas causewaySchemas);
 
     /** 'Simple' because no injection point resolving or advanced {@link 
DomainObjectList} handling. */
-    class Simple implements JaxbService {
+    static JaxbService simple() {
+        return new JaxbServiceInternal(JaxbServiceInternal.Config.simple());
+    }
+
+    record JaxbServiceInternal(
+        Config config
+        ) implements JaxbService {
+
+        public record Config(
+            Consumer<Marshaller> marshallerConfigurer,
+            Consumer<Unmarshaller> unmarshallerConfigurer,
+            Function<DomainObjectList, JAXBContext> 
jaxbContextForListProvider) {
+            /** 'Simple' because no injection point resolving or advanced 
{@link DomainObjectList} handling. */
+            static Config simple() {
+                return new Config(__->{}, __->{}, 
__->JaxbUtils.jaxbContextFor(DomainObjectList.class, true));
+            }
+        }
 
         @Override
         @Nullable
@@ -109,14 +127,13 @@ public final <T> T fromXml(
                 final @Nullable String xml,
                 final @Nullable Map<String, Object> unmarshallerProperties) {
 
-            if (xml == null) {
-                return null;
-            }
+            if (xml == null) return null;
+
             return JaxbUtils.tryRead(domainClass, xml, opts->{
                 for (var entry : _NullSafe.entrySet(unmarshallerProperties)) {
                     opts.property(entry.getKey(), entry.getValue());
                 }
-                opts.unmarshallerConfigurer(this::configure);
+                opts.unmarshallerConfigurer(config.unmarshallerConfigurer());
                 return opts;
             })
             .ifFailureFail()
@@ -129,14 +146,14 @@ public final String toXml(
                 final @Nullable Map<String, Object> marshallerProperties) {
 
             var jaxbContext = domainObject instanceof DomainObjectList
-                    ? jaxbContextForList((DomainObjectList)domainObject)
+                    ? 
config.jaxbContextForListProvider().apply((DomainObjectList)domainObject)
                     : JaxbUtils.jaxbContextFor(domainObject.getClass(), true);
 
             return Try.call(()->JaxbUtils.toStringUtf8(domainObject, opts->{
                 for (var entry : _NullSafe.entrySet(marshallerProperties)) {
                     opts.property(entry.getKey(), entry.getValue());
                 }
-                opts.marshallerConfigurer(this::configure);
+                opts.marshallerConfigurer(config.marshallerConfigurer());
                 opts.jaxbContextOverride(jaxbContext);
                 return opts;
             }))
@@ -144,25 +161,6 @@ public final String toXml(
             .getValue().orElse(null);
         }
 
-        /**
-         * Optional hook
-         */
-        protected JAXBContext jaxbContextForList(final @NonNull 
DomainObjectList list) {
-            return JaxbUtils.jaxbContextFor(DomainObjectList.class, true);
-        }
-
-        /**
-         * Optional hook
-         */
-        protected void configure(final Unmarshaller unmarshaller) {
-        }
-
-        /**
-         * Optional hook
-         */
-        protected void configure(final Marshaller marshaller) {
-        }
-
         @Override
         @SneakyThrows
         public final Map<String, String> toXsd(
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java
index 54ad4494328..79d584225db 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueSemanticsAbstract.java
@@ -33,9 +33,12 @@
 import java.util.function.Function;
 import java.util.function.Supplier;
 
-import org.springframework.beans.factory.annotation.Autowired;
+import jakarta.inject.Provider;
+
+import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.apache.causeway.applib.annotation.TimePrecision;
 import 
org.apache.causeway.applib.exceptions.recoverable.TextEntryParseException;
 import org.apache.causeway.applib.locale.UserLocale;
@@ -56,8 +59,6 @@
 import org.apache.causeway.schema.common.v2.ValueType;
 import org.apache.causeway.schema.common.v2.ValueWithTypeDto;
 
-import org.jspecify.annotations.NonNull;
-
 /**
  * @since 2.x {@index}
  */
@@ -387,19 +388,19 @@ protected DateTimeFormatter getTemporalIsoFormat(
     // -- TRANSLATION SUPPORT
 
     @Autowired(required = false) // nullable (JUnit support)
-    protected TranslationService translationService;
+    protected Provider<TranslationService> translationService;
     protected String translate(final String text) {
         return translationService!=null
-                ? translationService.translate(TranslationContext.empty(), 
text)
+                ? 
translationService.get().translate(TranslationContext.empty(), text)
                 : text;
     }
 
     // -- PLACEHOLDER RENDERING
 
     @Autowired(required = false) // nullable (JUnit support)
-    private Optional<PlaceholderRenderService> placeholderRenderService = 
Optional.empty();
+    private Optional<Provider<PlaceholderRenderService>> 
placeholderRenderService = Optional.empty();
     protected PlaceholderRenderService getPlaceholderRenderService() {
-        return 
placeholderRenderService.orElseGet(PlaceholderRenderService::fallback);
+        return 
placeholderRenderService.map(Provider::get).orElseGet(PlaceholderRenderService::fallback);
     }
 
 }
diff --git 
a/api/applib/src/test/java/org/apache/causeway/applib/layout/menubars/bootstrap/BSMenuBars_roundtrip_Test.java
 
b/api/applib/src/test/java/org/apache/causeway/applib/layout/menubars/bootstrap/BSMenuBars_roundtrip_Test.java
index 128188d7f1e..fdaa71a1349 100644
--- 
a/api/applib/src/test/java/org/apache/causeway/applib/layout/menubars/bootstrap/BSMenuBars_roundtrip_Test.java
+++ 
b/api/applib/src/test/java/org/apache/causeway/applib/layout/menubars/bootstrap/BSMenuBars_roundtrip_Test.java
@@ -34,7 +34,7 @@ public class BSMenuBars_roundtrip_Test {
 
     @BeforeEach
     public void setUp() throws Exception {
-        jaxbService = new JaxbService.Simple();
+        jaxbService = JaxbService.simple();
     }
 
     @AfterEach
diff --git 
a/api/applib/src/test/java/org/apache/causeway/applib/services/jaxb/JaxbServiceTest.java
 
b/api/applib/src/test/java/org/apache/causeway/applib/services/jaxb/JaxbServiceTest.java
index 93711ff8504..0b954332864 100644
--- 
a/api/applib/src/test/java/org/apache/causeway/applib/services/jaxb/JaxbServiceTest.java
+++ 
b/api/applib/src/test/java/org/apache/causeway/applib/services/jaxb/JaxbServiceTest.java
@@ -35,12 +35,12 @@
 
 class JaxbServiceTest {
 
-    private JaxbService.Simple simple;
+    private JaxbService simple;
     private ActionInvocationDto sampleDto;
 
     @BeforeEach
     void setUp() throws Exception {
-        simple = new JaxbService.Simple();
+        simple = JaxbService.simple();
         sampleDto = getSample();
     }
 
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
index 4940ea5751b..a64db222c1a 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridSystemServiceAbstract.java
@@ -24,6 +24,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import jakarta.inject.Inject;
+import jakarta.inject.Provider;
 
 import org.apache.causeway.applib.annotation.Programmatic;
 import org.apache.causeway.applib.layout.component.ActionLayoutData;
@@ -91,7 +92,7 @@
 public abstract class GridSystemServiceAbstract<G extends 
org.apache.causeway.applib.layout.grid.Grid>
 implements GridSystemService<G> {
 
-    protected final SpecificationLoader specificationLoader;
+    protected final Provider<SpecificationLoader> specLoaderProvider;
     protected final TranslationService translationService;
     protected final JaxbService jaxbService;
     protected final MessageService messageService;
@@ -137,7 +138,7 @@ private void overwriteFacets(
             final G fcGrid,
             final Class<?> domainClass) {
 
-        var objectSpec = specificationLoader.specForTypeElseFail(domainClass);
+        var objectSpec = 
specLoaderProvider.get().specForTypeElseFail(domainClass);
 
         var oneToOneAssociationById = 
ObjectMember.mapById(objectSpec.streamProperties(MixedIn.INCLUDED));
         var oneToManyAssociationById = 
ObjectMember.mapById(objectSpec.streamCollections(MixedIn.INCLUDED));
@@ -384,7 +385,7 @@ protected static SurplusAndMissing surplusAndMissing(final 
Set<String> first, fi
     @Override
     public void complete(final G grid, final Class<?> domainClass) {
         normalize(grid, domainClass);
-        var objectSpec = specificationLoader.specForTypeElseFail(domainClass);
+        var objectSpec = 
specLoaderProvider.get().specForTypeElseFail(domainClass);
         grid.visit(MetamodelToGridOverridingVisitor.of(objectSpec));
     }
 
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
index 33389cbcb0d..dcb5b126526 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
@@ -29,6 +29,7 @@
 import jakarta.annotation.Priority;
 import jakarta.inject.Inject;
 import jakarta.inject.Named;
+import jakarta.inject.Provider;
 
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Lazy;
@@ -67,7 +68,6 @@
 import org.apache.causeway.core.config.CausewayConfiguration;
 import org.apache.causeway.core.config.environment.CausewaySystemEnvironment;
 import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import 
org.apache.causeway.core.metamodel.facets.actions.position.ActionPositionFacet;
 import 
org.apache.causeway.core.metamodel.facets.members.layout.group.GroupIdAndName;
 import 
org.apache.causeway.core.metamodel.facets.members.layout.group.LayoutGroupFacet;
@@ -79,6 +79,7 @@
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
 import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 
 import static org.apache.causeway.commons.internal.base._NullSafe.stream;
 
@@ -123,14 +124,15 @@ public static interface FallbackLayoutDataSource {
 
     @Inject
     public GridSystemServiceBootstrap(
-            final MetaModelContext metaModelContext,
+            final CausewayConfiguration causewayConfiguration,
+            final Provider<SpecificationLoader> specLoaderProvider,
             final TranslationService translationService,
             final JaxbService jaxbService,
             final MessageService messageService,
             final CausewaySystemEnvironment causewaySystemEnvironment,
             final List<FallbackLayoutDataSource> fallbackLayoutDataSources) {
-        super(metaModelContext.getSpecificationLoader(), translationService, 
jaxbService, messageService, causewaySystemEnvironment);
-        this.config = metaModelContext.getConfiguration();
+        super(specLoaderProvider, translationService, jaxbService, 
messageService, causewaySystemEnvironment);
+        this.config = causewayConfiguration;
         this.fallbackLayoutDataSources = 
Can.ofCollection(fallbackLayoutDataSources);
     }
 
@@ -240,7 +242,7 @@ protected boolean validateAndNormalize(
             final Class<?> domainClass) {
 
         var bsGrid = (BSGrid) grid;
-        var objectSpec = specificationLoader.specForTypeElseFail(domainClass);
+        var objectSpec = 
specLoaderProvider.get().specForTypeElseFail(domainClass);
 
         var oneToOneAssociationById = 
ObjectMember.mapById(objectSpec.streamProperties(MixedIn.INCLUDED));
         var oneToManyAssociationById = 
ObjectMember.mapById(objectSpec.streamCollections(MixedIn.INCLUDED));
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java
index a1eff732d36..12da736ef91 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/metamodel/MetaModelServiceDefault.java
@@ -27,11 +27,13 @@
 import java.util.stream.Collectors;
 
 import jakarta.annotation.Priority;
-import jakarta.inject.Inject;
 import jakarta.inject.Named;
+import jakarta.inject.Provider;
 
-import org.springframework.beans.factory.annotation.Qualifier;
+import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
+
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import org.apache.causeway.applib.annotation.PriorityPrecedence;
@@ -63,8 +65,6 @@
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import org.apache.causeway.schema.metamodel.v2.MetamodelDto;
 
-import org.jspecify.annotations.NonNull;
-
 /**
  * Default implementation of {@link MetaModelService}.
  *
@@ -74,21 +74,25 @@
 @Named(CausewayModuleCoreMetamodel.NAMESPACE + ".MetaModelServiceDefault")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-public class MetaModelServiceDefault implements MetaModelService {
+public record MetaModelServiceDefault(
+    Provider<SpecificationLoader> specificationLoaderProvider,
+    GridService gridService
+    ) implements MetaModelService {
 
-    @Inject private SpecificationLoader specificationLoader;
-    @Inject private GridService gridService;
+    private SpecificationLoader specificationLoader() {
+        return specificationLoaderProvider.get();
+    }
 
     @Override
     public Optional<LogicalType> lookupLogicalTypeByName(final @Nullable 
String logicalTypeName) {
-        return specificationLoader.specForLogicalTypeName(logicalTypeName)
+        return specificationLoader().specForLogicalTypeName(logicalTypeName)
                 .map(ObjectSpecification::logicalType);
     }
 
     @Override
     public Can<LogicalType> logicalTypeAndAliasesFor(final LogicalType 
logicalType) {
         Set<LogicalType> logicalTypes = new TreeSet<>();
-        specificationLoader.specForLogicalType(logicalType)
+        specificationLoader().specForLogicalType(logicalType)
                 .ifPresent(objectSpecification -> {
                     logicalTypes.add(logicalType);
                     
objectSpecification.getAliases().stream().forEach(logicalTypes::add);
@@ -105,7 +109,7 @@ public Can<LogicalType> logicalTypeAndAliasesFor(final 
@Nullable String logicalT
 
     @Override
     public Optional<LogicalType> lookupLogicalTypeByClass(final @Nullable 
Class<?> domainType) {
-        return specificationLoader.specForType(domainType)
+        return specificationLoader().specForType(domainType)
                 .map(ObjectSpecification::logicalType);
     }
 
@@ -113,13 +117,13 @@ public Optional<LogicalType> 
lookupLogicalTypeByClass(final @Nullable Class<?> d
     public void rebuild(final Class<?> domainType) {
 
         gridService.remove(domainType);
-        specificationLoader.reloadSpecification(domainType);
+        specificationLoader().reloadSpecification(domainType);
     }
 
     @Override
     public DomainModel getDomainModel() {
 
-        var specifications = specificationLoader.snapshotSpecifications();
+        var specifications = specificationLoader().snapshotSpecifications();
 
         final List<DomainMember> rows = _Lists.newArrayList();
         for (final ObjectSpecification spec : specifications) {
@@ -172,7 +176,7 @@ public BeanSort sortOf(
         if(domainType == null) {
             return null;
         }
-        final ObjectSpecification objectSpec = 
specificationLoader.specForType(domainType).orElse(null);
+        final ObjectSpecification objectSpec = 
specificationLoader().specForType(domainType).orElse(null);
         if(objectSpec == null) {
             return BeanSort.UNKNOWN;
         }
@@ -200,7 +204,7 @@ public BeanSort sortOf(final Bookmark bookmark, final Mode 
mode) {
         final Class<?> domainType;
         switch (mode) {
         case RELAXED:
-            domainType = specificationLoader.specForBookmark(bookmark)
+            domainType = specificationLoader().specForBookmark(bookmark)
                 .map(ObjectSpecification::getCorrespondingClass)
                 .orElse(null);
             break;
@@ -208,7 +212,7 @@ public BeanSort sortOf(final Bookmark bookmark, final Mode 
mode) {
         case STRICT:
             // fall through to...
         default:
-            domainType = specificationLoader.specForBookmark(bookmark)
+            domainType = specificationLoader().specForBookmark(bookmark)
                 .map(ObjectSpecification::getCorrespondingClass)
                 .orElseThrow(()->_Exceptions
                         .noSuchElement("Cannot resolve logical type name %s to 
a java class",
@@ -228,7 +232,7 @@ public CommandDtoProcessor commandDtoProcessorFor(final 
String memberIdentifier)
             return null;
         }
 
-        final ObjectSpecification spec = 
specificationLoader.specForLogicalTypeName(logicalTypeName).orElse(null);
+        final ObjectSpecification spec = 
specificationLoader().specForLogicalTypeName(logicalTypeName).orElse(null);
         if(spec == null) {
             return null;
         }
@@ -256,13 +260,13 @@ public MetamodelDto exportMetaModel(final Config config) {
         if(config.isIncludeShadowedFacets()) {
             metaModelAnnotators.add(new ShadowedFactetAttributeAnnotator(new 
ExporterConfig(){}));
         }
-        return new MetaModelExporter(specificationLoader, metaModelAnnotators)
+        return new MetaModelExporter(specificationLoader(), 
metaModelAnnotators)
                 .exportMetaModel(config);
     }
 
     @Override
     public ObjectGraph exportObjectGraph(final @NonNull BiPredicate<BeanSort, 
LogicalType> filter) {
-        var objectSpecs = specificationLoader
+        var objectSpecs = specificationLoader()
                 .snapshotSpecifications()
                 .stream()
                 .filter(spec->filter.test(spec.getBeanSort(), 
spec.logicalType()))
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemantics.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemantics.java
index 25930e7ae98..07912c73dd5 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemantics.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemantics.java
@@ -40,7 +40,7 @@
 import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.core.config.CausewayConfiguration;
-import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
+import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.util.Facets;
 import org.apache.causeway.schema.common.v2.ValueType;
 import org.apache.causeway.schema.common.v2.ValueWithTypeDto;
@@ -60,8 +60,6 @@ public class BigDecimalValueSemantics
     Renderer<BigDecimal>,
     IdStringifier.EntityAgnostic<BigDecimal> {
 
-    @Setter @Inject
-    private SpecificationLoader specificationLoader;
     @Setter @Inject
     private CausewayConfiguration causewayConfiguration;
 
@@ -149,6 +147,8 @@ public int typicalLength() {
     protected void configureDecimalFormat(
             final Context context, final DecimalFormat format, final 
FormatUsageFor usedFor) {
 
+        var specificationLoader = 
MetaModelContext.instanceElseFail().getSpecificationLoader();
+
         var bigDecimalConfig = causewayConfiguration.valueTypes().bigDecimal();
         format.setGroupingUsed(
                 usedFor == PARSING
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/EnumValueSemantics.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/EnumValueSemantics.java
index ffcb7e116ba..8b6fade9e58 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/EnumValueSemantics.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/EnumValueSemantics.java
@@ -84,7 +84,7 @@ protected EnumValueSemantics(
             final TranslationService translationService,
             final Class<T> enumClass) {
         super();
-        this.translationService = translationService;
+        this.translationService = () -> translationService;
         this.correspondingClass = enumClass;
         this.maxLength = maxLengthFor(enumClass);
         this.enumSpecLazy = _Lazy.threadSafe(()->loadEnumSpec(enumClass));
@@ -194,7 +194,7 @@ private String friendlyName(final Context context, final T 
objectAsEnum) {
             .map(MmTitleUtils::titleOf)
             .orElseGet(()->Enums.getFriendlyNameOf(objectAsEnum.name()));
 
-        return Optional.ofNullable(translationService)
+        return Optional.ofNullable(translationService.get())
                 
.map(ts->ts.translate(TranslationContext.forEnum(objectAsEnum), 
friendlyNameOfEnum))
                 .orElse(friendlyNameOfEnum);
     }
diff --git 
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/menubars/BSMenuBarsTest.java
 
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/menubars/BSMenuBarsTest.java
index c37a578e07a..0118bcaa93d 100644
--- 
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/menubars/BSMenuBarsTest.java
+++ 
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/menubars/BSMenuBarsTest.java
@@ -28,7 +28,6 @@
 import org.apache.causeway.applib.layout.menubars.bootstrap.BSMenuBars;
 import org.apache.causeway.applib.services.jaxb.CausewaySchemas;
 import org.apache.causeway.applib.services.jaxb.JaxbService;
-import org.apache.causeway.applib.services.jaxb.JaxbService.Simple;
 import org.apache.causeway.commons.internal.resources._Resources;
 
 class BSMenuBarsTest {
@@ -37,7 +36,7 @@ class BSMenuBarsTest {
 
     @BeforeEach
     void setUp() throws Exception {
-        jaxbService = new Simple() {};
+        jaxbService = JaxbService.simple();
     }
 
     @AfterEach
diff --git 
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemanticsProvider_configureDecimalFormat_Test.java
 
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemanticsProvider_configureDecimalFormat_Test.java
index 1419d21ba1c..46e9b50d664 100644
--- 
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemanticsProvider_configureDecimalFormat_Test.java
+++ 
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemanticsProvider_configureDecimalFormat_Test.java
@@ -37,6 +37,7 @@
 import org.apache.causeway.core.metamodel.spec.feature.ObjectFeature;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import org.apache.causeway.core.mmtestsupport.ConfigurationTester;
+import org.apache.causeway.core.mmtestsupport.MetaModelContext_forTesting;
 
 class BigDecimalValueSemanticsProvider_configureDecimalFormat_Test {
 
@@ -60,8 +61,11 @@ void setUpObjects() throws Exception {
         // expecting
         
Mockito.lenient().when(mockSpecificationLoader.loadFeature(mockIdentifier)).thenReturn(Optional.of(mockObjectFeature));
 
+        MetaModelContext_forTesting.builder()
+            .specificationLoader(mockSpecificationLoader)
+            .build();
+
         valueSemantics = new BigDecimalValueSemantics();
-        valueSemantics.setSpecificationLoader(mockSpecificationLoader);
     }
 
     @Test
diff --git 
a/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java
 
b/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java
index 01a0403e4c1..9f95c613236 100644
--- 
a/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java
+++ 
b/core/mmtestsupport/src/main/java/org/apache/causeway/core/mmtestsupport/MetaModelContext_forTesting.java
@@ -445,7 +445,7 @@ public WebAppContextPath getWebAppContextPath() {
     // -- LAYOUT TESTING SUPPORT
 
     @Getter(lazy = true)
-    private final JaxbService jaxbService = new JaxbService.Simple();
+    private final JaxbService jaxbService = JaxbService.simple();
 
     @Getter(lazy = true)
     private final MenuBarsService menuBarsService = createMenuBarsService();
@@ -481,7 +481,8 @@ private final GridService createGridService() {
             getGridMarshallerService(),
             List.of(
                     new GridSystemServiceBootstrap(
-                            this,
+                            getConfiguration(),
+                            ()->getSpecificationLoader(),
                             getTranslationService(),
                             getJaxbService(),
                             getMessageService(),
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
index ae0900e0558..d8a8344f6eb 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/bookmarks/BookmarkServiceDefault.java
@@ -24,8 +24,8 @@
 import java.util.stream.Collectors;
 
 import jakarta.annotation.Priority;
-import jakarta.inject.Inject;
 import jakarta.inject.Named;
+import jakarta.inject.Provider;
 
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.jspecify.annotations.Nullable;
@@ -42,7 +42,6 @@
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
@@ -58,13 +57,12 @@
 @Named(CausewayModuleCoreRuntimeServices.NAMESPACE + ".BookmarkServiceDefault")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-public class BookmarkServiceDefault implements BookmarkService {
-
-    @Inject private SpecificationLoader specificationLoader;
-    @Inject private WrapperFactory wrapperFactory;
-    @Inject private ObjectManager objectManager;
-    @Inject private MetaModelContext mmc;
-    @Inject private MetaModelService metaModelService;
+public record BookmarkServiceDefault(
+        Provider<SpecificationLoader> specificationLoaderProvider,
+        WrapperFactory wrapperFactory,
+        ObjectManager objectManager,
+        MetaModelService metaModelService
+    ) implements BookmarkService {
 
     @Override
     public Optional<Object> lookup(final @Nullable BookmarkHolder 
bookmarkHolder) {
@@ -96,7 +94,7 @@ public List<Bookmark> bookmarksFor(final Object domainObject) 
{
     @Override
     public Optional<Object> lookup(final @Nullable Bookmark bookmark) {
         try {
-            return mmc.getObjectManager().loadObject(bookmark)
+            return objectManager.loadObject(bookmark)
                     .map(ManagedObject::getPojo);
         } catch(ObjectNotFoundException ex) {
             return Optional.empty();
@@ -122,7 +120,7 @@ public Optional<Bookmark> bookmarkFor(
                 || cls==null) {
             return Optional.empty();
         }
-        return specificationLoader.specForType(cls)
+        return specificationLoaderProvider.get().specForType(cls)
                 .map(ObjectSpecification::logicalType)
                 
.map(logicalType->Bookmark.forLogicalTypeAndIdentifier(logicalType, 
identifier));
     }
@@ -135,7 +133,7 @@ public Bookmark bookmarkForElseFail(final @Nullable Object 
domainObject) {
                         ()->_Exceptions.illegalArgument(
                         "cannot create bookmark for type %s",
                         domainObject!=null
-                            ? 
specificationLoader.specForType(domainObject.getClass())
+                            ? 
specificationLoaderProvider.get().specForType(domainObject.getClass())
                                     .map(spec->spec.toString())
                                     
.orElseGet(()->domainObject.getClass().getName())
                             : "<null>"));
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/factory/FactoryServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/factory/FactoryServiceDefault.java
index 076baaefd4e..56373acbc38 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/factory/FactoryServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/factory/FactoryServiceDefault.java
@@ -21,7 +21,6 @@
 import java.util.Optional;
 
 import jakarta.annotation.Priority;
-import jakarta.inject.Inject;
 import jakarta.inject.Named;
 import jakarta.inject.Provider;
 
@@ -56,12 +55,13 @@
 @Named(CausewayModuleCoreRuntimeServices.NAMESPACE + ".FactoryServiceDefault")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-public class FactoryServiceDefault implements FactoryService {
+public record FactoryServiceDefault(
+        InteractionService interactionService, // dependsOn
+        Provider<SpecificationLoader> specificationLoaderProvider,
+        CausewaySystemEnvironment causewaySystemEnvironment,
+        Provider<ObjectLifecyclePublisher> objectLifecyclePublisherProvider)
+implements FactoryService {
 
-    @Inject InteractionService interactionService; // dependsOn
-    @Inject private SpecificationLoader specificationLoader;
-    @Inject private CausewaySystemEnvironment causewaySystemEnvironment;
-    @Inject private Provider<ObjectLifecyclePublisher> 
objectLifecyclePublisherProvider;
     private ObjectLifecyclePublisher objectLifecyclePublisher() { return 
objectLifecyclePublisherProvider.get(); }
 
     @Override
@@ -156,7 +156,7 @@ public <T> T create(final @NonNull Class<T> domainClass) {
     // -- HELPER
 
     private ObjectSpecification loadSpecElseFail(final @NonNull Class<?> type) 
{
-        return specificationLoader.specForTypeElseFail(type);
+        return specificationLoaderProvider().get().specForTypeElseFail(type);
     }
 
     /** handles injection, post-construct and publishing */
@@ -185,7 +185,7 @@ private <T> T createObject(
 
     @Override
     public <T> TreeNode<T> treeNode(T root) {
-        return TreeNode.root(root, _Casts.uncheckedCast(new 
ObjectTreeAdapter(specificationLoader)));
+        return TreeNode.root(root, _Casts.uncheckedCast(new 
ObjectTreeAdapter(specificationLoaderProvider().get())));
     }
 
 }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
index 662741b2abe..89d1011fb19 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/jaxb/JaxbServiceDefault.java
@@ -18,16 +18,16 @@
  */
 package org.apache.causeway.core.runtimeservices.jaxb;
 
+import java.util.Map;
+
 import jakarta.annotation.Priority;
-import jakarta.inject.Inject;
 import jakarta.inject.Named;
-import jakarta.xml.bind.JAXBContext;
-import jakarta.xml.bind.Marshaller;
-import jakarta.xml.bind.Unmarshaller;
+import jakarta.inject.Provider;
 import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
-import org.apache.causeway.applib.services.jaxb.JaxbService;
+import org.jspecify.annotations.Nullable;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
@@ -36,17 +36,15 @@
 import org.apache.causeway.applib.jaxb.PersistentEntitiesAdapter;
 import org.apache.causeway.applib.jaxb.PersistentEntityAdapter;
 import org.apache.causeway.applib.services.inject.ServiceInjector;
-import org.apache.causeway.applib.services.jaxb.JaxbService.Simple;
+import org.apache.causeway.applib.services.jaxb.CausewaySchemas;
+import org.apache.causeway.applib.services.jaxb.JaxbService;
+import org.apache.causeway.commons.functional.Try;
 import org.apache.causeway.commons.internal.context._Context;
 import org.apache.causeway.commons.io.JaxbUtils;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import 
org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;
 
-import org.jspecify.annotations.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-
 /**
  * Default implementation of {@link JaxbService}.
  * @since 2.0 {@index}
@@ -55,50 +53,56 @@
 @Named(CausewayModuleCoreRuntimeServices.NAMESPACE + ".JaxbServiceDefault")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
-public class JaxbServiceDefault extends Simple {
+public record JaxbServiceDefault(
+    JaxbService delegate) implements JaxbService {
 
-    private final ServiceInjector serviceInjector;
-    private final SpecificationLoader specLoader;
+    @Autowired
+    public JaxbServiceDefault(
+        ServiceInjector serviceInjector,
+        Provider<SpecificationLoader> specLoaderProvider) {
+        this(new JaxbService.JaxbServiceInternal(new 
JaxbService.JaxbServiceInternal.Config(
+            marshaller->{
+                marshaller.setAdapter(PersistentEntityAdapter.class,
+                    serviceInjector.injectServicesInto(new 
PersistentEntityAdapter()));
+                marshaller.setAdapter(PersistentEntitiesAdapter.class,
+                        serviceInjector.injectServicesInto(new 
PersistentEntitiesAdapter()));
+            },
+            unmarshaller->{
+                unmarshaller.setAdapter(PersistentEntityAdapter.class,
+                    serviceInjector.injectServicesInto(new 
PersistentEntityAdapter()));
+                unmarshaller.setAdapter(PersistentEntitiesAdapter.class,
+                        serviceInjector.injectServicesInto(new 
PersistentEntitiesAdapter()));
+            },
+            domainObjectList->{
+                var elementCls = 
Try.call(()->_Context.loadClass(domainObjectList.getElementTypeFqcn()))
+                    .getValue() // silently ignore class loading issues
+                    .orElse(null);
+                var elementType = specLoaderProvider.get()
+                    .specForType(elementCls)
+                    .map(ObjectSpecification::getCorrespondingClass)
+                    .orElse(null);
+                if (elementType!=null
+                        && elementType.getAnnotation(XmlJavaTypeAdapter.class) 
== null) {
+                    return JaxbUtils.jaxbContextFor(DomainObjectList.class, 
elementType);
+                } else {
+                    return JaxbUtils.jaxbContextFor(DomainObjectList.class, 
true);
+                }
+            })));
+    }
 
-    @SneakyThrows
     @Override
-    protected JAXBContext jaxbContextForList(final @NonNull DomainObjectList 
domainObjectList) {
-        var elementType = specLoader
-                
.specForType(_Context.loadClass(domainObjectList.getElementTypeFqcn()))
-                .map(ObjectSpecification::getCorrespondingClass)
-                .orElse(null);
-        if (elementType!=null
-                && elementType.getAnnotation(XmlJavaTypeAdapter.class) == 
null) {
-            return JaxbUtils.jaxbContextFor(DomainObjectList.class, 
elementType);
-        } else {
-            return JaxbUtils.jaxbContextFor(DomainObjectList.class, true);
-        }
+    public <T> T fromXml(Class<T> domainClass, String xml, @Nullable 
Map<String, Object> unmarshallerProperties) {
+        return delegate.fromXml(domainClass, xml, unmarshallerProperties);
     }
 
     @Override
-    protected void configure(final Unmarshaller unmarshaller) {
-        unmarshaller.setAdapter(PersistentEntityAdapter.class,
-                serviceInjector.injectServicesInto(new 
PersistentEntityAdapter()));
-        unmarshaller.setAdapter(PersistentEntitiesAdapter.class,
-                serviceInjector.injectServicesInto(new 
PersistentEntitiesAdapter()));
+    public String toXml(Object domainObject, @Nullable Map<String, Object> 
marshallerProperties) {
+        return delegate.toXml(domainObject, marshallerProperties);
     }
 
     @Override
-    protected void configure(final Marshaller marshaller) {
-
-//debug
-//        marshaller.setListener(new Marshaller.Listener() {
-//            @Override
-//            public void beforeMarshal(final Object source) {
-//                System.err.printf("beforeMarshal %s%n", source);
-//            }
-//        });
-
-        marshaller.setAdapter(PersistentEntityAdapter.class,
-                serviceInjector.injectServicesInto(new 
PersistentEntityAdapter()));
-        marshaller.setAdapter(PersistentEntitiesAdapter.class,
-                serviceInjector.injectServicesInto(new 
PersistentEntitiesAdapter()));
+    public Map<String, String> toXsd(Object domainObject, CausewaySchemas 
causewaySchemas) {
+        return delegate.toXsd(domainObject, causewaySchemas);
     }
 
 }
diff --git 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
index 6c5b16d695b..5368b73a0c7 100644
--- 
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
+++ 
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java
@@ -94,7 +94,7 @@ public class InteractionServiceDefault
     final ThreadLocal<Stack<InteractionLayer>> interactionLayerStack = 
ThreadLocal.withInitial(Stack::new);
 
     final MetamodelEventService runtimeEventService;
-    final SpecificationLoader specificationLoader;
+    final Provider<SpecificationLoader> specificationLoaderProvider;
     final ServiceInjector serviceInjector;
 
     final ClockService clockService;
@@ -109,7 +109,7 @@ public class InteractionServiceDefault
     @Inject
     public InteractionServiceDefault(
             final MetamodelEventService runtimeEventService,
-            final SpecificationLoader specificationLoader,
+            final Provider<SpecificationLoader> specificationLoaderProvider,
             final ServiceInjector serviceInjector,
             final TransactionServiceSpring transactionServiceSpring,
             final ClockService clockService,
@@ -117,7 +117,7 @@ public InteractionServiceDefault(
             final ConfigurableBeanFactory beanFactory,
             final InteractionIdGenerator interactionIdGenerator) {
         this.runtimeEventService = runtimeEventService;
-        this.specificationLoader = specificationLoader;
+        this.specificationLoaderProvider = specificationLoaderProvider;
         this.serviceInjector = serviceInjector;
         this.transactionServiceSpring = transactionServiceSpring;
         this.clockService = clockService;
@@ -136,6 +136,8 @@ public void init(final ContextRefreshedEvent event) {
 
         runtimeEventService.fireBeforeMetamodelLoading();
 
+        var specificationLoader = specificationLoaderProvider.get();
+
         var taskList = 
_ConcurrentTaskList.named("CausewayInteractionFactoryDefault Init")
                 .addRunnable("SpecificationLoader::createMetaModel", 
specificationLoader::createMetaModel)
                 .addRunnable("ChangesDtoUtils::init", ChangesDtoUtils::init)


Reply via email to