diff --git a/di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java b/di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java index 67af0a0..73da4c6 100644 --- a/di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java +++ b/di/src/main/java/org/panda_lang/utilities/inject/DefaultInjector.java @@ -120,7 +120,7 @@ public MethodInjector forGeneratedMethod(Method method) throws Exception { } @Override - public @Nullable T invokeParameter(Parameter parameter, Object... injectorArgs) throws Exception { + public @Nullable T invokeParameter(Parameter parameter, Object... injectorArgs) throws Throwable { return ObjectUtils.cast(processor.tryFetchValue(processor, new PropertyParameter(parameter), injectorArgs)); } diff --git a/di/src/main/java/org/panda_lang/utilities/inject/DependencyInjectionException.java b/di/src/main/java/org/panda_lang/utilities/inject/DependencyInjectionException.java index e20eebe..2467762 100644 --- a/di/src/main/java/org/panda_lang/utilities/inject/DependencyInjectionException.java +++ b/di/src/main/java/org/panda_lang/utilities/inject/DependencyInjectionException.java @@ -22,6 +22,10 @@ public final class DependencyInjectionException extends RuntimeException { super(message); } + DependencyInjectionException(Throwable cause) { + super(cause); + } + DependencyInjectionException(String message, Throwable cause) { super(message, cause); } diff --git a/di/src/main/java/org/panda_lang/utilities/inject/FieldsInjector.java b/di/src/main/java/org/panda_lang/utilities/inject/FieldsInjector.java index 9ce4750..1b2433f 100644 --- a/di/src/main/java/org/panda_lang/utilities/inject/FieldsInjector.java +++ b/di/src/main/java/org/panda_lang/utilities/inject/FieldsInjector.java @@ -5,8 +5,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.panda_lang.utilities.inject.annotations.AutoConstruct; import org.panda_lang.utilities.inject.annotations.Inject; -import org.panda_lang.utilities.inject.annotations.PostConstruct; public final class FieldsInjector { @@ -22,7 +22,7 @@ public T newInstance(Object... injectorArgs) throws Throwable { T instance = constructorInjector.newInstance(injectorArgs); for (Field field : getAllFields(instance.getClass())) { - if (!field.isAnnotationPresent(Inject.class)) { + if (!field.isAnnotationPresent(Inject.class) && !field.isAnnotationPresent(AutoConstruct.class)) { continue; } diff --git a/di/src/main/java/org/panda_lang/utilities/inject/InjectorProcessor.java b/di/src/main/java/org/panda_lang/utilities/inject/InjectorProcessor.java index 62cc07f..4a01d0b 100644 --- a/di/src/main/java/org/panda_lang/utilities/inject/InjectorProcessor.java +++ b/di/src/main/java/org/panda_lang/utilities/inject/InjectorProcessor.java @@ -17,6 +17,7 @@ package org.panda_lang.utilities.inject; import org.jetbrains.annotations.Nullable; +import org.panda_lang.utilities.inject.annotations.AutoConstruct; import org.panda_lang.utilities.inject.annotations.Injectable; import panda.std.Option; import panda.utilities.ObjectUtils; @@ -37,8 +38,22 @@ final class InjectorProcessor { private final Injector injector; private final Map injectableCache = new HashMap<>(); + private final Bind autoConstructBind; + InjectorProcessor(Injector injector) { this.injector = injector; + + this.autoConstructBind = new DefaultBind<>(Object.class); + this.autoConstructBind.assignHandler((property, annotation, injectorArgs) -> { + if (property.getAnnotation(AutoConstruct.class) != null) { + try { + return injector.newInstanceWithFields(property.getType(), injectorArgs); + } catch (Throwable ex) { + throw new DependencyInjectionException(ex); + } + } + return null; + }); } protected Object[] fetchValues(InjectorCache cache, Object... injectorArgs) throws Exception { @@ -156,9 +171,17 @@ protected Bind[] fetchBinds(Annotation[] annotations, Executable exe protected Bind fetchBind(@Nullable Annotation annotation, Property property) { Class requiredType = annotation != null ? annotation.annotationType() : property.getType(); - return injector.getResources().getBind(requiredType).orThrow(() -> { - throw new DependencyInjectionException("Cannot find proper bind for " + property + " property (type " + property.getType() + ")"); - }); + + Bind bind = this.injector.getResources().getBind(requiredType).orNull(); + if (bind != null) { + return bind; + } + + if (property.getAnnotation(AutoConstruct.class) != null) { + return this.autoConstructBind; + } + + throw new DependencyInjectionException("Cannot find proper bind for " + property + " property (type " + property.getType() + ")"); } protected Collection>[] fetchHandlers(Executable executable) { diff --git a/di/src/main/java/org/panda_lang/utilities/inject/annotations/AutoConstruct.java b/di/src/main/java/org/panda_lang/utilities/inject/annotations/AutoConstruct.java new file mode 100644 index 0000000..3a2ee70 --- /dev/null +++ b/di/src/main/java/org/panda_lang/utilities/inject/annotations/AutoConstruct.java @@ -0,0 +1,15 @@ +package org.panda_lang.utilities.inject.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks field that value should be automatically constructed with current injector resources. + */ +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface AutoConstruct { + +} diff --git a/di/src/test/java/org/panda_lang/utilities/inject/AutoConstructTest.java b/di/src/test/java/org/panda_lang/utilities/inject/AutoConstructTest.java new file mode 100644 index 0000000..50da633 --- /dev/null +++ b/di/src/test/java/org/panda_lang/utilities/inject/AutoConstructTest.java @@ -0,0 +1,64 @@ +package org.panda_lang.utilities.inject; + +import org.junit.jupiter.api.Test; +import org.panda_lang.utilities.inject.annotations.AutoConstruct; +import org.panda_lang.utilities.inject.annotations.Inject; +import org.panda_lang.utilities.inject.annotations.PostConstruct; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +final class AutoConstructTest { + + static class Service { + + @AutoConstruct + private Repository repository; + + @PostConstruct + private void construct() { + assertNotNull(this.repository); + } + + } + + static class Repository { + + @AutoConstruct + private DataProvider dataProvider; + + @Inject + private String testValue; + + @PostConstruct + private void construct() { + assertEquals("TestString", this.testValue); + assertNotNull(this.dataProvider); + } + + } + + static class DataProvider { + + @Inject + private String testValue; + + @PostConstruct + private void construct() { + assertEquals("TestString", this.testValue); + } + + } + + @Test + void shouldRunPostConstructMethods() throws Throwable { + Injector injector = DependencyInjection.createInjector(resources -> { + resources.on(String.class).assignInstance("TestString"); + }); + + injector.newInstanceWithFields(Repository.class); + injector.newInstanceWithFields(Service.class); + } + +}