From 87270cd3b016bb3514e2fd593ba62155fbe45829 Mon Sep 17 00:00:00 2001 From: Peridot Date: Mon, 1 Apr 2024 16:30:51 +0200 Subject: [PATCH] Create CodegenConstructorInjector --- .../inject/InstanceConstructionBenchmark.java | 14 ++- .../utilities/inject/CodegenCache.java | 112 +++++++++++++++++- .../inject/CodegenConstructorInjector.java | 44 +------ .../CodegenConstructorInjectorFactory.java | 12 ++ .../inject/CodegenMethodInjector.java | 41 +------ .../inject/CodegenMethodInjectorFactory.java | 2 +- ...tilities.inject.ConstructorInjectorFactory | 1 + .../inject/DependencyInjectionTest.java | 4 +- .../inject/ConstructorInjectorFactory.java | 11 ++ .../utilities/inject/DefaultInjector.java | 18 ++- .../panda_lang/utilities/inject/Injector.java | 10 +- .../inject/MethodInjectorFactory.java | 6 +- 12 files changed, 176 insertions(+), 99 deletions(-) create mode 100644 di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjectorFactory.java create mode 100644 di-codegen/src/main/resources/META-INF/services/org.panda_lang.utilities.inject.ConstructorInjectorFactory create mode 100644 di/src/main/java/org/panda_lang/utilities/inject/ConstructorInjectorFactory.java diff --git a/di-benchmarks/src/jmh/java/org/panda_lang/utilities/inject/InstanceConstructionBenchmark.java b/di-benchmarks/src/jmh/java/org/panda_lang/utilities/inject/InstanceConstructionBenchmark.java index 5e21365..dc23aaa 100644 --- a/di-benchmarks/src/jmh/java/org/panda_lang/utilities/inject/InstanceConstructionBenchmark.java +++ b/di-benchmarks/src/jmh/java/org/panda_lang/utilities/inject/InstanceConstructionBenchmark.java @@ -28,7 +28,7 @@ @OutputTimeUnit(TimeUnit.MILLISECONDS) public class InstanceConstructionBenchmark { - private static class Entity { + public static class Entity { private final int id; private final String name; @@ -71,7 +71,7 @@ public void injected(DIState state) { @Benchmark public void injectedFast(DIState state) throws Exception { - state.entityInjector.forConstructor(state.entityConstructor).newInstance(); + state.entityInjector.forConstructor(Entity.class).newInstance(); } @Benchmark @@ -84,6 +84,16 @@ public void injectedStaticFast(DIState state) throws Exception { state.entityInjector.forConstructor(state.entityConstructor).newInstance(); } + @Benchmark + public void generatedInjected(DIState state) throws Exception { + state.entityInjector.forGeneratedConstructor(Entity.class).newInstance(); + } + + @Benchmark + public void generatedInjectedStatic(DIState state) throws Exception { + state.entityInjector.forGeneratedConstructor(state.entityConstructor).newInstance(); + } + @State(Scope.Benchmark) public static class DIState { diff --git a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java index e6d6699..f1aa90f 100644 --- a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java +++ b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenCache.java @@ -1,9 +1,119 @@ package org.panda_lang.utilities.inject; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.Function; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.MethodCall; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatchers; +import org.jetbrains.annotations.ApiStatus; +import panda.utilities.ObjectUtils; +@ApiStatus.Internal final class CodegenCache { - private CodegenCache() { } + private static final AtomicInteger CONSTRUCTORS_ID = new AtomicInteger(); + private static final Map, Function> CONSTRUCTOR_INVOKERS = new ConcurrentHashMap<>(); + + private static final AtomicInteger METHODS_ID = new AtomicInteger(); + private static final Map> METHOD_INVOKERS = new ConcurrentHashMap<>(); + + private CodegenCache() { + } + + public static Function getConstructorInvoker(Constructor constructor) { + return CONSTRUCTOR_INVOKERS.computeIfAbsent(constructor, key -> { + Class declaringClass = constructor.getDeclaringClass(); + if (!Modifier.isPublic(declaringClass.getModifiers())) { + throw new IllegalStateException(declaringClass + " has to be public"); + } + + if (!Modifier.isPublic(constructor.getModifiers())) { + throw new IllegalStateException(constructor + " has to be public"); + } + + ByteBuddy byteBuddy = new ByteBuddy(); + DynamicType.Unloaded classPackage = byteBuddy + .makePackage(declaringClass.getPackage().getName()) + .make(); + + Class loaded = byteBuddy.subclass(Object.class) + .implement(GeneratedFunction.class) + .name(declaringClass.getName() + "$" + constructor.getName() + "$" + CONSTRUCTORS_ID.incrementAndGet()) + .method(ElementMatchers.named("apply")) + .intercept(MethodCall.construct(constructor) + .withArgumentArrayElements(0) + .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)) + .make() + .include(classPackage) + .load(declaringClass.getClassLoader()) + .getLoaded(); + + try { + return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException ex) { + throw new DependencyInjectionException("Failed to generate codegen constructor invoker", ex); + } + }); + } + + public static BiFunction getMethodInvoker(Method method) { + return METHOD_INVOKERS.computeIfAbsent(method, key -> { + Class declaringClass = method.getDeclaringClass(); + if (!Modifier.isPublic(declaringClass.getModifiers())) { + throw new IllegalStateException(declaringClass + " has to be public"); + } + + if (!Modifier.isPublic(method.getModifiers())) { + throw new IllegalStateException(method + " has to be public"); + } + + ByteBuddy byteBuddy = new ByteBuddy(); + DynamicType.Unloaded classPackage = byteBuddy + .makePackage(declaringClass.getPackage().getName()) + .make(); + + Class loaded = byteBuddy.subclass(Object.class) + .implement(GeneratedBiFunction.class) + .name(declaringClass.getName() + "$" + method.getName() + "$" + METHODS_ID.incrementAndGet()) + .method(ElementMatchers.named("apply")) + .intercept(MethodCall.invoke(method) + .onArgument(0) + .withArgumentArrayElements(1) + .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)) + .make() + .include(classPackage) + .load(declaringClass.getClassLoader()) + .getLoaded(); + + try { + return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException ex) { + throw new DependencyInjectionException("Failed to generate codegen method invoker", ex); + } + }); + } + + @ApiStatus.Internal + @FunctionalInterface + public interface GeneratedFunction extends Function { + + } + + @ApiStatus.Internal + @FunctionalInterface + public interface GeneratedBiFunction extends BiFunction { + + } } diff --git a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjector.java b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjector.java index 819ea17..7af0031 100644 --- a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjector.java +++ b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjector.java @@ -33,22 +33,18 @@ final class CodegenConstructorInjector implements ConstructorInjector { private static final Object[] EMPTY = new Object[0]; - private static final AtomicInteger ID = new AtomicInteger(); - private final InjectorProcessor processor; private final Constructor constructor; private final boolean empty; private final InjectorCache cache; - private final Function generated; - CodegenConstructorInjector(InjectorProcessor processor, Constructor constructor) throws Exception { + CodegenConstructorInjector(InjectorProcessor processor, Constructor constructor) { this.processor = processor; this.constructor = constructor; this.empty = constructor.getParameterCount() == 0; this.cache = InjectorCache.of(processor, constructor); - - this.generated = generate(constructor); + this.generated = CodegenCache.getConstructorInvoker(constructor); } @SuppressWarnings("unchecked") @@ -61,45 +57,9 @@ public T newInstance(Object... injectorArgs) throws Exception { ); } - private static Function generate(Constructor constructor) throws Exception { - Class declaringClass = constructor.getDeclaringClass(); - if (!Modifier.isPublic(declaringClass.getModifiers())) { - throw new IllegalStateException(declaringClass + " has to be public"); - } - - if (!Modifier.isPublic(constructor.getModifiers())) { - throw new IllegalStateException(constructor + " has to be public"); - } - - ByteBuddy byteBuddy = new ByteBuddy(); - DynamicType.Unloaded classPackage = byteBuddy - .makePackage(declaringClass.getPackage().getName()) - .make(); - - Class loaded = byteBuddy.subclass(Object.class) - .implement(GeneratedFunction.class) - .name(declaringClass.getName() + "$" + constructor.getName() + "$" + ID.incrementAndGet()) - .method(ElementMatchers.named("apply")) - .intercept(MethodCall.invoke(constructor) - .onArgument(0) - .withArgumentArrayElements(1) - .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)) - .make() - .include(classPackage) - .load(declaringClass.getClassLoader()) - .getLoaded(); - - return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance()); - } - @Override public Constructor getConstructor() { return this.constructor; } - @FunctionalInterface - public interface GeneratedFunction extends Function { - - } - } diff --git a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjectorFactory.java b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjectorFactory.java new file mode 100644 index 0000000..e1cd839 --- /dev/null +++ b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenConstructorInjectorFactory.java @@ -0,0 +1,12 @@ +package org.panda_lang.utilities.inject; + +import java.lang.reflect.Constructor; + +public class CodegenConstructorInjectorFactory implements ConstructorInjectorFactory { + + @Override + public ConstructorInjector createConstructorInjector(InjectorProcessor processor, Constructor constructor) { + return new CodegenConstructorInjector<>(processor, constructor); + } + +} diff --git a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjector.java b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjector.java index 6511fa6..9cb4bb8 100644 --- a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjector.java +++ b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjector.java @@ -31,8 +31,6 @@ final class CodegenMethodInjector implements MethodInjector { private static final Object[] EMPTY = new Object[0]; - private static final AtomicInteger ID = new AtomicInteger(); - private final InjectorProcessor processor; private final Method method; private final InjectorCache cache; @@ -40,13 +38,12 @@ final class CodegenMethodInjector implements MethodInjector { private final BiFunction generated; - CodegenMethodInjector(InjectorProcessor processor, Method method) throws Exception { + CodegenMethodInjector(InjectorProcessor processor, Method method) { this.processor = processor; this.method = method; this.cache = InjectorCache.of(processor, method); this.empty = method.getParameterCount() == 0; - - this.generated = generate(method); + this.generated = CodegenCache.getMethodInvoker(method); } @SuppressWarnings("unchecked") @@ -59,43 +56,9 @@ public T invoke(Object instance, Object... injectorArgs) throws Exception { ); } - private static BiFunction generate(Method method) throws Exception { - Class declaringClass = method.getDeclaringClass(); - if (!Modifier.isPublic(declaringClass.getModifiers())) { - throw new IllegalStateException(declaringClass + " has to be public"); - } - - if (!Modifier.isPublic(method.getModifiers())) { - throw new IllegalStateException(method + " has to be public"); - } - - ByteBuddy byteBuddy = new ByteBuddy(); - DynamicType.Unloaded classPackage = byteBuddy - .makePackage(declaringClass.getPackage().getName()) - .make(); - - Class loaded = byteBuddy.subclass(Object.class) - .implement(GeneratedFunction.class) - .name(declaringClass.getName() + "$" + method.getName() + "$" + ID.incrementAndGet()) - .method(ElementMatchers.named("apply")) - .intercept(MethodCall.invoke(method) - .onArgument(0) - .withArgumentArrayElements(1) - .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)) - .make() - .include(classPackage) - .load(declaringClass.getClassLoader()) - .getLoaded(); - - return ObjectUtils.cast(loaded.getDeclaredConstructor().newInstance()); - } - @Override public Method getMethod() { return this.method; } - @FunctionalInterface - public interface GeneratedFunction extends BiFunction { } - } diff --git a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjectorFactory.java b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjectorFactory.java index 6c3d75e..44256e2 100644 --- a/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjectorFactory.java +++ b/di-codegen/src/main/java/org/panda_lang/utilities/inject/CodegenMethodInjectorFactory.java @@ -5,7 +5,7 @@ public final class CodegenMethodInjectorFactory implements MethodInjectorFactory { @Override - public MethodInjector createMethodInjector(InjectorProcessor processor, Method method) throws Exception { + public MethodInjector createMethodInjector(InjectorProcessor processor, Method method) { return new CodegenMethodInjector(processor, method); } diff --git a/di-codegen/src/main/resources/META-INF/services/org.panda_lang.utilities.inject.ConstructorInjectorFactory b/di-codegen/src/main/resources/META-INF/services/org.panda_lang.utilities.inject.ConstructorInjectorFactory new file mode 100644 index 0000000..003eb4e --- /dev/null +++ b/di-codegen/src/main/resources/META-INF/services/org.panda_lang.utilities.inject.ConstructorInjectorFactory @@ -0,0 +1 @@ +org.panda_lang.utilities.inject.CodegenConstructorInjectorFactory \ No newline at end of file diff --git a/di-codegen/src/test/java/org/panda_lang/utilities/inject/DependencyInjectionTest.java b/di-codegen/src/test/java/org/panda_lang/utilities/inject/DependencyInjectionTest.java index bdad8c5..8425013 100644 --- a/di-codegen/src/test/java/org/panda_lang/utilities/inject/DependencyInjectionTest.java +++ b/di-codegen/src/test/java/org/panda_lang/utilities/inject/DependencyInjectionTest.java @@ -66,7 +66,7 @@ void testInjector() { }); Assertions.assertDoesNotThrow(() -> { - TestClass instance = injector.newInstance(TestClass.class); + TestClass instance = injector.forGeneratedConstructor(TestClass.class).newInstance(); Method testTypeInvoke = ReflectionUtils.getMethod(TestClass.class, "testTypeInvoke", String.class).get(); assertEquals(HELLO, injector.invokeMethod(testTypeInvoke, instance)); @@ -87,7 +87,7 @@ void testInjector() { public static final class TestClass { - TestClass(String value) { + public TestClass(String value) { assertEquals(HELLO, value); } diff --git a/di/src/main/java/org/panda_lang/utilities/inject/ConstructorInjectorFactory.java b/di/src/main/java/org/panda_lang/utilities/inject/ConstructorInjectorFactory.java new file mode 100644 index 0000000..5221532 --- /dev/null +++ b/di/src/main/java/org/panda_lang/utilities/inject/ConstructorInjectorFactory.java @@ -0,0 +1,11 @@ +package org.panda_lang.utilities.inject; + +import java.lang.reflect.Constructor; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +interface ConstructorInjectorFactory { + + ConstructorInjector createConstructorInjector(InjectorProcessor processor, Constructor constructor); + +} 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 1a2d68f..fb12cd0 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 @@ -36,6 +36,13 @@ final class DefaultInjector implements Injector { private final Resources resources; private final InjectorProcessor processor; + private final Lazy constructorInjectorFactory = new Lazy<>(() -> + StreamSupport.stream(Spliterators.spliteratorUnknownSize(ServiceLoader.load(ConstructorInjectorFactory.class).iterator(), ORDERED), false) + .findAny() + .orElseGet(() -> ((processor, constructor) -> { + return this.forConstructor(constructor); + })) + ); private final Lazy methodInjectorFactory = new Lazy<>(() -> StreamSupport.stream(Spliterators.spliteratorUnknownSize(ServiceLoader.load(MethodInjectorFactory.class).iterator(), ORDERED), false) .findAny() @@ -58,13 +65,14 @@ public ConstructorInjector forConstructor(Constructor constructor) { } @Override - public ConstructorInjector forGeneratedConstructor(Constructor constructor) { - return this.forConstructor(constructor); + public ConstructorInjector forGeneratedConstructor(Class type) { + return this.forGeneratedConstructor(ClassCache.getConstructor(type)); } + @SuppressWarnings("unchecked") @Override - public ConstructorInjector forGeneratedConstructor(Class type) { - return this.forConstructor(type); + public ConstructorInjector forGeneratedConstructor(Constructor constructor) { + return (ConstructorInjector) this.constructorInjectorFactory.get().createConstructorInjector(this.processor, constructor); } @Override @@ -137,7 +145,7 @@ public MethodInjector forMethod(Method method) { } @Override - public MethodInjector forGeneratedMethod(Method method) throws Exception { + public MethodInjector forGeneratedMethod(Method method) { return this.methodInjectorFactory.get().createMethodInjector(this.processor, method); } diff --git a/di/src/main/java/org/panda_lang/utilities/inject/Injector.java b/di/src/main/java/org/panda_lang/utilities/inject/Injector.java index 744c43c..1193b56 100644 --- a/di/src/main/java/org/panda_lang/utilities/inject/Injector.java +++ b/di/src/main/java/org/panda_lang/utilities/inject/Injector.java @@ -44,10 +44,10 @@ public interface Injector { */ ConstructorInjector forConstructor(Constructor constructor); - ConstructorInjector forGeneratedConstructor(Constructor constructor); - ConstructorInjector forGeneratedConstructor(Class type); + ConstructorInjector forGeneratedConstructor(Constructor constructor); + /** * Create injector for fields (and constructor) * @@ -66,10 +66,10 @@ public interface Injector { */ FieldsInjector forFields(Constructor constructor); - FieldsInjector forGeneratedFields(Constructor constructor); - FieldsInjector forGeneratedFields(Class type); + FieldsInjector forGeneratedFields(Constructor constructor); + /** * Create a new instance of the specified type using Injector * @@ -128,7 +128,7 @@ public interface Injector { * @return injector for the given method * @throws Exception if anything happens during the generation of method wrapper */ - MethodInjector forGeneratedMethod(Method method) throws Exception; + MethodInjector forGeneratedMethod(Method method); /** * Invoke the method using Injector diff --git a/di/src/main/java/org/panda_lang/utilities/inject/MethodInjectorFactory.java b/di/src/main/java/org/panda_lang/utilities/inject/MethodInjectorFactory.java index e05219e..2c4982c 100644 --- a/di/src/main/java/org/panda_lang/utilities/inject/MethodInjectorFactory.java +++ b/di/src/main/java/org/panda_lang/utilities/inject/MethodInjectorFactory.java @@ -1,9 +1,11 @@ package org.panda_lang.utilities.inject; import java.lang.reflect.Method; +import org.jetbrains.annotations.ApiStatus; -public interface MethodInjectorFactory { +@ApiStatus.Internal +interface MethodInjectorFactory { - MethodInjector createMethodInjector(InjectorProcessor processor, Method method) throws Exception; + MethodInjector createMethodInjector(InjectorProcessor processor, Method method); }