This is an automated email from the ASF dual-hosted git repository. ggrzybek pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 5c713226c88 [CAMEL-19564] Add 2nd attempt to instantiate spring beans after reading class definitions from JavaRoutesBuilderLoader 5c713226c88 is described below commit 5c713226c88f2d0ea58b7c0581e94d716b452c3d Author: Grzegorz Grzybek <gr.grzy...@gmail.com> AuthorDate: Fri Jun 30 14:13:14 2023 +0200 [CAMEL-19564] Add 2nd attempt to instantiate spring beans after reading class definitions from JavaRoutesBuilderLoader --- .../org/apache/camel/main/BaseMainSupport.java | 23 ++++-- .../java/org/apache/camel/main/KameletMain.java | 87 +++++++++++++++++++++- 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index e6a12db2d85..aeb9df75289 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -657,6 +657,11 @@ public abstract class BaseMainSupport extends BaseService { recorder.endStep(step); } + // after the routes are read (org.apache.camel.spi.RoutesBuilderLoader did their work), we may have + // new classes defined, so main implementations may have to reconfigure the registry using newly + // available bean definitions + postProcessCamelRegistry(camelContext, mainConfigurationProperties); + // allow doing custom configuration before camel is started for (MainListener listener : listeners) { listener.afterConfigure(this); @@ -896,8 +901,7 @@ public abstract class BaseMainSupport extends BaseService { // Spring's ApplicationContext. // so here, before configuring Camel Context, we can process the registry and let Main implementations // decide how to do it - Registry registry = camelContext.getCamelContextExtension().getRegistry(); - postProcessCamelRegistry(camelContext, config, registry); + preProcessCamelRegistry(camelContext, config); // lookup and configure SPI beans DefaultConfigurationConfigurer.afterConfigure(camelContext); @@ -1152,11 +1156,18 @@ public abstract class BaseMainSupport extends BaseService { * * @param camelContext * @param config - * @param registry */ - protected void postProcessCamelRegistry( - CamelContext camelContext, MainConfigurationProperties config, - Registry registry) { + protected void preProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config) { + } + + /** + * Main implementation may do some additional configuration of the {@link Registry} after loading the routes, but + * before the routes are started. + * + * @param camelContext + * @param config + */ + protected void postProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config) { } private void setRouteTemplateProperties( diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java index 65db3209e72..4a42b9eb3a2 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -79,10 +80,16 @@ import org.apache.camel.support.DefaultContextReloadStrategy; import org.apache.camel.support.PluginHelper; import org.apache.camel.support.RouteOnDemandReloadStrategy; import org.apache.camel.support.service.ServiceHelper; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.SmartFactoryBean; +import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.AbstractResource; +import org.springframework.core.metrics.StartupStep; /** * A Main class for booting up Camel with Kamelet in standalone mode. @@ -101,6 +108,11 @@ public class KameletMain extends MainCommandLineSupport { private DownloadListener downloadListener; private DependencyDownloaderClassLoader classLoader; + // when preparing spring-based beans, we may have problems loading classes which are provided with Java DSL + // that's why some beans should be processed later + private final List<String> delayedBeans = new LinkedList<>(); + private Set<String> infraBeanNames; + public KameletMain() { configureInitialProperties(DEFAULT_KAMELETS_LOCATION); } @@ -607,7 +619,7 @@ public class KameletMain extends MainCommandLineSupport { } @Override - protected void postProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config, Registry registry) { + protected void preProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config) { // camel-kamelet-main has access to Spring libraries, so we can grab XML documents representing // actual Spring Beans and read them using Spring's BeanFactory to populate Camel registry final Map<String, Document> xmls = new TreeMap<>(); @@ -627,10 +639,12 @@ public class KameletMain extends MainCommandLineSupport { // Spring registry and then copy the beans (whether the scope is) final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.setAllowCircularReferences(true); // for now + beanFactory.setBeanClassLoader(classLoader); + registry.bind("SpringBeanFactory", beanFactory); // register some existing beans (the list may change) // would be nice to keep the documentation up to date: docs/user-manual/modules/ROOT/pages/camel-jbang.adoc - Set<String> infraBeanNames = Set.of("CamelContext", "MainConfiguration"); + infraBeanNames = Set.of("CamelContext", "MainConfiguration"); beanFactory.registerSingleton("CamelContext", camelContext); beanFactory.registerSingleton("MainConfiguration", config); // ... @@ -658,9 +672,74 @@ public class KameletMain extends MainCommandLineSupport { // which extra/infra beans are added beanFactory.freezeConfiguration(); - beanFactory.preInstantiateSingletons(); - for (String name : beanFactory.getBeanDefinitionNames()) { + List<String> beanNames = Arrays.asList(beanFactory.getBeanDefinitionNames()); + + // Trigger initialization of all non-lazy singleton beans... + instantiateAndRegisterBeans(beanFactory, beanNames); + } + + @Override + protected void postProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config) { + if (delayedBeans.isEmpty()) { + return; + } + + DefaultListableBeanFactory beanFactory + = registry.lookupByNameAndType("SpringBeanFactory", DefaultListableBeanFactory.class); + + // we have some beans with classes that we couldn't load before. now, after loading the routes + // we may have the needed class definitions + for (String beanName : delayedBeans) { + BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName); + if (bd instanceof AbstractBeanDefinition abd) { + if (!abd.hasBeanClass()) { + Class<?> c = camelContext.getClassResolver().resolveClass(abd.getBeanClassName()); + abd.setBeanClass(c); + } + } + } + + instantiateAndRegisterBeans(beanFactory, delayedBeans); + } + + private void instantiateAndRegisterBeans(DefaultListableBeanFactory beanFactory, List<String> beanNames) { + List<String> instantiatedBeanNames = new LinkedList<>(); + + for (String beanName : beanNames) { + BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName); + if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { + try { + if (beanFactory.isFactoryBean(beanName)) { + Object bean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); + if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) { + beanFactory.getBean(beanName); + instantiatedBeanNames.add(beanName); + } + } else { + beanFactory.getBean(beanName); + instantiatedBeanNames.add(beanName); + } + } catch (CannotLoadBeanClassException ignored) { + // we'll try to resolve later + delayedBeans.add(beanName); + } + } + } + + // Trigger post-initialization callback for all applicable beans... + for (String beanName : instantiatedBeanNames) { + Object singletonInstance = beanFactory.getSingleton(beanName); + if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) { + StartupStep smartInitialize = beanFactory.getApplicationStartup() + .start("spring.beans.smart-initialize") + .tag("beanName", beanName); + smartSingleton.afterSingletonsInstantiated(); + smartInitialize.end(); + } + } + + for (String name : instantiatedBeanNames) { if (infraBeanNames.contains(name)) { continue; }