Fix bug where @Singleton classes bound toSelf() caused stack overflow.
This commit is contained in:
parent
04866b4d3a
commit
525932668f
6
TODO.md
6
TODO.md
@ -24,9 +24,9 @@ For example:
|
|||||||
- [ ] Get rid of wvc compiler dependency on di
|
- [ ] Get rid of wvc compiler dependency on di
|
||||||
|
|
||||||
## 0.1.3
|
## 0.1.3
|
||||||
- [ ] refactor tools/gradle start scripts to use dist instead of custom bin script
|
- [ ] ~~refactor tools/gradle start scripts to use dist instead of custom bin script~~
|
||||||
- [ ] have custom bin/* scripts which point to dist(s) for convenience
|
- [ ] ~~have custom bin/* scripts which point to dist(s) for convenience~~
|
||||||
- [ ] di bug: @Singleton toSelf() causes stack overflow
|
- [x] di bug: @Singleton toSelf() causes stack overflow
|
||||||
- [ ] `OutletContainer` trait or interface for components which can contain an `<Outlet />` child.
|
- [ ] `OutletContainer` trait or interface for components which can contain an `<Outlet />` child.
|
||||||
- [ ] `Context` should have methods for simply finding an ancestor of a certain type without the need for a predicate.
|
- [ ] `Context` should have methods for simply finding an ancestor of a certain type without the need for a predicate.
|
||||||
|
|
||||||
|
@ -3,31 +3,58 @@ package groowt.util.di;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class DefaultRegistry implements Registry {
|
public class DefaultRegistry implements Registry {
|
||||||
|
|
||||||
protected record ClassKeyBinding<T>(Class<T> key, Binding<T> binding) {}
|
protected static class BindingContainer {
|
||||||
|
|
||||||
protected final Collection<ClassKeyBinding<?>> classBindings = new ArrayList<>();
|
private final Map<Class<?>, Binding<?>> bindings = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> @Nullable Binding<T> get(Class<T> key) {
|
||||||
|
for (final var entry : bindings.entrySet()) {
|
||||||
|
if (entry.getKey().isAssignableFrom(key)) {
|
||||||
|
return (Binding<T>) entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void put(Class<T> key, Binding<T> binding) {
|
||||||
|
this.bindings.put(key, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(Class<?> key) {
|
||||||
|
this.bindings.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void removeIf(Class<T> key, Predicate<? super Binding<T>> filter) {
|
||||||
|
if (filter.test(this.get(key))) {
|
||||||
|
this.bindings.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
this.bindings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final BindingContainer bindingContainer = new BindingContainer();
|
||||||
protected final Collection<RegistryExtension> extensions = new ArrayList<>();
|
protected final Collection<RegistryExtension> extensions = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeBinding(Class<?> key) {
|
public void removeBinding(Class<?> key) {
|
||||||
this.classBindings.removeIf(classKeyBinding -> classKeyBinding.key().equals(key));
|
this.bindingContainer.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter) {
|
public <T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter) {
|
||||||
this.classBindings.removeIf(classKeyBinding ->
|
this.bindingContainer.removeIf(key, filter);
|
||||||
classKeyBinding.key().equals(key) && filter.test((Binding<T>) classKeyBinding.binding())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <E extends RegistryExtension> List<E> getAllRegistryExtensions(Class<E> extensionType) {
|
private <E extends RegistryExtension> List<E> getAllRegistryExtensions(Class<E> extensionType) {
|
||||||
@ -117,18 +144,12 @@ public class DefaultRegistry implements Registry {
|
|||||||
public <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure) {
|
public <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure) {
|
||||||
final var configurator = new SimpleBindingConfigurator<>(key);
|
final var configurator = new SimpleBindingConfigurator<>(key);
|
||||||
configure.accept(configurator);
|
configure.accept(configurator);
|
||||||
this.classBindings.add(new ClassKeyBinding<>(key, configurator.getBinding()));
|
this.bindingContainer.put(key, configurator.getBinding());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable <T> Binding<T> getBinding(Class<T> key) {
|
public <T> @Nullable Binding<T> getBinding(Class<T> key) {
|
||||||
for (final var classKeyBinding : this.classBindings) {
|
return this.bindingContainer.get(key);
|
||||||
if (key.isAssignableFrom(classKeyBinding.key())) {
|
|
||||||
return (Binding<T>) classKeyBinding.binding();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyBinder<?> findKeyBinder(Class<?> keyClass) {
|
private KeyBinder<?> findKeyBinder(Class<?> keyClass) {
|
||||||
@ -189,7 +210,7 @@ public class DefaultRegistry implements Registry {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearAllBindings() {
|
public void clearAllBindings() {
|
||||||
this.classBindings.clear();
|
this.bindingContainer.clear();
|
||||||
for (final var extension : this.extensions) {
|
for (final var extension : this.extensions) {
|
||||||
if (extension instanceof KeyBinder<?> keyBinder) {
|
if (extension instanceof KeyBinder<?> keyBinder) {
|
||||||
keyBinder.clearAllBindings();
|
keyBinder.clearAllBindings();
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package groowt.util.di;
|
package groowt.util.di;
|
||||||
|
|
||||||
|
import jakarta.inject.Provider;
|
||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
|
|
||||||
import static groowt.util.di.BindingUtil.toSingleton;
|
import static groowt.util.di.BindingUtil.toLazySingleton;
|
||||||
|
|
||||||
public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
|
public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
|
||||||
|
|
||||||
@ -19,12 +20,22 @@ public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
|
|||||||
RegistryObjectFactory objectFactory
|
RegistryObjectFactory objectFactory
|
||||||
) {
|
) {
|
||||||
final Binding<T> potentialBinding = this.owner.getBinding(dependencyClass);
|
final Binding<T> potentialBinding = this.owner.getBinding(dependencyClass);
|
||||||
if (potentialBinding != null) {
|
return switch (potentialBinding) {
|
||||||
return potentialBinding;
|
case ClassBinding<T>(Class<T> from, Class<? extends T> to) -> {
|
||||||
} else {
|
this.owner.bind(from, toLazySingleton(() -> objectFactory.createInstance(to)));
|
||||||
this.owner.bind(dependencyClass, toSingleton(objectFactory.createInstance(dependencyClass)));
|
yield this.owner.getBinding(from);
|
||||||
return this.owner.getBinding(dependencyClass);
|
}
|
||||||
}
|
case ProviderBinding<T>(Class<T> from, Provider<? extends T> provider) -> {
|
||||||
|
this.owner.bind(from, toLazySingleton(provider::get));
|
||||||
|
yield this.owner.getBinding(from);
|
||||||
|
}
|
||||||
|
case SingletonBinding<T> singletonBinding -> singletonBinding;
|
||||||
|
case LazySingletonBinding<T> lazySingletonBinding -> lazySingletonBinding;
|
||||||
|
case null -> {
|
||||||
|
this.owner.bind(dependencyClass, toLazySingleton(() -> objectFactory.createInstance(dependencyClass)));
|
||||||
|
yield this.owner.getBinding(dependencyClass);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,6 +2,7 @@ package groowt.util.di;
|
|||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Named;
|
import jakarta.inject.Named;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static groowt.util.di.BindingUtil.*;
|
import static groowt.util.di.BindingUtil.*;
|
||||||
@ -250,4 +251,25 @@ public class DefaultRegistryObjectFactoryTests {
|
|||||||
assertEquals("Given Greeting", g.greet());
|
assertEquals("Given Greeting", g.greet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public static final class SingletonGreeter implements Greeter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String greet() {
|
||||||
|
return "Hello, World!";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singletonDoesNotOverflow() {
|
||||||
|
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
|
||||||
|
b.configureRegistry(r -> {
|
||||||
|
r.bind(SingletonGreeter.class, toSelf());
|
||||||
|
});
|
||||||
|
final var f = b.build();
|
||||||
|
final var g = f.get(SingletonGreeter.class);
|
||||||
|
assertEquals("Hello, World!", g.greet());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user