From 186973826bca2bd2c1380244a31e0fc2c1d655c5 Mon Sep 17 00:00:00 2001 From: QwQ-dev Date: Sun, 25 May 2025 16:11:07 +0800 Subject: [PATCH 1/2] feat: Implement TypeDescriptor support in SingletonObjectRegistry. --- .../io/fairyproject/container/Containers.java | 5 + .../destroyer/ContainerNodeDestroyer.java | 18 +- .../node/loader/ContainerNodeLoader.java | 158 +++++++----------- .../singleton/SingletonObjectRegistry.java | 20 +++ .../SingletonObjectRegistryImpl.java | 102 ++++++++++- ...letonObjectRegistryTypeDescriptorTest.java | 148 ++++++++++++++++ 6 files changed, 345 insertions(+), 106 deletions(-) create mode 100644 framework/platforms/core-platform/src/test/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryTypeDescriptorTest.java diff --git a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/Containers.java b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/Containers.java index f0f31614..775fb7cb 100644 --- a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/Containers.java +++ b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/Containers.java @@ -24,6 +24,7 @@ package io.fairyproject.container; +import io.fairyproject.container.type.TypeDescriptor; import lombok.experimental.UtilityClass; /** @@ -39,5 +40,9 @@ public T get(Class type) { return type.cast(CONTAINER_CONTEXT.singletonObjectRegistry().getSingleton(type)); } + @SuppressWarnings("unchecked") + public T get(TypeDescriptor typeDescriptor) { + return (T) CONTAINER_CONTEXT.singletonObjectRegistry().getSingleton(typeDescriptor); + } } diff --git a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/destroyer/ContainerNodeDestroyer.java b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/destroyer/ContainerNodeDestroyer.java index 17536579..5989fcfd 100644 --- a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/destroyer/ContainerNodeDestroyer.java +++ b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/destroyer/ContainerNodeDestroyer.java @@ -49,12 +49,10 @@ private void handlePreDestroy(ContainerObj object) { } private void callPreDestroyProcessor(ContainerObj object) { - if (!object.isSingletonScope()) - return; + if (!object.isSingletonScope()) return; - Object instance = context.singletonObjectRegistry().getSingleton(object.getType()); - if (instance == null) - return; + Object instance = context.singletonObjectRegistry().getSingleton(object.getTypeDescriptor()); + if (instance == null) return; for (ContainerObjDestroyProcessor destroyProcessor : this.context.destroyProcessors()) { destroyProcessor.processPreDestroy(object, instance); @@ -69,16 +67,14 @@ private void handlePostDestroy(ContainerObj object) { this.context.containerObjectBinder().unbind(type); this.context.objectCollectorRegistry().removeFromCollectors(object); - this.context.singletonObjectRegistry().removeSingleton(type); + this.context.singletonObjectRegistry().removeSingleton(object.getTypeDescriptor()); } private void callPostDestroyProcessor(ContainerObj object) { - if (!object.isSingletonScope()) - return; + if (!object.isSingletonScope()) return; - Object instance = context.singletonObjectRegistry().getSingleton(object.getType()); - if (instance == null) - return; + Object instance = context.singletonObjectRegistry().getSingleton(object.getTypeDescriptor()); + if (instance == null) return; for (ContainerObjDestroyProcessor destroyProcessor : this.context.destroyProcessors()) { destroyProcessor.processPostDestroy(object, instance); diff --git a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java index 723aeb20..52281b15 100644 --- a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java +++ b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java @@ -47,7 +47,6 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -63,46 +62,48 @@ public class ContainerNodeLoader { private ContainerObjectResolver containerObjectResolver; private InstanceCollection collection; + @NotNull + private static Object createInstance(ContainerObj obj, Object[] dependencies, InstanceProvider instanceProvider) { + Object instance; + try { + instance = instanceProvider.provide(dependencies); + } catch (Exception ex) { + throw new IllegalStateException("Failed to provide instance for " + obj.getType().getName(), ex); + } + + return instance; + } + public boolean load() { - this.containerObjectResolver = ContainerObjectResolver.create( - this.context.containerObjectBinder(), - new ContainerObjectFactory() { - @Override - public CompletableFuture createInstance(Class type) throws Exception { - return findSingletonInstance(type); - } - - @Override - public CompletableFuture createInstance(TypeDescriptor typeDescriptor) throws Exception { - return findSingletonInstance(typeDescriptor); - } - }, - new ContainerObjectFactory() { - @Override - public CompletableFuture createInstance(Class type) throws Exception { - return findPrototypeInstance(type); - } - - @Override - public CompletableFuture createInstance(TypeDescriptor typeDescriptor) throws Exception { - return findPrototypeInstance(typeDescriptor); - } - } - ); + this.containerObjectResolver = ContainerObjectResolver.create(this.context.containerObjectBinder(), new ContainerObjectFactory() { + @Override + public CompletableFuture createInstance(Class type) throws Exception { + return findSingletonInstance(type); + } + + @Override + public CompletableFuture createInstance(TypeDescriptor typeDescriptor) throws Exception { + return findSingletonInstance(typeDescriptor); + } + }, new ContainerObjectFactory() { + @Override + public CompletableFuture createInstance(Class type) throws Exception { + return findPrototypeInstance(type); + } + + @Override + public CompletableFuture createInstance(TypeDescriptor typeDescriptor) throws Exception { + return findPrototypeInstance(typeDescriptor); + } + }); this.collection = InstanceCollection.create(); BlockingThreadAwaitQueue queue = BlockingThreadAwaitQueue.create(); this.node.resolve(); - if (!this.node.isResolved()) - return false; + if (!this.node.isResolved()) return false; - CompletableFuture completableFuture = this.provideInstances() - .thenRun(this::callNodePreInitProcessors) - .thenComposeAsync(directlyCompose(this::callPreInitProcessors), queue) - .thenRun(this::handleObjCollector) - .thenComposeAsync(directlyCompose(this::callPostInitProcessors), queue) - .thenRun(this::callNodePostInitProcessors); + CompletableFuture completableFuture = this.provideInstances().thenRun(this::callNodePreInitProcessors).thenComposeAsync(directlyCompose(this::callPreInitProcessors), queue).thenRun(this::handleObjCollector).thenComposeAsync(directlyCompose(this::callPostInitProcessors), queue).thenRun(this::callNodePostInitProcessors); queue.await(completableFuture::isDone); ThrowingRunnable.sneaky(completableFuture::get).run(); @@ -124,16 +125,14 @@ private void callNodePostInitProcessors() { private CompletableFuture findSingletonInstance(Class type) { SingletonObjectRegistry singletonObjectRegistry = this.context.singletonObjectRegistry(); Object instance = singletonObjectRegistry.getSingleton(type); - if (instance == null) - throw new IllegalStateException("Singleton instance for " + type.getName() + " is null!"); + if (instance == null) throw new IllegalStateException("Singleton instance for " + type.getName() + " is null!"); return CompletableFuture.completedFuture(instance); } private CompletableFuture findPrototypeInstance(Class type) { ContainerObj obj = this.context.containerObjectBinder().getBinding(type); - if (obj == null) - throw new IllegalStateException("Container object for " + type.getName() + " is null!"); + if (obj == null) throw new IllegalStateException("Container object for " + type.getName() + " is null!"); try { return this.provideInstance(obj); @@ -144,18 +143,15 @@ private CompletableFuture findPrototypeInstance(Class type) { private CompletableFuture findSingletonInstance(TypeDescriptor typeDescriptor) { SingletonObjectRegistry singletonObjectRegistry = this.context.singletonObjectRegistry(); - // 注意:当前 SingletonObjectRegistry 可能还没有支持 TypeDescriptor,这里先使用 rawType - Object instance = singletonObjectRegistry.getSingleton(typeDescriptor.getRawType()); - if (instance == null) - throw new IllegalStateException("Singleton instance for " + typeDescriptor + " is null!"); + Object instance = singletonObjectRegistry.getSingleton(typeDescriptor); + if (instance == null) throw new IllegalStateException("Singleton instance for " + typeDescriptor + " is null!"); return CompletableFuture.completedFuture(instance); } private CompletableFuture findPrototypeInstance(TypeDescriptor typeDescriptor) { ContainerObj obj = this.context.containerObjectBinder().getBinding(typeDescriptor); - if (obj == null) - throw new IllegalStateException("Container object for " + typeDescriptor + " is null!"); + if (obj == null) throw new IllegalStateException("Container object for " + typeDescriptor + " is null!"); try { return this.provideInstance(obj); @@ -174,24 +170,20 @@ private CompletableFuture callPreInitProcessors() { for (InstanceEntry entry : snapshot) { Object instance = entry.getInstance(); ContainerObj object = entry.getContainerObject(); - if (!this.trySetLifeCycle(object, LifeCycle.PRE_INIT)) - continue; + if (!this.trySetLifeCycle(object, LifeCycle.PRE_INIT)) continue; CompletableFuture chain = null; for (ContainerObjInitProcessor initProcessor : this.context.initProcessors()) { try { Supplier> callback = () -> initProcessor.processPreInitialization(object, instance, this.containerObjectResolver); - if (chain == null) - chain = callback.get(); - else - chain = chain.thenCompose($ -> callback.get()); + if (chain == null) chain = callback.get(); + else chain = chain.thenCompose($ -> callback.get()); } catch (Throwable throwable) { ContainerLogger.report(this.node, object, throwable, "processing pre initialization"); } } - if (chain != null) - futures.add(chain); + if (chain != null) futures.add(chain); } return AsyncUtils.allOf(futures); @@ -203,24 +195,20 @@ private CompletableFuture callPostInitProcessors() { for (InstanceEntry entry : this.collection) { Object instance = entry.getInstance(); ContainerObj object = entry.getContainerObject(); - if (!this.trySetLifeCycle(object, LifeCycle.POST_INIT)) - continue; + if (!this.trySetLifeCycle(object, LifeCycle.POST_INIT)) continue; CompletableFuture chain = null; for (ContainerObjInitProcessor initProcessor : this.context.initProcessors()) { try { Supplier> callback = () -> initProcessor.processPostInitialization(object, instance); - if (chain == null) - chain = callback.get(); - else - chain = chain.thenCompose($ -> callback.get()); + if (chain == null) chain = callback.get(); + else chain = chain.thenCompose($ -> callback.get()); } catch (Throwable throwable) { ContainerLogger.report(this.node, object, throwable, "processing post initialization"); } } - if (chain != null) - futures.add(chain); + if (chain != null) futures.add(chain); } return AsyncUtils.allOf(futures); @@ -228,8 +216,7 @@ private CompletableFuture callPostInitProcessors() { private CompletableFuture provideInstances() { return this.node.forEachClockwiseAwait(obj -> { - if (obj.isPrototypeScope()) - return AsyncUtils.empty(); + if (obj.isPrototypeScope()) return AsyncUtils.empty(); try { return this.provideInstance(obj); @@ -249,8 +236,8 @@ private CompletableFuture provideInstance(ContainerObj obj) throws Excep return AsyncUtils.empty(); } - if (singletonObjectRegistry.containsSingleton(objectType)) { - Object instance = singletonObjectRegistry.getSingleton(objectType); + if (singletonObjectRegistry.containsSingleton(obj.getTypeDescriptor())) { + Object instance = singletonObjectRegistry.getSingleton(obj.getTypeDescriptor()); this.postInstanceConstruct(instance, obj); return AsyncUtils.empty(); @@ -263,35 +250,20 @@ private CompletableFuture provideInstance(ContainerObj obj) throws Excep } CompletableFuture future = this.containerObjectResolver.resolveInstances(instanceProvider.getDependencies()); - return future - .thenApplyAsync(objects -> createInstance(obj, objects, instanceProvider), obj.getThreadingMode().getExecutor()) - .thenCompose(this::callConstructProcessors) - .thenApply(instance -> { - if (obj.isSingletonScope()) { - singletonObjectRegistry.registerSingleton(objectType, instance); - } - - this.postInstanceConstruct(instance, obj); - return instance; - }); + return future.thenApplyAsync(objects -> createInstance(obj, objects, instanceProvider), obj.getThreadingMode().getExecutor()).thenCompose(this::callConstructProcessors).thenApply(instance -> { + if (obj.isSingletonScope()) { + singletonObjectRegistry.registerSingleton(obj.getTypeDescriptor(), instance); + } + + this.postInstanceConstruct(instance, obj); + return instance; + }); } private void postInstanceConstruct(Object instance, ContainerObj obj) { this.collection.add(instance, obj); } - @NotNull - private static Object createInstance(ContainerObj obj, Object[] dependencies, InstanceProvider instanceProvider) { - Object instance; - try { - instance = instanceProvider.provide(dependencies); - } catch (Exception ex) { - throw new IllegalStateException("Failed to provide instance for " + obj.getType().getName(), ex); - } - - return instance; - } - private CompletableFuture callConstructProcessors(Object instance) { List> futures = new ArrayList<>(); @@ -303,16 +275,14 @@ private CompletableFuture callConstructProcessors(Object instance) { } private boolean trySetLifeCycle(ContainerObj obj, LifeCycle lifeCycle) { - if (obj.getScope() != InjectableScope.SINGLETON) - return true; + if (obj.getScope() != InjectableScope.SINGLETON) return true; - Class type = obj.getType(); + TypeDescriptor typeDescriptor = obj.getTypeDescriptor(); SingletonObjectRegistry singletonObjectRegistry = this.context.singletonObjectRegistry(); - LifeCycle current = singletonObjectRegistry.getSingletonLifeCycle(type); - if (current.isAfter(lifeCycle)) - return false; + LifeCycle current = singletonObjectRegistry.getSingletonLifeCycle(typeDescriptor); + if (current.isAfter(lifeCycle)) return false; - singletonObjectRegistry.setSingletonLifeCycle(type, lifeCycle); + singletonObjectRegistry.setSingletonLifeCycle(typeDescriptor, lifeCycle); return true; } diff --git a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistry.java b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistry.java index 89adf624..67394bab 100644 --- a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistry.java +++ b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistry.java @@ -25,6 +25,9 @@ package io.fairyproject.container.object.singleton; import io.fairyproject.container.object.LifeCycle; +import io.fairyproject.container.type.TypeDescriptor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -48,4 +51,21 @@ static SingletonObjectRegistry create() { void setSingletonLifeCycle(Class type, LifeCycle lifeCycle); + void registerSingleton(@NotNull TypeDescriptor typeDescriptor, @NotNull Object instance); + + @Nullable + Object getSingleton(@NotNull TypeDescriptor typeDescriptor); + + boolean containsSingleton(@NotNull TypeDescriptor typeDescriptor); + + void removeSingleton(@NotNull TypeDescriptor typeDescriptor); + + @NotNull + Set getSingletonTypeDescriptors(); + + @NotNull + LifeCycle getSingletonLifeCycle(@NotNull TypeDescriptor typeDescriptor); + + void setSingletonLifeCycle(@NotNull TypeDescriptor typeDescriptor, @NotNull LifeCycle lifeCycle); + } diff --git a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryImpl.java b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryImpl.java index 3c972a36..59cf4a11 100644 --- a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryImpl.java +++ b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryImpl.java @@ -25,7 +25,11 @@ package io.fairyproject.container.object.singleton; import io.fairyproject.container.object.LifeCycle; +import io.fairyproject.container.type.TypeDescriptor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Type; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -36,15 +40,20 @@ public class SingletonObjectRegistryImpl implements SingletonObjectRegistry { private final Map, Object> objectByType = new ConcurrentHashMap<>(); private final Map, LifeCycle> lifeCycleByType = new ConcurrentHashMap<>(); + private final Map objectByTypeDescriptor = new ConcurrentHashMap<>(); + private final Map lifeCycleByTypeDescriptor = new ConcurrentHashMap<>(); + @Override public void registerSingleton(Class type, Object instance) { synchronized (this.objectByType) { Object previous = this.objectByType.get(type); if (previous != null) { - throw new IllegalStateException("Could not register object [" + instance + "] under type [" + type.getName() + "]: there is already object [" + previous + "] bound"); + throw new IllegalStateException("Could not register object [" + instance + "] under type [" + + type.getName() + "]: there is already object [" + previous + "] bound"); } this.objectByType.put(type, instance); + this.objectByTypeDescriptor.put(new TypeDescriptor(type), instance); } } @@ -62,6 +71,10 @@ public boolean containsSingleton(Class type) { public void removeSingleton(Class type) { objectByType.remove(type); lifeCycleByType.remove(type); + + TypeDescriptor typeDescriptor = new TypeDescriptor(type); + objectByTypeDescriptor.remove(typeDescriptor); + lifeCycleByTypeDescriptor.remove(typeDescriptor); } @Override @@ -77,5 +90,92 @@ public LifeCycle getSingletonLifeCycle(Class type) { @Override public void setSingletonLifeCycle(Class type, LifeCycle lifeCycle) { lifeCycleByType.put(type, lifeCycle); + + TypeDescriptor typeDescriptor = new TypeDescriptor(type); + lifeCycleByTypeDescriptor.put(typeDescriptor, lifeCycle); + } + + @Override + public void registerSingleton(@NotNull TypeDescriptor typeDescriptor, @NotNull Object instance) { + synchronized (this.objectByTypeDescriptor) { + Object previous = this.objectByTypeDescriptor.get(typeDescriptor); + if (previous != null) { + throw new IllegalStateException("Could not register object [" + instance + "] under type [" + + typeDescriptor + "]: there is already object [" + previous + "] bound"); + } + + this.objectByTypeDescriptor.put(typeDescriptor, instance); + this.objectByType.put(typeDescriptor.getRawType(), instance); + } + } + + @Override + @Nullable + public Object getSingleton(@NotNull TypeDescriptor typeDescriptor) { + Object obj = objectByTypeDescriptor.get(typeDescriptor); + if (obj != null) { + return obj; + } + + for (Map.Entry entry : objectByTypeDescriptor.entrySet()) { + if (entry.getKey().isAssignableTo(typeDescriptor)) { + return entry.getValue(); + } + } + + return objectByType.get(typeDescriptor.getRawType()); + } + + @Override + public boolean containsSingleton(@NotNull TypeDescriptor typeDescriptor) { + return getSingleton(typeDescriptor) != null; + } + + @Override + public void removeSingleton(@NotNull TypeDescriptor typeDescriptor) { + objectByTypeDescriptor.remove(typeDescriptor); + lifeCycleByTypeDescriptor.remove(typeDescriptor); + Type[] genericTypes = typeDescriptor.getGenericTypes(); + + if (genericTypes == null || genericTypes.length == 0) { + Class rawType = typeDescriptor.getRawType(); + + objectByType.remove(rawType); + lifeCycleByType.remove(rawType); + } + } + + @Override + @NotNull + public Set getSingletonTypeDescriptors() { + return Collections.unmodifiableSet(objectByTypeDescriptor.keySet()); + } + + @Override + @NotNull + public LifeCycle getSingletonLifeCycle(@NotNull TypeDescriptor typeDescriptor) { + LifeCycle lifeCycle = lifeCycleByTypeDescriptor.get(typeDescriptor); + if (lifeCycle != null) { + return lifeCycle; + } + + for (Map.Entry entry : lifeCycleByTypeDescriptor.entrySet()) { + if (entry.getKey().isAssignableTo(typeDescriptor)) { + return entry.getValue(); + } + } + + return lifeCycleByType.getOrDefault(typeDescriptor.getRawType(), LifeCycle.NONE); } + + @Override + public void setSingletonLifeCycle(@NotNull TypeDescriptor typeDescriptor, @NotNull LifeCycle lifeCycle) { + lifeCycleByTypeDescriptor.put(typeDescriptor, lifeCycle); + Type[] genericTypes = typeDescriptor.getGenericTypes(); + + if (genericTypes == null || genericTypes.length == 0) { + lifeCycleByType.put(typeDescriptor.getRawType(), lifeCycle); + } + } + } diff --git a/framework/platforms/core-platform/src/test/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryTypeDescriptorTest.java b/framework/platforms/core-platform/src/test/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryTypeDescriptorTest.java new file mode 100644 index 00000000..5a9f51aa --- /dev/null +++ b/framework/platforms/core-platform/src/test/java/io/fairyproject/container/object/singleton/SingletonObjectRegistryTypeDescriptorTest.java @@ -0,0 +1,148 @@ +package io.fairyproject.container.object.singleton; + +import io.fairyproject.container.object.LifeCycle; +import io.fairyproject.container.type.TypeDescriptor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SingletonObjectRegistryTypeDescriptorTest { + + private SingletonObjectRegistryImpl registry; + + @BeforeEach + void setUp() { + this.registry = new SingletonObjectRegistryImpl(); + } + + @Test + public void testRegisterSingletonWithTypeDescriptor() { + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + TestService instance = new TestService(); + + this.registry.registerSingleton(typeDescriptor, instance); + + assertTrue(this.registry.containsSingleton(typeDescriptor)); + assertEquals(instance, this.registry.getSingleton(typeDescriptor)); + } + + @Test + public void testRegisterSingletonWithGenericTypeDescriptor() { + TypeDescriptor listStringTypeDescriptor = new TypeDescriptor(List.class, new Type[]{String.class}); + List stringList = Arrays.asList("test1", "test2"); + + this.registry.registerSingleton(listStringTypeDescriptor, stringList); + + assertTrue(this.registry.containsSingleton(listStringTypeDescriptor)); + assertEquals(stringList, this.registry.getSingleton(listStringTypeDescriptor)); + } + + @Test + public void testGetSingletonTypeDescriptors() { + TypeDescriptor typeDescriptor1 = new TypeDescriptor(TestService.class); + TypeDescriptor typeDescriptor2 = new TypeDescriptor(List.class, new Type[]{String.class}); + + TestService service = new TestService(); + List stringList = Arrays.asList("test"); + + this.registry.registerSingleton(typeDescriptor1, service); + this.registry.registerSingleton(typeDescriptor2, stringList); + + Set typeDescriptors = this.registry.getSingletonTypeDescriptors(); + assertEquals(2, typeDescriptors.size()); + assertTrue(typeDescriptors.contains(typeDescriptor1)); + assertTrue(typeDescriptors.contains(typeDescriptor2)); + } + + @Test + public void testRemoveSingletonWithTypeDescriptor() { + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + TestService instance = new TestService(); + + this.registry.registerSingleton(typeDescriptor, instance); + assertTrue(this.registry.containsSingleton(typeDescriptor)); + + this.registry.removeSingleton(typeDescriptor); + assertFalse(this.registry.containsSingleton(typeDescriptor)); + assertNull(this.registry.getSingleton(typeDescriptor)); + } + + @Test + public void testLifeCycleWithTypeDescriptor() { + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + + this.registry.setSingletonLifeCycle(typeDescriptor, LifeCycle.POST_INIT); + assertEquals(LifeCycle.POST_INIT, this.registry.getSingletonLifeCycle(typeDescriptor)); + } + + @Test + public void testLifeCycleDefaultValue() { + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + assertEquals(LifeCycle.NONE, this.registry.getSingletonLifeCycle(typeDescriptor)); + } + + @Test + public void testBackwardCompatibilityWithClassBasedMethods() { + TestService instance = new TestService(); + this.registry.registerSingleton(TestService.class, instance); + + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + assertEquals(instance, this.registry.getSingleton(typeDescriptor)); + assertTrue(this.registry.containsSingleton(typeDescriptor)); + } + + @Test + public void testBackwardCompatibilityWithTypeDescriptorBasedMethods() { + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + TestService instance = new TestService(); + this.registry.registerSingleton(typeDescriptor, instance); + + assertEquals(instance, this.registry.getSingleton(TestService.class)); + assertTrue(this.registry.containsSingleton(TestService.class)); + } + + @Test + public void testRegisterSingletonTwiceWithTypeDescriptorShouldThrowException() { + TypeDescriptor typeDescriptor = new TypeDescriptor(TestService.class); + TestService instance1 = new TestService(); + TestService instance2 = new TestService(); + + this.registry.registerSingleton(typeDescriptor, instance1); + + assertThrows(IllegalStateException.class, () -> this.registry.registerSingleton(typeDescriptor, instance2)); + } + + @Test + public void testGenericTypeCompatibility() { + TypeDescriptor listStringTypeDescriptor = new TypeDescriptor(List.class, new Type[]{String.class}); + TypeDescriptor listIntegerTypeDescriptor = new TypeDescriptor(List.class, new Type[]{Integer.class}); + + List stringList = Arrays.asList("test"); + List integerList = Arrays.asList(1, 2, 3); + + this.registry.registerSingleton(listStringTypeDescriptor, stringList); + this.registry.registerSingleton(listIntegerTypeDescriptor, integerList); + + assertEquals(stringList, this.registry.getSingleton(listStringTypeDescriptor)); + assertEquals(integerList, this.registry.getSingleton(listIntegerTypeDescriptor)); + + assertNotEquals(this.registry.getSingleton(listStringTypeDescriptor), + this.registry.getSingleton(listIntegerTypeDescriptor)); + } + + private static class TestService { + + } + +} \ No newline at end of file From 4063c3cbd45cbf367fcd0ba921d09928b968977a Mon Sep 17 00:00:00 2001 From: QwQ-dev Date: Sun, 25 May 2025 16:44:34 +0800 Subject: [PATCH 2/2] feat: Add TypeDescriptor-based dependency resolution to ContainerNodeLoader. --- .../node/loader/ContainerNodeLoader.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java index 52281b15..b953f27c 100644 --- a/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java +++ b/framework/platforms/core-platform/src/main/java/io/fairyproject/container/node/loader/ContainerNodeLoader.java @@ -31,7 +31,9 @@ import io.fairyproject.container.node.loader.collection.InstanceEntry; import io.fairyproject.container.object.ContainerObj; import io.fairyproject.container.object.LifeCycle; +import io.fairyproject.container.object.provider.ConstructorInstanceProvider; import io.fairyproject.container.object.provider.InstanceProvider; +import io.fairyproject.container.object.provider.MethodInvokeInstanceProvider; import io.fairyproject.container.object.resolver.ContainerObjectFactory; import io.fairyproject.container.object.resolver.ContainerObjectResolver; import io.fairyproject.container.object.singleton.SingletonObjectRegistry; @@ -249,7 +251,17 @@ private CompletableFuture provideInstance(ContainerObj obj) throws Excep throw new IllegalStateException("Instance provider for " + objectType.getName() + " is null!"); } - CompletableFuture future = this.containerObjectResolver.resolveInstances(instanceProvider.getDependencies()); + CompletableFuture future; + if (instanceProvider instanceof ConstructorInstanceProvider) { + ConstructorInstanceProvider constructorProvider = (ConstructorInstanceProvider) instanceProvider; + future = this.resolveInstancesByTypeDescriptors(constructorProvider.getParameterTypeDescriptors()); + } else if (instanceProvider instanceof MethodInvokeInstanceProvider) { + MethodInvokeInstanceProvider methodProvider = (MethodInvokeInstanceProvider) instanceProvider; + future = this.resolveInstancesByTypeDescriptors(methodProvider.getParameterTypeDescriptors()); + } else { + future = this.containerObjectResolver.resolveInstances(instanceProvider.getDependencies()); + } + return future.thenApplyAsync(objects -> createInstance(obj, objects, instanceProvider), obj.getThreadingMode().getExecutor()).thenCompose(this::callConstructProcessors).thenApply(instance -> { if (obj.isSingletonScope()) { singletonObjectRegistry.registerSingleton(obj.getTypeDescriptor(), instance); @@ -311,4 +323,18 @@ private T handleThrow(ContainerObj obj, Function function) } } + private CompletableFuture resolveInstancesByTypeDescriptors(TypeDescriptor[] typeDescriptors) throws Exception { + Object[] args = new Object[typeDescriptors.length]; + CompletableFuture[] futures = new CompletableFuture[typeDescriptors.length]; + + for (int i = 0; i < args.length; i++) { + TypeDescriptor typeDescriptor = typeDescriptors[i]; + int index = i; + + futures[i] = this.containerObjectResolver.resolveInstance(typeDescriptor).thenAccept(instance -> args[index] = instance); + } + + return CompletableFuture.allOf(futures).thenApply($ -> args); + } + }