From ae9138148373548507fe1f6b83dd0667cdfa295b Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 27 Jan 2023 15:12:52 +0100 Subject: [PATCH] ArC: add support for static disposer methods This commit also fixes a NPE in case of static producer method declared on a `@Dependent` bean. --- .../quarkus/arc/processor/BeanGenerator.java | 44 ++++--- .../producer/disposer/StaticDisposerTest.java | 112 ++++++++++++++++++ 2 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/StaticDisposerTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 279f4b60c6687..fea18c51b3f30 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -846,6 +846,7 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide // Invoke the disposer method // declaringProvider.get(new CreationalContextImpl<>()).dispose() MethodInfo disposerMethod = bean.getDisposer().getDisposerMethod(); + boolean isStatic = Modifier.isStatic(disposerMethod.flags()); ResultHandle declaringProviderSupplierHandle = destroy.readInstanceField( FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER_SUPPLIER, @@ -855,15 +856,21 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide MethodDescriptors.SUPPLIER_GET, declaringProviderSupplierHandle); ResultHandle ctxHandle = destroy.newInstance( MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class), destroy.loadNull()); - ResultHandle declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( - MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, - ctxHandle); - - if (bean.getDeclaringBean().getScope().isNormal()) { - // We need to unwrap the client proxy + ResultHandle declaringProviderInstanceHandle; + if (isStatic) { + // for static disposers, we don't need to resolve this handle + // the `null` will only be used for reflective invocation in case the disposer is private, which is OK + declaringProviderInstanceHandle = destroy.loadNull(); + } else { declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( - MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, - declaringProviderInstanceHandle); + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + ctxHandle); + if (bean.getDeclaringBean().getScope().isNormal()) { + // We need to unwrap the client proxy + declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( + MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle); + } } ResultHandle[] referenceHandles = new ResultHandle[disposerMethod.parametersCount()]; @@ -904,6 +911,8 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, destroy.loadClass(disposerMethod.declaringClass().name().toString()), destroy.load(disposerMethod.name()), paramTypesArray, declaringProviderInstanceHandle, argsArray); + } else if (isStatic) { + destroy.invokeStaticMethod(MethodDescriptor.of(disposerMethod), referenceHandles); } else { destroy.invokeVirtualMethod(MethodDescriptor.of(disposerMethod), declaringProviderInstanceHandle, referenceHandles); @@ -912,8 +921,8 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide // Destroy @Dependent instances injected into method parameters of a disposer method destroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, ctxHandle); - // If the declaring bean is @Dependent we must destroy the instance afterwards - if (BuiltinScope.DEPENDENT.is(bean.getDisposer().getDeclaringBean().getScope())) { + // If the declaring bean is @Dependent and the disposer is not static, we must destroy the instance afterwards + if (BuiltinScope.DEPENDENT.is(bean.getDisposer().getDeclaringBean().getScope()) && !isStatic) { destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); } @@ -927,7 +936,7 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide // Bridge method needed MethodCreator bridgeDestroy = beanCreator.getMethodCreator("destroy", void.class, Object.class, CreationalContext.class) - .setModifiers(ACC_PUBLIC); + .setModifiers(ACC_PUBLIC | ACC_BRIDGE); bridgeDestroy.returnValue(bridgeDestroy.invokeVirtualMethod(destroy.getMethodDescriptor(), bridgeDestroy.getThis(), bridgeDestroy.getMethodParam(0), bridgeDestroy.getMethodParam(1))); @@ -1241,6 +1250,8 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean AssignableResultHandle instanceHandle; MethodInfo producerMethod = bean.getTarget().get().asMethod(); + boolean isStatic = Modifier.isStatic(producerMethod.flags()); + instanceHandle = create.createVariable(DescriptorUtils.extToInt(providerType.className())); // instance = declaringProviderSupplier.get().get(new CreationalContextImpl<>()).produce() ResultHandle ctxHandle = create.newInstance( @@ -1252,8 +1263,9 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean create.getThis()); ResultHandle declaringProviderHandle = create.invokeInterfaceMethod( MethodDescriptors.SUPPLIER_GET, declaringProviderSupplierHandle); - if (Modifier.isStatic(producerMethod.flags())) { - // for static producers, we don't need to resolve this this handle + if (isStatic) { + // for static producers, we don't need to resolve this handle + // the `null` will only be used for reflective invocation in case the producer is private, which is OK declaringProviderInstanceHandle = create.loadNull(); } else { declaringProviderInstanceHandle = create.invokeInterfaceMethod( @@ -1309,7 +1321,7 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean argsArray)); } else { ResultHandle invokeMethodHandle; - if (Modifier.isStatic(producerMethod.flags())) { + if (isStatic) { invokeMethodHandle = create.invokeStaticMethod(MethodDescriptor.of(producerMethod), referenceHandles); } else { @@ -1326,8 +1338,8 @@ void implementCreateForProducerMethod(ClassOutput classOutput, ClassCreator bean bean.getTarget().get().asMethod().name() + "()"); } - // If the declaring bean is @Dependent we must destroy the instance afterwards - if (BuiltinScope.DEPENDENT.is(bean.getDeclaringBean().getScope())) { + // If the declaring bean is @Dependent and the producer is not static, we must destroy the instance afterwards + if (BuiltinScope.DEPENDENT.is(bean.getDeclaringBean().getScope()) && !isStatic) { create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/StaticDisposerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/StaticDisposerTest.java new file mode 100644 index 0000000000000..264ce8ab98c7a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/StaticDisposerTest.java @@ -0,0 +1,112 @@ +package io.quarkus.arc.test.producer.disposer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class StaticDisposerTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(Producers.class, Dependency.class); + + @Test + public void testProducersDisposers() { + assertState(0, 0, false, false, 0, 0); + + InstanceHandle intHandle = Arc.container().instance(BigInteger.class); + assertEquals(1, intHandle.get().intValue()); // instance producer + assertState(1, 1, false, false, 1, 0); + intHandle.destroy(); // static disposer + assertState(1, 1, false, true, 2, 2); + + InstanceHandle decHandle = Arc.container().instance(BigDecimal.class); + assertEquals(2, decHandle.get().intValue()); // static producer + assertState(1, 1, false, true, 3, 2); + decHandle.destroy(); // instance disposer + assertState(2, 2, true, true, 4, 4); + } + + private void assertState(int expectedInstancesCreated, int expectedInstancesDestroyed, + boolean expectedInstanceDisposerCalled, boolean expectedStaticDisposerCalled, + int expectedDependencyCreated, int expectedDependencyDestroyed) { + assertEquals(expectedInstancesCreated, Producers.instancesCreated); + assertEquals(expectedInstancesDestroyed, Producers.instancesDestroyed); + assertEquals(expectedInstanceDisposerCalled, Producers.instanceDisposerCalled); + assertEquals(expectedStaticDisposerCalled, Producers.staticDisposerCalled); + assertEquals(expectedDependencyCreated, Dependency.created); + assertEquals(expectedDependencyDestroyed, Dependency.destroyed); + } + + @Dependent + static class Producers { + static int instancesCreated = 0; + static int instancesDestroyed = 0; + + static boolean instanceDisposerCalled = false; + static boolean staticDisposerCalled = false; + + @PostConstruct + void created() { + instancesCreated++; + } + + @PreDestroy + void destroy() { + instancesDestroyed++; + } + + @Produces + @Dependent + BigInteger instanceProducer(Dependency ignored) { + return new BigInteger("1"); + } + + @Produces + @Dependent + static BigDecimal staticProducer(Dependency ignored) { + return new BigDecimal("2.0"); + } + + void instanceDisposer(@Disposes BigDecimal disposed, Dependency ignored) { + assertFalse(instanceDisposerCalled); + assertEquals(2, disposed.intValue()); + instanceDisposerCalled = true; + } + + static void staticDisposer(@Disposes BigInteger disposed, Dependency ignored) { + assertFalse(staticDisposerCalled); + assertEquals(1, disposed.intValue()); + staticDisposerCalled = true; + } + } + + @Dependent + static class Dependency { + static int created = 0; + static int destroyed = 0; + + @PostConstruct + void created() { + created++; + } + + @PreDestroy + void destroyed() { + destroyed++; + } + } +}