Repository: quarkusio/quarkus
Issue: ComponentsProviderGenerator - break the generated getComponents() method for large deployments into multiple methods

Otherwise we end up with `org.objectweb.asm.MethodTooLargeException`. `BytecodeRecorderImpl` already has something similar implemented.

Changed files:
independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java
independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java
independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.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 4f8c40e307d..8a638e6ad37 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
@@ -144,7 +144,7 @@ Collection<Resource> generateSyntheticBean(BeanInfo bean, ReflectionRegistration
 
         // Foo_Bean implements InjectableBean<T>
         ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName)
-                .interfaces(InjectableBean.class).build();
+                .interfaces(InjectableBean.class, Supplier.class).build();
 
         // Fields
         FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class)
@@ -195,6 +195,7 @@ Collection<Resource> generateSyntheticBean(BeanInfo bean, ReflectionRegistration
         constructor.returnValue(null);
 
         implementGetIdentifier(bean, beanCreator);
+        implementSupplierGet(beanCreator);
         if (!bean.hasDefaultDestroy()) {
             implementDestroy(bean, beanCreator, providerTypeName, Collections.emptyMap(), reflectionRegistration,
                     isApplicationClass, baseName);
@@ -246,7 +247,7 @@ Collection<Resource> generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle
 
         // Foo_Bean implements InjectableBean<T>
         ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName)
-                .interfaces(InjectableBean.class).build();
+                .interfaces(InjectableBean.class, Supplier.class).build();
 
         // Fields
         FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class)
@@ -275,6 +276,7 @@ Collection<Resource> generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle
                 annotationLiterals);
 
         implementGetIdentifier(bean, beanCreator);
+        implementSupplierGet(beanCreator);
         if (!bean.hasDefaultDestroy()) {
             implementDestroy(bean, beanCreator, providerTypeName, injectionPointToProviderSupplierField, reflectionRegistration,
                     isApplicationClass, baseName);
@@ -341,7 +343,7 @@ Collection<Resource> generateProducerMethodBean(BeanInfo bean, MethodInfo produc
 
         // Foo_Bean implements InjectableBean<T>
         ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName)
-                .interfaces(InjectableBean.class).build();
+                .interfaces(InjectableBean.class, Supplier.class).build();
 
         // Fields
         FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class)
@@ -369,6 +371,7 @@ Collection<Resource> generateProducerMethodBean(BeanInfo bean, MethodInfo produc
                 annotationLiterals);
 
         implementGetIdentifier(bean, beanCreator);
+        implementSupplierGet(beanCreator);
         if (!bean.hasDefaultDestroy()) {
             implementDestroy(bean, beanCreator, providerTypeName, injectionPointToProviderField, reflectionRegistration,
                     isApplicationClass, baseName);
@@ -426,7 +429,7 @@ Collection<Resource> generateProducerFieldBean(BeanInfo bean, FieldInfo producer
 
         // Foo_Bean implements InjectableBean<T>
         ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName)
-                .interfaces(InjectableBean.class).build();
+                .interfaces(InjectableBean.class, Supplier.class).build();
 
         // Fields
         FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class)
@@ -450,6 +453,7 @@ Collection<Resource> generateProducerFieldBean(BeanInfo bean, FieldInfo producer
                 annotationLiterals);
 
         implementGetIdentifier(bean, beanCreator);
+        implementSupplierGet(beanCreator);
         if (!bean.hasDefaultDestroy()) {
             implementDestroy(bean, beanCreator, providerTypeName, null, reflectionRegistration, isApplicationClass, baseName);
         }
@@ -1514,6 +1518,11 @@ protected void implementGetName(BeanInfo bean, ClassCreator beanCreator) {
         }
     }
 
+    protected void implementSupplierGet(ClassCreator beanCreator) {
+        MethodCreator get = beanCreator.getMethodCreator("get", Object.class).setModifiers(ACC_PUBLIC);
+        get.returnValue(get.getThis());
+    }
+
     private String getProxyTypeName(BeanInfo bean, String baseName) {
         return getPackageName(bean) + "." + baseName + ClientProxyGenerator.CLIENT_PROXY_SUFFIX;
     }
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java
index 232dd3c348e..8c6c60da26b 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java
@@ -1,12 +1,12 @@
 package io.quarkus.arc.processor;
 
 import static java.util.stream.Collectors.toList;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
 
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.Components;
 import io.quarkus.arc.ComponentsProvider;
-import io.quarkus.arc.impl.LazyValue;
 import io.quarkus.arc.processor.ResourceOutput.Resource;
 import io.quarkus.gizmo.ClassCreator;
 import io.quarkus.gizmo.MethodCreator;
@@ -38,8 +38,9 @@
 public class ComponentsProviderGenerator extends AbstractGenerator {
 
     static final String COMPONENTS_PROVIDER_SUFFIX = "_ComponentsProvider";
-
     static final String SETUP_PACKAGE = Arc.class.getPackage().getName() + ".setup";
+    static final String ADD_OBSERVERS = "addObservers";
+    static final String ADD_BEANS = "addBeans";
 
     protected final AnnotationLiteralProcessor annotationLiterals;
 
@@ -67,51 +68,73 @@ Collection<Resource> generate(String name, BeanDeployment beanDeployment, Map<Be
         MethodCreator getComponents = componentsProvider.getMethodCreator("getComponents", Components.class)
                 .setModifiers(ACC_PUBLIC);
 
-        // Map<String, Supplier<InjectableBean<?>>> beanIdToBeanSupplier = new HashMap<>();
-        ResultHandle beanIdToBeanSupplierHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
-
+        // Maps a bean to all injection points it is resolved to 
         // Bar -> Foo, Baz
         // Foo -> Baz
         // Interceptor -> Baz
-        Map<BeanInfo, List<BeanInfo>> beanToInjections = new HashMap<>();
-        for (BeanInfo bean : beanDeployment.getBeans()) {
-            if (bean.isProducerMethod() || bean.isProducerField()) {
-                beanToInjections.computeIfAbsent(bean.getDeclaringBean(), d -> new ArrayList<>()).add(bean);
-            }
-            for (Injection injection : bean.getInjections()) {
-                for (InjectionPointInfo injectionPoint : injection.injectionPoints) {
-                    if (!BuiltinBean.resolvesTo(injectionPoint)) {
-                        beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()).add(bean);
-                    }
-                }
-            }
-            if (bean.getDisposer() != null) {
-                for (InjectionPointInfo injectionPoint : bean.getDisposer().getInjection().injectionPoints) {
-                    if (!BuiltinBean.resolvesTo(injectionPoint)) {
-                        beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()).add(bean);
-                    }
-                }
-            }
-            for (InterceptorInfo interceptor : bean.getBoundInterceptors()) {
-                beanToInjections.computeIfAbsent(interceptor, d -> new ArrayList<>()).add(bean);
-            }
+        Map<BeanInfo, List<BeanInfo>> beanToInjections = initBeanToInjections(beanDeployment);
+
+        // Break bean processing into multiple addBeans() methods
+        // Map<String, InjectableBean<?>>
+        ResultHandle beanIdToBeanHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
+        processBeans(componentsProvider, getComponents, beanIdToBeanHandle, beanToInjections, beanToGeneratedName,
+                beanDeployment);
+
+        // Break observers processing into multiple ddObservers() methods
+        ResultHandle observersHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
+        processObservers(componentsProvider, getComponents, beanDeployment, beanIdToBeanHandle, observersHandle,
+                observerToGeneratedName);
+
+        // Custom contexts
+        ResultHandle contextsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
+        for (Entry<ScopeInfo, Function<MethodCreator, ResultHandle>> entry : beanDeployment.getCustomContexts().entrySet()) {
+            ResultHandle contextHandle = entry.getValue().apply(getComponents);
+            getComponents.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, contextsHandle, contextHandle);
         }
-        // Also process interceptors injection points
-        for (InterceptorInfo interceptor : beanDeployment.getInterceptors()) {
-            for (Injection injection : interceptor.getInjections()) {
-                for (InjectionPointInfo injectionPoint : injection.injectionPoints) {
-                    if (!BuiltinBean.resolvesTo(injectionPoint)) {
-                        beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>())
-                                .add(interceptor);
-                    }
-                }
+
+        ResultHandle transitiveBindingsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
+        for (Entry<DotName, Set<AnnotationInstance>> entry : beanDeployment.getTransitiveInterceptorBindings().entrySet()) {
+            ResultHandle bindingsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashSet.class));
+            for (AnnotationInstance binding : entry.getValue()) {
+                // Create annotation literals first
+                ClassInfo bindingClass = beanDeployment.getInterceptorBinding(binding.name());
+                getComponents.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle,
+                        annotationLiterals.process(getComponents, classOutput, bindingClass, binding,
+                                SETUP_PACKAGE));
             }
+            getComponents.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, transitiveBindingsHandle,
+                    getComponents.loadClass(entry.getKey().toString()), bindingsHandle);
         }
 
-        Map<BeanInfo, LazyValue<ResultHandle>> beanToResultSupplierHandle = new HashMap<>();
-        List<BeanInfo> processed = new ArrayList<>();
+        ResultHandle beansHandle = getComponents.invokeInterfaceMethod(
+                MethodDescriptor.ofMethod(Map.class, "values", Collection.class),
+                beanIdToBeanHandle);
+
+        ResultHandle componentsHandle = getComponents.newInstance(
+                MethodDescriptor.ofConstructor(Components.class, Collection.class, Collection.class, Collection.class,
+                        Map.class),
+                beansHandle, observersHandle, contextsHandle, transitiveBindingsHandle);
+        getComponents.returnValue(componentsHandle);
+
+        // Finally write the bytecode
+        componentsProvider.close();
+
+        List<Resource> resources = new ArrayList<>();
+        for (Resource resource : classOutput.getResources()) {
+            resources.add(resource);
+            resources.add(ResourceImpl.serviceProvider(ComponentsProvider.class.getName(),
+                    (resource.getName().replace('/', '.')).getBytes(Charset.forName("UTF-8"))));
+        }
+        return resources;
+    }
+
+    private void processBeans(ClassCreator componentsProvider, MethodCreator getComponents, ResultHandle beanIdToBeanHandle,
+            Map<BeanInfo, List<BeanInfo>> beanToInjections,
+            Map<BeanInfo, String> beanToGeneratedName, BeanDeployment beanDeployment) {
+
+        Set<BeanInfo> processed = new HashSet<>();
+        BeanAdder beanAdder = new BeanAdder(componentsProvider, getComponents, processed);
 
-        // At this point we are going to fill the beanIdToBeanSupplier map
         // - iterate over beanToInjections entries and process beans for which all dependencies are already present in the map
         // - when a bean is processed the map entry is removed
         // - if we're stuck and the map is not empty ISE is thrown
@@ -129,12 +152,12 @@ Collection<Resource> generate(String name, BeanDeployment beanDeployment, Map<Be
             }
             stuck = true;
             // First try to proces beans that are not dependencies
-            stuck = addBeans(beanToInjections, processed, beanToResultSupplierHandle, getComponents, beanIdToBeanSupplierHandle,
+            stuck = addBeans(beanAdder, beanToInjections, processed, beanIdToBeanHandle,
                     beanToGeneratedName, b -> !isDependency(b, beanToInjections));
             if (stuck) {
                 // It seems we're stuck but we can try to process normal scoped beans that can prevent a circular dependency
-                stuck = addBeans(beanToInjections, processed, beanToResultSupplierHandle, getComponents,
-                        beanIdToBeanSupplierHandle, beanToGeneratedName,
+                stuck = addBeans(beanAdder, beanToInjections, processed, beanIdToBeanHandle,
+                        beanToGeneratedName,
                         b -> {
                             // Try to process non-producer beans first, including declaring beans of producers
                             if (b.isProducerField() || b.isProducerMethod()) {
@@ -143,8 +166,8 @@ Collection<Resource> generate(String name, BeanDeployment beanDeployment, Map<Be
                             return b.getScope().isNormal() || !isDependency(b, beanToInjections);
                         });
                 if (stuck) {
-                    stuck = addBeans(beanToInjections, processed, beanToResultSupplierHandle, getComponents,
-                            beanIdToBeanSupplierHandle, beanToGeneratedName,
+                    stuck = addBeans(beanAdder, beanToInjections, processed,
+                            beanIdToBeanHandle, beanToGeneratedName,
                             b -> !isDependency(b, beanToInjections) || b.getScope().isNormal());
                 }
             }
@@ -152,97 +175,78 @@ Collection<Resource> generate(String name, BeanDeployment beanDeployment, Map<Be
         // Finally process beans and interceptors that are not dependencies
         for (BeanInfo bean : beanDeployment.getBeans()) {
             if (!processed.contains(bean)) {
-                addBean(getComponents, beanIdToBeanSupplierHandle, bean, beanToGeneratedName, beanToResultSupplierHandle);
+                beanAdder.addBean(bean, beanIdToBeanHandle, beanToGeneratedName);
             }
         }
         for (BeanInfo interceptor : beanDeployment.getInterceptors()) {
             if (!processed.contains(interceptor)) {
-                addBean(getComponents, beanIdToBeanSupplierHandle, interceptor, beanToGeneratedName,
-                        beanToResultSupplierHandle);
+                beanAdder.addBean(interceptor, beanIdToBeanHandle, beanToGeneratedName);
             }
         }
 
-        // Observers
-        ResultHandle observersHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
-        for (ObserverInfo observer : beanDeployment.getObservers()) {
-            String observerType = observerToGeneratedName.get(observer);
-            List<InjectionPointInfo> injectionPoints = observer.getInjection().injectionPoints.stream()
-                    .filter(ip -> !BuiltinBean.resolvesTo(ip))
-                    .collect(toList());
-            List<ResultHandle> params = new ArrayList<>();
-            List<String> paramTypes = new ArrayList<>();
+        // Make sure the last addBeans() method is closed properly
+        beanAdder.close();
+    }
 
-            ResultHandle resultSupplierHandle = beanToResultSupplierHandle.get(observer.getDeclaringBean()).get();
-            params.add(resultSupplierHandle);
-            paramTypes.add(Type.getDescriptor(Supplier.class));
-            for (InjectionPointInfo injectionPoint : injectionPoints) {
-                resultSupplierHandle = beanToResultSupplierHandle.get(injectionPoint.getResolvedBean()).get();
-                params.add(resultSupplierHandle);
-                paramTypes.add(Type.getDescriptor(Supplier.class));
+    private void processObservers(ClassCreator componentsProvider, MethodCreator getComponents, BeanDeployment beanDeployment,
+            ResultHandle beanIdToBeanHandle, ResultHandle observersHandle, Map<ObserverInfo, String> observerToGeneratedName) {
+        try (ObserverAdder observerAdder = new ObserverAdder(componentsProvider, getComponents)) {
+            for (ObserverInfo observer : beanDeployment.getObservers()) {
+                observerAdder.addObserver(observer, beanIdToBeanHandle, observersHandle, observerToGeneratedName);
             }
-            ResultHandle observerInstance = getComponents.newInstance(
-                    MethodDescriptor.ofConstructor(observerType, paramTypes.toArray(new String[0])),
-                    params.toArray(new ResultHandle[0]));
-            getComponents.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, observersHandle, observerInstance);
-        }
-
-        // Custom contexts
-        ResultHandle contextsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
-        for (Entry<ScopeInfo, Function<MethodCreator, ResultHandle>> entry : beanDeployment.getCustomContexts().entrySet()) {
-            ResultHandle contextHandle = entry.getValue().apply(getComponents);
-            getComponents.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, contextsHandle, contextHandle);
         }
+    }
 
-        ResultHandle transitiveBindingsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
-        for (Entry<DotName, Set<AnnotationInstance>> entry : beanDeployment.getTransitiveInterceptorBindings().entrySet()) {
-            ResultHandle bindingsHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(HashSet.class));
-            for (AnnotationInstance binding : entry.getValue()) {
-                // Create annotation literals first
-                ClassInfo bindingClass = beanDeployment.getInterceptorBinding(binding.name());
-                getComponents.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle,
-                        annotationLiterals.process(getComponents, classOutput, bindingClass, binding,
-                                SETUP_PACKAGE));
+    private Map<BeanInfo, List<BeanInfo>> initBeanToInjections(BeanDeployment beanDeployment) {
+        Map<BeanInfo, List<BeanInfo>> beanToInjections = new HashMap<>();
+        for (BeanInfo bean : beanDeployment.getBeans()) {
+            if (bean.isProducerMethod() || bean.isProducerField()) {
+                beanToInjections.computeIfAbsent(bean.getDeclaringBean(), d -> new ArrayList<>()).add(bean);
+            }
+            for (Injection injection : bean.getInjections()) {
+                for (InjectionPointInfo injectionPoint : injection.injectionPoints) {
+                    if (!BuiltinBean.resolvesTo(injectionPoint)) {
+                        beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()).add(bean);
+                    }
+                }
+            }
+            if (bean.getDisposer() != null) {
+                for (InjectionPointInfo injectionPoint : bean.getDisposer().getInjection().injectionPoints) {
+                    if (!BuiltinBean.resolvesTo(injectionPoint)) {
+                        beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()).add(bean);
+                    }
+                }
+            }
+            for (InterceptorInfo interceptor : bean.getBoundInterceptors()) {
+                beanToInjections.computeIfAbsent(interceptor, d -> new ArrayList<>()).add(bean);
             }
-            getComponents.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, transitiveBindingsHandle,
-                    getComponents.loadClass(entry.getKey().toString()), bindingsHandle);
         }
-
-        // final Collection beans = beanIdToBeanSupplier.values();
-        ResultHandle beansHandle = getComponents.invokeInterfaceMethod(
-                MethodDescriptor.ofMethod(Map.class, "values", Collection.class),
-                beanIdToBeanSupplierHandle);
-
-        ResultHandle componentsHandle = getComponents.newInstance(
-                MethodDescriptor.ofConstructor(Components.class, Collection.class, Collection.class, Collection.class,
-                        Map.class),
-                beansHandle, observersHandle, contextsHandle, transitiveBindingsHandle);
-        getComponents.returnValue(componentsHandle);
-
-        // Finally write the bytecode
-        componentsProvider.close();
-
-        List<Resource> resources = new ArrayList<>();
-        for (Resource resource : classOutput.getResources()) {
-            resources.add(resource);
-            // TODO proper name conversion
-            resources.add(ResourceImpl.serviceProvider(ComponentsProvider.class.getName(),
-                    (resource.getName().replace('/', '.')).getBytes(Charset.forName("UTF-8"))));
+        // Also process interceptors injection points
+        for (InterceptorInfo interceptor : beanDeployment.getInterceptors()) {
+            for (Injection injection : interceptor.getInjections()) {
+                for (InjectionPointInfo injectionPoint : injection.injectionPoints) {
+                    if (!BuiltinBean.resolvesTo(injectionPoint)) {
+                        beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>())
+                                .add(interceptor);
+                    }
+                }
+            }
         }
-        return resources;
+        // Note that we do not have to process observer injection points because observers are always processed after all beans are ready 
+        return beanToInjections;
     }
 
-    private boolean addBeans(Map<BeanInfo, List<BeanInfo>> beanToInjections, List<BeanInfo> processed,
-            Map<BeanInfo, LazyValue<ResultHandle>> beanToResultSupplierHandle, MethodCreator getComponents,
-            ResultHandle beanIdToBeanSupplierHandle, Map<BeanInfo, String> beanToGeneratedName, Predicate<BeanInfo> filter) {
+    private boolean addBeans(BeanAdder beanAdder,
+            Map<BeanInfo, List<BeanInfo>> beanToInjections, Set<BeanInfo> processed,
+            ResultHandle beanIdToBeanHandle, Map<BeanInfo, String> beanToGeneratedName, Predicate<BeanInfo> filter) {
         boolean stuck = true;
         for (Iterator<Entry<BeanInfo, List<BeanInfo>>> iterator = beanToInjections.entrySet().iterator(); iterator
                 .hasNext();) {
             Entry<BeanInfo, List<BeanInfo>> entry = iterator.next();
             BeanInfo bean = entry.getKey();
             if (filter.test(bean)) {
-                addBean(getComponents, beanIdToBeanSupplierHandle, bean, beanToGeneratedName,
-                        beanToResultSupplierHandle);
                 iterator.remove();
+                beanAdder.addBean(bean, beanIdToBeanHandle, beanToGeneratedName);
                 processed.add(bean);
                 stuck = false;
             }
@@ -250,75 +254,192 @@ private boolean addBeans(Map<BeanInfo, List<BeanInfo>> beanToInjections, List<Be
         return stuck;
     }
 
-    private void addBean(MethodCreator getComponents, ResultHandle beanIdToBeanSupplierHandle, BeanInfo bean,
-            Map<BeanInfo, String> beanToGeneratedName,
-            Map<BeanInfo, LazyValue<ResultHandle>> beanToResultSupplierHandle) {
+    private boolean isDependency(BeanInfo bean, Map<BeanInfo, List<BeanInfo>> beanToInjections) {
+        for (Iterator<Entry<BeanInfo, List<BeanInfo>>> iterator = beanToInjections.entrySet().iterator(); iterator.hasNext();) {
+            Entry<BeanInfo, List<BeanInfo>> entry = iterator.next();
+            if (entry.getValue().contains(bean)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static class ObserverAdder implements AutoCloseable {
+
+        private static final int GROUP_LIMIT = 30;
+        private int group;
+        private int observersAdded;
+        private MethodCreator addObserversMethod;
+        private final MethodCreator getComponentsMethod;
+        private final ClassCreator componentsProvider;
 
-        String beanType = beanToGeneratedName.get(bean);
-        if (beanType == null) {
-            throw new IllegalStateException("No bean type found for: " + bean);
+        public ObserverAdder(ClassCreator componentsProvider, MethodCreator getComponentsMethod) {
+            this.group = 1;
+            this.getComponentsMethod = getComponentsMethod;
+            this.componentsProvider = componentsProvider;
         }
 
-        List<InjectionPointInfo> injectionPoints = bean.getInjections().stream().flatMap(i -> i.injectionPoints.stream())
-                .filter(ip -> !BuiltinBean.resolvesTo(ip)).collect(toList());
-        List<ResultHandle> params = new ArrayList<>();
-        List<String> paramTypes = new ArrayList<>();
-
-        if (bean.isProducerMethod() || bean.isProducerField()) {
-            ResultHandle resultSupplierHandle = beanToResultSupplierHandle.get(bean.getDeclaringBean()).get();
-            if (resultSupplierHandle == null) {
-                throw new IllegalStateException(
-                        "A supplier for a declaring bean of a producer bean is not available - most probaly an unsupported circular dependency use case \\n - declaring bean: "
-                                + bean.getDeclaringBean() + "\\n - producer bean: " + bean);
+        public void close() {
+            if (addObserversMethod != null) {
+                addObserversMethod.returnValue(null);
             }
-            params.add(resultSupplierHandle);
-            paramTypes.add(Type.getDescriptor(Supplier.class));
         }
-        for (InjectionPointInfo injectionPoint : injectionPoints) {
 
-            // new MapValueSupplier(beanIdToBeanSupplier, id);
-            ResultHandle beanIdHandle = getComponents.load(injectionPoint.getResolvedBean().getIdentifier());
-            ResultHandle beanSupplierHandle = getComponents.newInstance(MethodDescriptors.MAP_VALUE_SUPPLIER_CONSTRUCTOR,
-                    beanIdToBeanSupplierHandle, beanIdHandle);
+        void addObserver(ObserverInfo observer, ResultHandle beanIdToBeanHandle, ResultHandle observersHandle,
+                Map<ObserverInfo, String> observerToGeneratedName) {
+
+            if (addObserversMethod == null || observersAdded >= GROUP_LIMIT) {
+                if (addObserversMethod != null) {
+                    addObserversMethod.returnValue(null);
+                }
+                observersAdded = 0;
+                // First add next addObservers(map) method
+                addObserversMethod = componentsProvider
+                        .getMethodCreator(ADD_OBSERVERS + group++, void.class, Map.class, List.class)
+                        .setModifiers(ACC_PRIVATE);
+                // Invoke addObservers(map) inside the getComponents() method
+                getComponentsMethod.invokeVirtualMethod(
+                        MethodDescriptor.ofMethod(componentsProvider.getClassName(),
+                                addObserversMethod.getMethodDescriptor().getName(), void.class, Map.class, List.class),
+                        getComponentsMethod.getThis(), beanIdToBeanHandle, observersHandle);
+            }
+            observersAdded++;
+
+            // Append to the addObservers() method body
+            beanIdToBeanHandle = addObserversMethod.getMethodParam(0);
+            observersHandle = addObserversMethod.getMethodParam(1);
+
+            String observerType = observerToGeneratedName.get(observer);
+            List<InjectionPointInfo> injectionPoints = observer.getInjection().injectionPoints.stream()
+                    .filter(ip -> !BuiltinBean.resolvesTo(ip))
+                    .collect(toList());
+            List<ResultHandle> params = new ArrayList<>();
+            List<String> paramTypes = new ArrayList<>();
 
-            params.add(beanSupplierHandle);
+            // First param - declaring bean
+            params.add(addObserversMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET,
+                    beanIdToBeanHandle, addObserversMethod.load(observer.getDeclaringBean().getIdentifier())));
             paramTypes.add(Type.getDescriptor(Supplier.class));
-        }
-        if (bean.getDisposer() != null) {
-            for (InjectionPointInfo injectionPoint : bean.getDisposer().getInjection().injectionPoints) {
-                ResultHandle resultSupplierHandle = beanToResultSupplierHandle.get(injectionPoint.getResolvedBean()).get();
-                params.add(resultSupplierHandle);
+            for (InjectionPointInfo injectionPoint : injectionPoints) {
+                params.add(addObserversMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET,
+                        beanIdToBeanHandle, addObserversMethod.load(injectionPoint.getResolvedBean().getIdentifier())));
                 paramTypes.add(Type.getDescriptor(Supplier.class));
             }
-        }
-        for (InterceptorInfo interceptor : bean.getBoundInterceptors()) {
-            ResultHandle resultSupplierHandle = beanToResultSupplierHandle.get(interceptor).get();
-            params.add(resultSupplierHandle);
-            paramTypes.add(Type.getDescriptor(Supplier.class));
+            ResultHandle observerInstance = addObserversMethod.newInstance(
+                    MethodDescriptor.ofConstructor(observerType, paramTypes.toArray(new String[0])),
+                    params.toArray(new ResultHandle[0]));
+            addObserversMethod.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, observersHandle, observerInstance);
         }
 
-        // Foo_Bean bean2 = new Foo_Bean(bean2)
-        ResultHandle beanInstance = getComponents.newInstance(
-                MethodDescriptor.ofConstructor(beanType, paramTypes.toArray(new String[0])),
-                params.toArray(new ResultHandle[0]));
-        // beans.put(..., bean2)
-        final ResultHandle beanInfoHandle = getComponents.load(bean.getIdentifier());
-        getComponents.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, beanIdToBeanSupplierHandle, beanInfoHandle,
-                beanInstance);
-
-        // Create a Supplier that will return the bean instance at runtime.
-        beanToResultSupplierHandle.put(bean, new LazyValue<>(
-                () -> getComponents.newInstance(MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, beanInstance)));
     }
 
-    private boolean isDependency(BeanInfo bean, Map<BeanInfo, List<BeanInfo>> beanToInjections) {
-        for (Iterator<Entry<BeanInfo, List<BeanInfo>>> iterator = beanToInjections.entrySet().iterator(); iterator.hasNext();) {
-            Entry<BeanInfo, List<BeanInfo>> entry = iterator.next();
-            if (entry.getValue().contains(bean)) {
-                return true;
+    static class BeanAdder implements AutoCloseable {
+
+        private static final int GROUP_LIMIT = 30;
+        private int group;
+        private int beansWritten;
+        private MethodCreator addBeansMethod;
+        private final MethodCreator getComponentsMethod;
+        private final ClassCreator componentsProvider;
+        private final Set<BeanInfo> processedBeans;
+
+        public BeanAdder(ClassCreator componentsProvider, MethodCreator getComponentsMethod, Set<BeanInfo> processed) {
+            this.group = 1;
+            this.getComponentsMethod = getComponentsMethod;
+            this.componentsProvider = componentsProvider;
+            this.processedBeans = processed;
+        }
+
+        public void close() {
+            if (addBeansMethod != null) {
+                addBeansMethod.returnValue(null);
             }
         }
-        return false;
+
+        void addBean(BeanInfo bean, ResultHandle beanIdToBeanHandle,
+                Map<BeanInfo, String> beanToGeneratedName) {
+
+            if (addBeansMethod == null || beansWritten >= GROUP_LIMIT) {
+                if (addBeansMethod != null) {
+                    addBeansMethod.returnValue(null);
+                }
+                beansWritten = 0;
+                // First add next addBeans(map) method
+                addBeansMethod = componentsProvider.getMethodCreator(ADD_BEANS + group++, void.class, Map.class)
+                        .setModifiers(ACC_PRIVATE);
+                // Invoke addBeans(map) inside the getComponents() method
+                getComponentsMethod.invokeVirtualMethod(
+                        MethodDescriptor.ofMethod(componentsProvider.getClassName(),
+                                addBeansMethod.getMethodDescriptor().getName(), void.class, Map.class),
+                        getComponentsMethod.getThis(), beanIdToBeanHandle);
+            }
+            beansWritten++;
+
+            // Append to the addBeans() method body
+            beanIdToBeanHandle = addBeansMethod.getMethodParam(0);
+            String beanType = beanToGeneratedName.get(bean);
+            if (beanType == null) {
+                throw new IllegalStateException("No bean type found for: " + bean);
+            }
+
+            List<InjectionPointInfo> injectionPoints = bean.getInjections().stream().flatMap(i -> i.injectionPoints.stream())
+                    .filter(ip -> !BuiltinBean.resolvesTo(ip)).collect(toList());
+            List<ResultHandle> params = new ArrayList<>();
+            List<String> paramTypes = new ArrayList<>();
+
+            if (bean.isProducerMethod() || bean.isProducerField()) {
+                if (!processedBeans.contains(bean.getDeclaringBean())) {
+                    throw new IllegalStateException(
+                            "Declaring bean of a producer bean is not available - most probaly an unsupported circular dependency use case \\n - declaring bean: "
+                                    + bean.getDeclaringBean() + "\\n - producer bean: " + bean);
+                }
+                params.add(addBeansMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET,
+                        beanIdToBeanHandle, addBeansMethod.load(bean.getDeclaringBean().getIdentifier())));
+                paramTypes.add(Type.getDescriptor(Supplier.class));
+            }
+            for (InjectionPointInfo injectionPoint : injectionPoints) {
+                if (processedBeans.contains(injectionPoint.getResolvedBean())) {
+                    params.add(addBeansMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET,
+                            beanIdToBeanHandle, addBeansMethod.load(injectionPoint.getResolvedBean().getIdentifier())));
+                } else {
+                    // Dependency was not processed yet - use MapValueSupplier
+                    params.add(addBeansMethod.newInstance(
+                            MethodDescriptors.MAP_VALUE_SUPPLIER_CONSTRUCTOR,
+                            beanIdToBeanHandle, addBeansMethod.load(injectionPoint.getResolvedBean().getIdentifier())));
+                }
+                paramTypes.add(Type.getDescriptor(Supplier.class));
+            }
+            if (bean.getDisposer() != null) {
+                for (InjectionPointInfo injectionPoint : bean.getDisposer().getInjection().injectionPoints) {
+                    params.add(addBeansMethod.newInstance(
+                            MethodDescriptors.MAP_VALUE_SUPPLIER_CONSTRUCTOR,
+                            beanIdToBeanHandle, addBeansMethod.load(injectionPoint.getResolvedBean().getIdentifier())));
+                    paramTypes.add(Type.getDescriptor(Supplier.class));
+                }
+            }
+            for (InterceptorInfo interceptor : bean.getBoundInterceptors()) {
+                if (processedBeans.contains(interceptor)) {
+                    params.add(addBeansMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET,
+                            beanIdToBeanHandle, addBeansMethod.load(interceptor.getIdentifier())));
+                } else {
+                    // Bound interceptor was not processed yet - use MapValueSupplier
+                    params.add(addBeansMethod.newInstance(
+                            MethodDescriptors.MAP_VALUE_SUPPLIER_CONSTRUCTOR,
+                            beanIdToBeanHandle, addBeansMethod.load(interceptor.getIdentifier())));
+                }
+                paramTypes.add(Type.getDescriptor(Supplier.class));
+            }
+
+            // Foo_Bean bean = new Foo_Bean(bean3)
+            ResultHandle beanInstance = addBeansMethod.newInstance(
+                    MethodDescriptor.ofConstructor(beanType, paramTypes.toArray(new String[0])),
+                    params.toArray(new ResultHandle[0]));
+            // beans.put(id, bean)
+            final ResultHandle beanIdHandle = addBeansMethod.load(bean.getIdentifier());
+            addBeansMethod.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, beanIdToBeanHandle, beanIdHandle,
+                    beanInstance);
+        }
+
     }
 
 }
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java
index 07c570520f4..2bcd661e701 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import javax.enterprise.inject.spi.InterceptionType;
 import javax.interceptor.InvocationContext;
 import org.jboss.jandex.AnnotationInstance;
@@ -73,7 +74,7 @@ Collection<Resource> generate(InterceptorInfo interceptor, ReflectionRegistratio
 
         // MyInterceptor_Bean implements InjectableInterceptor<T>
         ClassCreator interceptorCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName)
-                .interfaces(InjectableInterceptor.class)
+                .interfaces(InjectableInterceptor.class, Supplier.class)
                 .build();
 
         // Fields
@@ -92,6 +93,7 @@ Collection<Resource> generate(InterceptorInfo interceptor, ReflectionRegistratio
                 bindings.getFieldDescriptor());
 
         implementGetIdentifier(interceptor, interceptorCreator);
+        implementSupplierGet(interceptorCreator);
         implementCreate(classOutput, interceptorCreator, interceptor, providerTypeName, baseName, injectionPointToProviderField,
                 interceptorToProviderField,
                 reflectionRegistration, targetPackage, isApplicationClass);