diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 02a6491546b860..8255af8ae61ff5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; import jakarta.annotation.Priority; +import jakarta.enterprise.context.RequestScoped; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -201,8 +202,15 @@ public List generateResources(ReflectionRegistration reflectionRegistr beanGenerator.precomputeGeneratedName(bean); } + CustomAlterableContextsGenerator alterableContextsGenerator = new CustomAlterableContextsGenerator(generateSources); + List alterableContexts = customAlterableContexts.getRegistered(); + + boolean optimizeClientProxyRequestDelegate = !beanDeployment.getCustomContexts() + .containsKey(BuiltinScope.REQUEST.getInfo()) + && alterableContexts.stream().noneMatch(c -> c.scopeAnnotation.equals(RequestScoped.class)); + ClientProxyGenerator clientProxyGenerator = new ClientProxyGenerator(applicationClassPredicate, generateSources, - allowMocking, refReg, existingClasses); + allowMocking, refReg, existingClasses, optimizeClientProxyRequestDelegate); InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate, privateMembers, generateSources, refReg, existingClasses, beanToGeneratedName, @@ -233,15 +241,12 @@ public List generateResources(ReflectionRegistration reflectionRegistr } ContextInstancesGenerator contextInstancesGenerator = new ContextInstancesGenerator(generateSources, - reflectionRegistration, beanDeployment, scopeToGeneratedName); + refReg, beanDeployment, scopeToGeneratedName); if (optimizeContexts) { contextInstancesGenerator.precomputeGeneratedName(BuiltinScope.APPLICATION.getName()); contextInstancesGenerator.precomputeGeneratedName(BuiltinScope.REQUEST.getName()); } - CustomAlterableContextsGenerator alterableContextsGenerator = new CustomAlterableContextsGenerator(generateSources); - List alterableContexts = customAlterableContexts.getRegistered(); - List resources = new ArrayList<>(); if (executor != null) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java index 9edfed2862acbf..65ccbc7ec03e4e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java @@ -59,13 +59,17 @@ public class ClientProxyGenerator extends AbstractGenerator { private final Predicate applicationClassPredicate; private final boolean mockable; private final Set existingClasses; + // We otimize the access to the delegate if a single request context is present + private final boolean optimizeClientProxyRequestDelegate; public ClientProxyGenerator(Predicate applicationClassPredicate, boolean generateSources, boolean mockable, - ReflectionRegistration reflectionRegistration, Set existingClasses) { + ReflectionRegistration reflectionRegistration, Set existingClasses, + boolean optimizeClientProxyRequestDelegate) { super(generateSources, reflectionRegistration); this.applicationClassPredicate = applicationClassPredicate; this.mockable = mockable; this.existingClasses = existingClasses; + this.optimizeClientProxyRequestDelegate = optimizeClientProxyRequestDelegate; } /** @@ -136,8 +140,9 @@ Collection generate(BeanInfo bean, String beanClassName, clientProxy.getFieldCreator(MOCK_FIELD, providerType.descriptorName()).setModifiers(ACC_PRIVATE | ACC_VOLATILE); } FieldCreator contextField = null; - if (BuiltinScope.APPLICATION.is(bean.getScope())) { - // It is safe to store the application context instance on the proxy + if (BuiltinScope.APPLICATION.is(bean.getScope()) + || (BuiltinScope.REQUEST.is(bean.getScope()) && optimizeClientProxyRequestDelegate)) { + // It is safe to store the context instance on the proxy contextField = clientProxy.getFieldCreator(CONTEXT_FIELD, InjectableContext.class) .setModifiers(ACC_PRIVATE | ACC_FINAL); } @@ -275,11 +280,14 @@ void createConstructor(ClassCreator clientProxy, String superClasName, FieldDesc beanIdentifierHandle); creator.writeInstanceField(beanField, creator.getThis(), beanHandle); if (contextField != null) { - creator.writeInstanceField(contextField, creator.getThis(), creator.invokeInterfaceMethod( - MethodDescriptors.ARC_CONTAINER_GET_ACTIVE_CONTEXT, + // At this point we can be sure there's only one context implementation available + ResultHandle contextList = creator.invokeInterfaceMethod( + MethodDescriptors.ARC_CONTAINER_GET_CONTEXTS, containerHandle, creator .invokeInterfaceMethod(MethodDescriptor.ofMethod(InjectableBean.class, "getScope", Class.class), - beanHandle))); + beanHandle)); + creator.writeInstanceField(contextField, creator.getThis(), + creator.invokeInterfaceMethod(MethodDescriptors.LIST_GET, contextList, creator.load(0))); } creator.returnValue(null); } @@ -305,6 +313,12 @@ void implementDelegate(ClassCreator clientProxy, ProviderType providerType, Fiel FieldDescriptor.of(clientProxy.getClassName(), CONTEXT_FIELD, InjectableContext.class), creator.getThis()), beanHandle)); + } else if (optimizeClientProxyRequestDelegate && BuiltinScope.REQUEST.is(bean.getScope())) { + creator.returnValue(creator.invokeStaticMethod(MethodDescriptors.CLIENT_PROXIES_GET_REQ_SCOPED_DELEGATE, + creator.readInstanceField( + FieldDescriptor.of(clientProxy.getClassName(), CONTEXT_FIELD, InjectableContext.class), + creator.getThis()), + beanHandle)); } else { creator.returnValue(creator.invokeStaticMethod(MethodDescriptors.CLIENT_PROXIES_GET_DELEGATE, beanHandle)); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/CustomAlterableContexts.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/CustomAlterableContexts.java index aa4dcf4189f03a..167e4a01dba7f8 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/CustomAlterableContexts.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/CustomAlterableContexts.java @@ -1,5 +1,6 @@ package io.quarkus.arc.processor; +import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; @@ -19,11 +20,12 @@ public class CustomAlterableContexts { this.applicationClassPredicate = applicationClassPredicate; } - public CustomAlterableContextInfo add(Class contextClass, Boolean isNormal) { + public CustomAlterableContextInfo add(Class contextClass, Boolean isNormal, + Class scopeAnnotation) { String generatedName = contextClass.getName() + "_InjectableContext"; boolean isApplicationClass = applicationClassPredicate.test(DotName.createSimple(contextClass)); CustomAlterableContextInfo result = new CustomAlterableContextInfo(contextClass, isNormal, generatedName, - isApplicationClass); + isApplicationClass, scopeAnnotation); registered.add(result); return result; } @@ -52,13 +54,15 @@ public static class CustomAlterableContextInfo { public final Boolean isNormal; public final String generatedName; public final boolean isApplicationClass; + public final Class scopeAnnotation; CustomAlterableContextInfo(Class contextClass, Boolean isNormal, - String generatedName, boolean isApplicationClass) { + String generatedName, boolean isApplicationClass, Class scopeAnnotation) { this.contextClass = contextClass; this.isNormal = isNormal; this.generatedName = generatedName; this.isApplicationClass = isApplicationClass; + this.scopeAnnotation = scopeAnnotation; } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 756833e572aa00..5ed3aaa1cf0386 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -240,6 +240,9 @@ public final class MethodDescriptors { public static final MethodDescriptor ARC_CONTAINER_GET_ACTIVE_CONTEXT = MethodDescriptor.ofMethod(ArcContainer.class, "getActiveContext", InjectableContext.class, Class.class); + public static final MethodDescriptor ARC_CONTAINER_GET_CONTEXTS = MethodDescriptor.ofMethod(ArcContainer.class, + "getContexts", List.class, Class.class); + public static final MethodDescriptor CONTEXT_GET = MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class, CreationalContext.class); @@ -273,6 +276,9 @@ public final class MethodDescriptors { public static final MethodDescriptor CLIENT_PROXIES_GET_APP_SCOPED_DELEGATE = MethodDescriptor.ofMethod(ClientProxies.class, "getApplicationScopedDelegate", Object.class, InjectableContext.class, InjectableBean.class); + public static final MethodDescriptor CLIENT_PROXIES_GET_REQ_SCOPED_DELEGATE = MethodDescriptor.ofMethod(ClientProxies.class, + "getRequestScopedDelegate", Object.class, InjectableContext.class, InjectableBean.class); + public static final MethodDescriptor CLIENT_PROXIES_GET_DELEGATE = MethodDescriptor.ofMethod(ClientProxies.class, "getDelegate", Object.class, InjectableBean.class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ExtensionsEntryPoint.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ExtensionsEntryPoint.java index 2fec16043a592d..cf490e5604ff8d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ExtensionsEntryPoint.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/bcextensions/ExtensionsEntryPoint.java @@ -221,7 +221,8 @@ public void register(RegistrationContext registrationContext) { if (InjectableContext.class.isAssignableFrom(contextClass)) { config.contextClass((Class) contextClass); } else { - CustomAlterableContextInfo info = customAlterableContexts.add(contextClass, context.isNormal); + CustomAlterableContextInfo info = customAlterableContexts.add(contextClass, context.isNormal, + scopeAnnotation); config.creator(bytecode -> { return bytecode.newInstance(MethodDescriptor.ofConstructor(info.generatedName)); }); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java index 8a2417950e4164..2d8b8e61cffda0 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java @@ -23,6 +23,15 @@ public static T getApplicationScopedDelegate(InjectableContext applicationCo return result; } + // This method is only used if a single request context is present + public static T getRequestScopedDelegate(InjectableContext requestContext, InjectableBean bean) { + T result = requestContext.getIfActive(bean, ClientProxies::newCreationalContext); + if (result == null) { + throw notActive(bean); + } + return result; + } + public static T getDelegate(InjectableBean bean) { List contexts = Arc.container().getContexts(bean.getScope()); T result = null; @@ -46,17 +55,21 @@ public static T getDelegate(InjectableBean bean) { } } if (result == null) { - String msg = String.format( - "%s context was not active when trying to obtain a bean instance for a client proxy of %s", - bean.getScope().getSimpleName(), bean); - if (bean.getScope().equals(RequestScoped.class)) { - msg += "\n\t- you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding"; - } - throw new ContextNotActiveException(msg); + throw notActive(bean); } return result; } + private static ContextNotActiveException notActive(InjectableBean bean) { + String msg = String.format( + "%s context was not active when trying to obtain a bean instance for a client proxy of %s", + bean.getScope().getSimpleName(), bean); + if (bean.getScope().equals(RequestScoped.class)) { + msg += "\n\t- you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding"; + } + return new ContextNotActiveException(msg); + } + private static CreationalContextImpl newCreationalContext(Contextual contextual) { return new CreationalContextImpl<>(contextual); }