You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/08/23 07:36:19 UTC

[camel] 01/06: CAMEL-19772: camel-core - Dump routes to include custom beans

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch camel19772
in repository https://gitbox.apache.org/repos/asf/camel.git

commit aee85e3555e6505f8269496e86c9a028bb58d9e3
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Aug 22 12:43:33 2023 +0200

    CAMEL-19772: camel-core - Dump routes to include custom beans
---
 .../main/camel-main-configuration-metadata.json    |   2 +-
 .../org/apache/camel/spi/DumpRoutesStrategy.java   |   4 +-
 .../org/apache/camel/spi/ModelToXMLDumper.java     |  12 ++
 .../org/apache/camel/spi/ModelToYAMLDumper.java    |  12 ++
 .../org/apache/camel/impl/DefaultCamelContext.java |  11 ++
 .../camel/impl/DefaultDumpRoutesStrategy.java      | 121 +++++++++++++++++++--
 .../java/org/apache/camel/impl/DefaultModel.java   |  15 +++
 .../org/apache/camel/builder/RouteBuilder.java     |  22 ++++
 .../main/java/org/apache/camel/model/Model.java    |  11 ++
 .../camel-main-configuration-metadata.json         |   2 +-
 core/camel-main/src/main/docs/main.adoc            |   2 +-
 .../camel/main/DefaultConfigurationProperties.java |   8 +-
 .../main/java/org/apache/camel/util/IOHelper.java  |  14 +--
 .../org/apache/camel/xml/LwModelToXMLDumper.java   |  77 +++++++++++++
 .../camel/xml/jaxb/JaxbModelToXMLDumper.java       |  76 +++++++++++++
 .../org/apache/camel/yaml/LwModelToYAMLDumper.java |  81 ++++++++++++++
 .../camel/dsl/xml/io/XmlRoutesBuilderLoader.java   |   6 +
 .../dsl/yaml/deserializers/BeansDeserializer.java  |   6 +
 18 files changed, 455 insertions(+), 27 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 302701cffaf..6dd2a3c22de 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -40,7 +40,7 @@
     { "name": "camel.main.devConsoleEnabled", "description": "Whether to enable developer console (requires camel-console on classpath). The developer console is only for assisting during development. This is NOT for production usage.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.main.dumpRoutes", "description": "If dumping is enabled then Camel will during startup dump all loaded routes (incl rests and route templates) represented as XML\/YAML DSL into the log. This is intended for trouble shooting or to assist during development. Sensitive information that may be configured in the route endpoints could potentially be included in the dump output and is therefore not recommended being used for production usage. This requires to have camel-xml [...]
     { "name": "camel.main.dumpRoutesDirectory", "description": "Whether to save route dumps to files in the given directory. The name of the files are based on original loaded resource, or an autogenerated name.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" },
-    { "name": "camel.main.dumpRoutesInclude", "description": "Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates. Multiple values can be separated by comma. Default is routes.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "routes" },
+    { "name": "camel.main.dumpRoutesInclude", "description": "Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans. Multiple values can be separated by comma. Default is routes.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "routes" },
     { "name": "camel.main.dumpRoutesLog", "description": "Whether to log route dumps to Logger", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.main.dumpRoutesResolvePlaceholders", "description": "Whether to resolve property placeholders in the dumped output. Default is true.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.main.dumpRoutesUriAsParameters", "description": "When dumping routes to YAML format, then this option controls whether endpoint URIs should be expanded into a key\/value parameters.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java
index 04cba2962f6..54ad32226d5 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java
@@ -40,8 +40,8 @@ public interface DumpRoutesStrategy extends StaticService {
     /**
      * Controls what to include in output.
      *
-     * Possible values: all, routes, rests, routeConfigurations, routeTemplates. Multiple values can be separated by
-     * comma. Default is routes.
+     * Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans. Multiple values can be separated
+     * by comma. Default is routes.
      */
     void setInclude(String include);
 
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ModelToXMLDumper.java b/core/camel-api/src/main/java/org/apache/camel/spi/ModelToXMLDumper.java
index cbd9b885316..94f5ec8a764 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ModelToXMLDumper.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ModelToXMLDumper.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.spi;
 
+import java.util.List;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.NamedNode;
 
@@ -52,4 +54,14 @@ public interface ModelToXMLDumper {
             CamelContext context, NamedNode definition, boolean resolvePlaceholders)
             throws Exception;
 
+    /**
+     * Dumps the beans as XML
+     *
+     * @param  context   the CamelContext
+     * @param  beans     list of beans (RegistryBeanDefinition)
+     * @return           the output in XML (is formatted)
+     * @throws Exception is throw if error marshalling to XML
+     */
+    String dumpBeansAsXml(CamelContext context, List<Object> beans) throws Exception;
+
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ModelToYAMLDumper.java b/core/camel-api/src/main/java/org/apache/camel/spi/ModelToYAMLDumper.java
index 6c76313bd9d..6f4ae0f1423 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ModelToYAMLDumper.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ModelToYAMLDumper.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.spi;
 
+import java.util.List;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.NamedNode;
 
@@ -54,4 +56,14 @@ public interface ModelToYAMLDumper {
             boolean resolvePlaceholders, boolean uriAsParameters)
             throws Exception;
 
+    /**
+     * Dumps the beans as YAML
+     *
+     * @param  context   the CamelContext
+     * @param  beans     list of beans (RegistryBeanDefinition)
+     * @return           the output in YAML (is formatted)
+     * @throws Exception is throw if error marshalling to YAML
+     */
+    String dumpBeansAsYaml(CamelContext context, List<Object> beans) throws Exception;
+
 }
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
index 582c9848026..6752f19be27 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
@@ -56,6 +56,7 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteDefinitionHelper;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.TemplatedRouteDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
 import org.apache.camel.model.language.ExpressionDefinition;
 import org.apache.camel.model.rest.RestDefinition;
@@ -517,6 +518,16 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
         return model.getRouteFilter();
     }
 
+    @Override
+    public void addRegistryBean(RegistryBeanDefinition bean) {
+        model.addRegistryBean(bean);
+    }
+
+    @Override
+    public List<RegistryBeanDefinition> getRegistryBeans() {
+        return model.getRegistryBeans();
+    }
+
     @Override
     public ModelReifierFactory getModelReifierFactory() {
         return model.getModelReifierFactory();
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
index efbf597efe0..4e550933bf3 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
@@ -19,8 +19,12 @@ package org.apache.camel.impl;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.camel.CamelContext;
@@ -33,6 +37,7 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.rest.RestsDefinition;
 import org.apache.camel.spi.DumpRoutesStrategy;
@@ -57,6 +62,9 @@ import static org.apache.camel.support.LoggerHelper.stripSourceLocationLineNumbe
 @ServiceFactory("default-" + DumpRoutesStrategy.FACTORY)
 public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRoutesStrategy, CamelContextAware {
 
+    // TODO: XML to disk with beans should use <camel>
+    // TODO: inlined routes in <camel> should be array of <route> and not have <routes>
+
     private static final Logger LOG = LoggerFactory.getLogger(DefaultDumpRoutesStrategy.class);
     private static final String DIVIDER = "--------------------------------------------------------------------------------";
 
@@ -132,6 +140,36 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
         final ModelToYAMLDumper dumper = PluginHelper.getModelToYAMLDumper(camelContext);
         final Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
         final DummyResource dummy = new DummyResource(null, null);
+        final Set<String> files = new HashSet<>();
+
+        if (include.contains("*") || include.contains("all") || include.contains("beans")) {
+            int size = model.getRegistryBeans().size();
+            if (size > 0) {
+                Map<Resource, List<RegistryBeanDefinition>> groups = new LinkedHashMap<>();
+                for (RegistryBeanDefinition bean : model.getRegistryBeans()) {
+                    Resource res = bean.getResource();
+                    if (res == null) {
+                        res = dummy;
+                    }
+                    List<RegistryBeanDefinition> beans = groups.computeIfAbsent(res, resource -> new ArrayList<>());
+                    beans.add(bean);
+                }
+                StringBuilder sbLog = new StringBuilder();
+                for (Map.Entry<Resource, List<RegistryBeanDefinition>> entry : groups.entrySet()) {
+                    List<RegistryBeanDefinition> beans = entry.getValue();
+                    Resource resource = entry.getKey();
+
+                    StringBuilder sbLocal = new StringBuilder();
+                    doDumpYamlBeans(camelContext, beans, resource == dummy ? null : resource, dumper, "beans", sbLocal, sbLog);
+                    // dump each resource into its own file
+                    doDumpToDirectory(resource, sbLocal, "beans", "yaml", files);
+                }
+                if (!sbLog.isEmpty() && log) {
+                    LOG.info("Dumping {} beans as YAML", size);
+                    LOG.info("{}", sbLog);
+                }
+            }
+        }
 
         if (include.contains("*") || include.contains("all") || include.contains("routes")) {
             int size = model.getRouteDefinitions().size();
@@ -153,7 +191,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     StringBuilder sbLocal = new StringBuilder();
                     doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "routes", sbLocal, sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "routes", "yaml");
+                    doDumpToDirectory(resource, sbLocal, "routes", "yaml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} routes as YAML", size);
@@ -185,7 +223,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "route-configurations", sbLocal,
                             sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "route-configurations", "yaml");
+                    doDumpToDirectory(resource, sbLocal, "route-configurations", "yaml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} route-configurations as YAML", size);
@@ -214,7 +252,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     StringBuilder sbLocal = new StringBuilder();
                     doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "rests", sbLocal, sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "rests", "yaml");
+                    doDumpToDirectory(resource, sbLocal, "rests", "yaml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} rests as YAML", size);
@@ -245,7 +283,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     doDumpYaml(camelContext, def, resource == dummy ? null : resource, dumper, "route-templates", sbLocal,
                             sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "route-templates", "yaml");
+                    doDumpToDirectory(resource, sbLocal, "route-templates", "yaml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} route-templates as YAML", size);
@@ -267,10 +305,52 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
         }
     }
 
+    protected void doDumpYamlBeans(
+            CamelContext camelContext, List beans, Resource resource,
+            ModelToYAMLDumper dumper, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
+        try {
+            String dump = dumper.dumpBeansAsYaml(camelContext, beans);
+            sbLocal.append(dump);
+            appendLogDump(resource, dump, sbLog);
+        } catch (Exception e) {
+            LOG.warn("Error dumping {}} to YAML due to {}. This exception is ignored.", kind, e.getMessage(), e);
+        }
+    }
+
     protected void doDumpRoutesAsXml(CamelContext camelContext) {
         final ModelToXMLDumper dumper = PluginHelper.getModelToXMLDumper(camelContext);
         final Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
         final DummyResource dummy = new DummyResource(null, null);
+        final Set<String> files = new HashSet<>();
+
+        if (include.contains("*") || include.contains("all") || include.contains("beans")) {
+            int size = model.getRegistryBeans().size();
+            if (size > 0) {
+                Map<Resource, List<RegistryBeanDefinition>> groups = new LinkedHashMap<>();
+                for (RegistryBeanDefinition bean : model.getRegistryBeans()) {
+                    Resource res = bean.getResource();
+                    if (res == null) {
+                        res = dummy;
+                    }
+                    List<RegistryBeanDefinition> beans = groups.computeIfAbsent(res, resource -> new ArrayList<>());
+                    beans.add(bean);
+                }
+                StringBuilder sbLog = new StringBuilder();
+                for (Map.Entry<Resource, List<RegistryBeanDefinition>> entry : groups.entrySet()) {
+                    List<RegistryBeanDefinition> beans = entry.getValue();
+                    Resource resource = entry.getKey();
+
+                    StringBuilder sbLocal = new StringBuilder();
+                    doDumpXmlBeans(camelContext, beans, resource == dummy ? null : resource, dumper, "beans", sbLocal, sbLog);
+                    // dump each resource into its own file
+                    doDumpToDirectory(resource, sbLocal, "beans", "xml", files);
+                }
+                if (!sbLog.isEmpty() && log) {
+                    LOG.info("Dumping {} beans as XML", size);
+                    LOG.info("{}", sbLog);
+                }
+            }
+        }
 
         if (include.contains("*") || include.contains("all") || include.contains("routes")) {
             int size = model.getRouteDefinitions().size();
@@ -293,7 +373,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "route", "routes", sbLocal,
                             sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "routes", "xml");
+                    doDumpToDirectory(resource, sbLocal, "routes", "xml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} routes as XML", size);
@@ -325,7 +405,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "rest", "route-configurations",
                             sbLocal, sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "route-configurations", "xml");
+                    doDumpToDirectory(resource, sbLocal, "route-configurations", "xml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} route-configurations as XML", size);
@@ -354,7 +434,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     StringBuilder sbLocal = new StringBuilder();
                     doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "rest", "rests", sbLocal, sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "rests", "xml");
+                    doDumpToDirectory(resource, sbLocal, "rests", "xml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} rests as XML", size);
@@ -385,7 +465,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
                     doDumpXml(camelContext, def, resource == dummy ? null : resource, dumper, "routeTemplate",
                             "route-templates", sbLocal, sbLog);
                     // dump each resource into its own file
-                    doDumpToDirectory(resource, sbLocal, "route-templates", "xml");
+                    doDumpToDirectory(resource, sbLocal, "route-templates", "xml", files);
                 }
                 if (!sbLog.isEmpty() && log) {
                     LOG.info("Dumping {} route-templates as XML", size);
@@ -395,6 +475,18 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
         }
     }
 
+    protected void doDumpXmlBeans(
+            CamelContext camelContext, List beans, Resource resource,
+            ModelToXMLDumper dumper, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
+        try {
+            String dump = dumper.dumpBeansAsXml(camelContext, beans);
+            sbLocal.append(dump);
+            appendLogDump(resource, dump, sbLog);
+        } catch (Exception e) {
+            LOG.warn("Error dumping {}} to XML due to {}. This exception is ignored.", kind, e.getMessage(), e);
+        }
+    }
+
     protected void doDumpXml(
             CamelContext camelContext, NamedNode def, Resource resource,
             ModelToXMLDumper dumper, String replace, String kind, StringBuilder sbLocal, StringBuilder sbLog) {
@@ -411,7 +503,7 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
         }
     }
 
-    protected void doDumpToDirectory(Resource resource, StringBuilder sbLocal, String kind, String ext) {
+    protected void doDumpToDirectory(Resource resource, StringBuilder sbLocal, String kind, String ext, Set<String> files) {
         if (directory != null && !sbLocal.isEmpty()) {
             // make sure directory exists
             File dir = new File(directory);
@@ -425,10 +517,19 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
             if (name.contains(":")) {
                 name = StringHelper.after(name, ":");
             }
+
             name = FileUtil.onlyName(name) + "." + ext;
+            boolean newFile = files.isEmpty() || !files.contains(name);
             File target = new File(directory, name);
             try {
-                IOHelper.writeText(sbLocal.toString(), target);
+                if (newFile) {
+                    // write as new file (override old file if exists)
+                    IOHelper.writeText(sbLocal.toString(), target);
+                } else {
+                    // append to existing file
+                    IOHelper.appendText(sbLocal.toString(), target);
+                }
+                files.add(name);
                 LOG.info("Dumped {} to file: {}", kind, target);
             } catch (IOException e) {
                 throw new RuntimeException("Error dumping " + kind + " to file: " + target, e);
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
index dbbf80ce1f6..729bb3ce5ff 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
@@ -56,6 +56,7 @@ import org.apache.camel.model.TemplatedRouteBeanDefinition;
 import org.apache.camel.model.TemplatedRouteDefinition;
 import org.apache.camel.model.TemplatedRouteParameterDefinition;
 import org.apache.camel.model.ToDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.transformer.TransformerDefinition;
@@ -91,6 +92,8 @@ public class DefaultModel implements Model {
     private Map<String, DataFormatDefinition> dataFormats = new HashMap<>();
     private List<TransformerDefinition> transformers = new ArrayList<>();
     private List<ValidatorDefinition> validators = new ArrayList<>();
+    // XML and YAML DSL allows to declare beans in the DSL
+    private List<RegistryBeanDefinition> beans = new ArrayList<>();
     private final Map<String, ServiceCallConfigurationDefinition> serviceCallConfigurations = new ConcurrentHashMap<>();
     private final Map<String, Resilience4jConfigurationDefinition> resilience4jConfigurations = new ConcurrentHashMap<>();
     private final Map<String, FaultToleranceConfigurationDefinition> faultToleranceConfigurations = new ConcurrentHashMap<>();
@@ -917,6 +920,18 @@ public class DefaultModel implements Model {
         this.modelReifierFactory = modelReifierFactory;
     }
 
+    @Override
+    public void addRegistryBean(RegistryBeanDefinition bean) {
+        // remove exiting bean with same name to update
+        beans.removeIf(b -> bean.getName().equals(b.getName()));
+        beans.add(bean);
+    }
+
+    @Override
+    public List<RegistryBeanDefinition> getRegistryBeans() {
+        return beans;
+    }
+
     /**
      * Should we start newly added routes?
      */
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
index 3c99c0c385a..048ccc05416 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
@@ -46,6 +46,7 @@ import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.model.TemplatedRouteDefinition;
 import org.apache.camel.model.TemplatedRoutesDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.errorhandler.RefErrorHandlerDefinition;
 import org.apache.camel.model.rest.RestConfigurationDefinition;
 import org.apache.camel.model.rest.RestDefinition;
@@ -75,6 +76,8 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild
     private final List<RouteBuilderLifecycleStrategy> lifecycleInterceptors = new ArrayList<>();
     private final List<TransformerBuilder> transformerBuilders = new ArrayList<>();
     private final List<ValidatorBuilder> validatorBuilders = new ArrayList<>();
+    // XML and YAML DSL allows to define custom beans which we need to capture
+    private final List<RegistryBeanDefinition> beans = new ArrayList<>();
 
     private RestsDefinition restCollection = new RestsDefinition();
     private RestConfigurationDefinition restConfiguration;
@@ -603,6 +606,7 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild
         configureRests(context);
 
         // but populate rests before routes, as we want to turn rests into routes
+        populateBeans();
         populateRests();
         populateTransformers();
         populateValidators();
@@ -633,6 +637,7 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild
         configureRests(context);
 
         // but populate rests before routes, as we want to turn rests into routes
+        populateBeans();
         populateRests();
         populateTransformers();
         populateValidators();
@@ -731,6 +736,9 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild
             getRouteCollection().setResource(getResource());
             getRestCollection().setResource(getResource());
             getRouteTemplateCollection().setResource(getResource());
+            for (RegistryBeanDefinition def : beans) {
+                def.setResource(getResource());
+            }
 
             for (RouteDefinition route : getRouteCollection().getRoutes()) {
                 // ensure the route is prepared after configure method is complete
@@ -855,6 +863,20 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild
         }
     }
 
+    protected void populateBeans() {
+        CamelContext camelContext = notNullCamelContext();
+
+        Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
+        for (RegistryBeanDefinition def : beans) {
+            // add to model
+            model.addRegistryBean(def);
+        }
+    }
+
+    public List<RegistryBeanDefinition> getBeans() {
+        return beans;
+    }
+
     public RestsDefinition getRestCollection() {
         return restCollection;
     }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java b/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
index 7008c586159..7efa1b59452 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
@@ -23,6 +23,7 @@ import java.util.function.Function;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.RouteTemplateContext;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.transformer.TransformerDefinition;
@@ -489,4 +490,14 @@ public interface Model {
      */
     void setModelReifierFactory(ModelReifierFactory modelReifierFactory);
 
+    /**
+     * Adds the custom bean
+     */
+    void addRegistryBean(RegistryBeanDefinition bean);
+
+    /**
+     * Gets the custom beans
+     */
+    List<RegistryBeanDefinition> getRegistryBeans();
+
 }
diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 302701cffaf..6dd2a3c22de 100644
--- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -40,7 +40,7 @@
     { "name": "camel.main.devConsoleEnabled", "description": "Whether to enable developer console (requires camel-console on classpath). The developer console is only for assisting during development. This is NOT for production usage.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.main.dumpRoutes", "description": "If dumping is enabled then Camel will during startup dump all loaded routes (incl rests and route templates) represented as XML\/YAML DSL into the log. This is intended for trouble shooting or to assist during development. Sensitive information that may be configured in the route endpoints could potentially be included in the dump output and is therefore not recommended being used for production usage. This requires to have camel-xml [...]
     { "name": "camel.main.dumpRoutesDirectory", "description": "Whether to save route dumps to files in the given directory. The name of the files are based on original loaded resource, or an autogenerated name.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String" },
-    { "name": "camel.main.dumpRoutesInclude", "description": "Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates. Multiple values can be separated by comma. Default is routes.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "routes" },
+    { "name": "camel.main.dumpRoutesInclude", "description": "Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans. Multiple values can be separated by comma. Default is routes.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "routes" },
     { "name": "camel.main.dumpRoutesLog", "description": "Whether to log route dumps to Logger", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.main.dumpRoutesResolvePlaceholders", "description": "Whether to resolve property placeholders in the dumped output. Default is true.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
     { "name": "camel.main.dumpRoutesUriAsParameters", "description": "When dumping routes to YAML format, then this option controls whether endpoint URIs should be expanded into a key\/value parameters.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc
index 190214d6498..b0c27393219 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -51,7 +51,7 @@ The camel.main supports 125 options, which are listed below.
 | *camel.main.devConsoleEnabled* | Whether to enable developer console (requires camel-console on classpath). The developer console is only for assisting during development. This is NOT for production usage. | false | boolean
 | *camel.main.dumpRoutes* | If dumping is enabled then Camel will during startup dump all loaded routes (incl rests and route templates) represented as XML/YAML DSL into the log. This is intended for trouble shooting or to assist during development. Sensitive information that may be configured in the route endpoints could potentially be included in the dump output and is therefore not recommended being used for production usage. This requires to have camel-xml-io/camel-yaml-io on the cla [...]
 | *camel.main.dumpRoutesDirectory* | Whether to save route dumps to files in the given directory. The name of the files are based on original loaded resource, or an autogenerated name. |  | String
-| *camel.main.dumpRoutesInclude* | Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates. Multiple values can be separated by comma. Default is routes. | routes | String
+| *camel.main.dumpRoutesInclude* | Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans. Multiple values can be separated by comma. Default is routes. | routes | String
 | *camel.main.dumpRoutesLog* | Whether to log route dumps to Logger | true | boolean
 | *camel.main.dumpRoutesResolve{zwsp}Placeholders* | Whether to resolve property placeholders in the dumped output. Default is true. | true | boolean
 | *camel.main.dumpRoutesUriAs{zwsp}Parameters* | When dumping routes to YAML format, then this option controls whether endpoint URIs should be expanded into a key/value parameters. | false | boolean
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index e962f1a8825..29a092a3935 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -1398,8 +1398,8 @@ public abstract class DefaultConfigurationProperties<T> {
     /**
      * Controls what to include in output for route dumping.
      *
-     * Possible values: all, routes, rests, routeConfigurations, routeTemplates. Multiple values can be separated by
-     * comma. Default is routes.
+     * Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans. Multiple values can be separated
+     * by comma. Default is routes.
      */
     public void setDumpRoutesInclude(String dumpRoutesInclude) {
         this.dumpRoutesInclude = dumpRoutesInclude;
@@ -2646,8 +2646,8 @@ public abstract class DefaultConfigurationProperties<T> {
     /**
      * Controls what to include in output for route dumping.
      *
-     * Possible values: all, routes, rests, routeConfigurations, routeTemplates. Multiple values can be separated by
-     * comma. Default is routes.
+     * Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans. Multiple values can be separated
+     * by comma. Default is routes.
      */
     public T withDumpRoutesInclude(String dumpRoutesInclude) {
         this.dumpRoutesInclude = dumpRoutesInclude;
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
index d0c76c2e08b..e18d2168e46 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java
@@ -479,26 +479,24 @@ public final class IOHelper {
      * Appends the text to the file.
      */
     public static void appendText(String text, File file) throws IOException {
-        if (!file.exists()) {
-            String path = FileUtil.onlyPath(file.getPath());
-            if (path != null) {
-                new File(path).mkdirs();
-            }
-        }
-        writeText(text, new FileOutputStream(file, true));
+        doWriteText(text, file, true);
     }
 
     /**
      * Writes the text to the file.
      */
     public static void writeText(String text, File file) throws IOException {
+        doWriteText(text, file, false);
+    }
+
+    private static void doWriteText(String text, File file, boolean append) throws IOException {
         if (!file.exists()) {
             String path = FileUtil.onlyPath(file.getPath());
             if (path != null) {
                 new File(path).mkdirs();
             }
         }
-        writeText(text, new FileOutputStream(file, false));
+        writeText(text, new FileOutputStream(file, append));
     }
 
     /**
diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
index 5e379f9836a..db76fe5ca41 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
@@ -18,14 +18,17 @@ package org.apache.camel.xml;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.function.Consumer;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.Expression;
 import org.apache.camel.NamedNode;
 import org.apache.camel.model.ExpressionNode;
@@ -37,6 +40,7 @@ import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.model.SendDefinition;
 import org.apache.camel.model.ToDynamicDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.language.ExpressionDefinition;
 import org.apache.camel.spi.ModelToXMLDumper;
 import org.apache.camel.spi.NamespaceAware;
@@ -173,6 +177,28 @@ public class LwModelToXMLDumper implements ModelToXMLDumper {
         return buffer.toString();
     }
 
+    @Override
+    public String dumpBeansAsXml(CamelContext context, List<Object> beans) throws Exception {
+        StringWriter buffer = new StringWriter();
+        BeanModelWriter writer = new BeanModelWriter(buffer);
+
+        List<RegistryBeanDefinition> list = new ArrayList<>();
+        for (Object bean : beans) {
+            if (bean instanceof RegistryBeanDefinition rb) {
+                list.add(rb);
+            }
+        }
+        writer.setCamelContext(context);
+        writer.start();
+        try {
+            writer.writeBeans(list);
+        } finally {
+            writer.stop();
+        }
+
+        return buffer.toString();
+    }
+
     /**
      * Extract all XML namespaces from the expressions in the route
      *
@@ -258,4 +284,55 @@ public class LwModelToXMLDumper implements ModelToXMLDumper {
         return na;
     }
 
+    private static class BeanModelWriter implements CamelContextAware {
+
+        private final StringWriter buffer;
+        private CamelContext camelContext;
+
+        public BeanModelWriter(StringWriter buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public CamelContext getCamelContext() {
+            return camelContext;
+        }
+
+        @Override
+        public void setCamelContext(CamelContext camelContext) {
+            this.camelContext = camelContext;
+        }
+
+        public void start() {
+            // noop
+        }
+
+        public void stop() {
+            // noop
+        }
+
+        public void writeBeans(List<RegistryBeanDefinition> beans) {
+            if (beans.isEmpty()) {
+                return;
+            }
+            for (RegistryBeanDefinition b : beans) {
+                doWriteRegistryBeanDefinition(b);
+            }
+        }
+
+        private void doWriteRegistryBeanDefinition(RegistryBeanDefinition b) {
+            buffer.write(String.format("    <bean name=\"%s\" type=\"%s\">%n", b.getName(), b.getType()));
+            if (b.getProperties() != null && !b.getProperties().isEmpty()) {
+                buffer.write(String.format("        <properties>%n"));
+                for (Map.Entry<String, Object> entry : b.getProperties().entrySet()) {
+                    String key = entry.getKey();
+                    Object value = entry.getValue();
+                    buffer.write(String.format("            <property key=\"%s\" value=\"%s\"/>%n", key, value));
+                }
+                buffer.write(String.format("        </properties>%n"));
+            }
+            buffer.write(String.format("    </bean>%n"));
+        }
+    }
+
 }
diff --git a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
index 3bf298e862e..266162386b4 100644
--- a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
+++ b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbModelToXMLDumper.java
@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -40,6 +41,7 @@ import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.NamedNode;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.converter.jaxp.XmlConverter;
@@ -47,6 +49,7 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.spi.ModelToXMLDumper;
 import org.apache.camel.spi.PropertiesComponent;
 import org.apache.camel.spi.annotations.JdkService;
@@ -216,6 +219,28 @@ public class JaxbModelToXMLDumper implements ModelToXMLDumper {
         return xml;
     }
 
+    @Override
+    public String dumpBeansAsXml(CamelContext context, List<Object> beans) throws Exception {
+        StringWriter buffer = new StringWriter();
+        BeanModelWriter writer = new BeanModelWriter(buffer);
+
+        List<RegistryBeanDefinition> list = new ArrayList<>();
+        for (Object bean : beans) {
+            if (bean instanceof RegistryBeanDefinition rb) {
+                list.add(rb);
+            }
+        }
+        writer.setCamelContext(context);
+        writer.start();
+        try {
+            writer.writeBeans(list);
+        } finally {
+            writer.stop();
+        }
+
+        return buffer.toString();
+    }
+
     private static void sanitizeXml(Node node) {
         // we want to remove all customId="false" attributes as they are noisy
         if (node.hasAttributes()) {
@@ -260,4 +285,55 @@ public class JaxbModelToXMLDumper implements ModelToXMLDumper {
         }
     }
 
+    private static class BeanModelWriter implements CamelContextAware {
+
+        private final StringWriter buffer;
+        private CamelContext camelContext;
+
+        public BeanModelWriter(StringWriter buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public CamelContext getCamelContext() {
+            return camelContext;
+        }
+
+        @Override
+        public void setCamelContext(CamelContext camelContext) {
+            this.camelContext = camelContext;
+        }
+
+        public void start() {
+            // noop
+        }
+
+        public void stop() {
+            // noop
+        }
+
+        public void writeBeans(List<RegistryBeanDefinition> beans) {
+            if (beans.isEmpty()) {
+                return;
+            }
+            for (RegistryBeanDefinition b : beans) {
+                doWriteRegistryBeanDefinition(b);
+            }
+        }
+
+        private void doWriteRegistryBeanDefinition(RegistryBeanDefinition b) {
+            buffer.write(String.format("    <bean name=\"%s\" type=\"%s\">%n", b.getName(), b.getType()));
+            if (b.getProperties() != null && !b.getProperties().isEmpty()) {
+                buffer.write(String.format("        <properties>%n"));
+                for (Map.Entry<String, Object> entry : b.getProperties().entrySet()) {
+                    String key = entry.getKey();
+                    Object value = entry.getValue();
+                    buffer.write(String.format("            <property key=\"%s\" value=\"%s\"/>%n", key, value));
+                }
+                buffer.write(String.format("        </properties>%n"));
+            }
+            buffer.write(String.format("    </bean>%n"));
+        }
+    }
+
 }
diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
index de16de01052..72e52a6f93b 100644
--- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
+++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
@@ -18,14 +18,17 @@ package org.apache.camel.yaml;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.function.Consumer;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.Expression;
 import org.apache.camel.NamedNode;
 import org.apache.camel.model.ExpressionNode;
@@ -37,6 +40,7 @@ import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.model.SendDefinition;
 import org.apache.camel.model.ToDynamicDefinition;
+import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.model.language.ExpressionDefinition;
 import org.apache.camel.spi.ModelToYAMLDumper;
 import org.apache.camel.spi.NamespaceAware;
@@ -160,6 +164,28 @@ public class LwModelToYAMLDumper implements ModelToYAMLDumper {
         return buffer.toString();
     }
 
+    @Override
+    public String dumpBeansAsYaml(CamelContext context, List<Object> beans) throws Exception {
+        StringWriter buffer = new StringWriter();
+        BeanModelWriter writer = new BeanModelWriter(buffer);
+
+        List<RegistryBeanDefinition> list = new ArrayList<>();
+        for (Object bean : beans) {
+            if (bean instanceof RegistryBeanDefinition rb) {
+                list.add(rb);
+            }
+        }
+        writer.setCamelContext(context);
+        writer.start();
+        try {
+            writer.writeBeans(list);
+        } finally {
+            writer.stop();
+        }
+
+        return buffer.toString();
+    }
+
     /**
      * Extract all XML namespaces from the expressions in the route
      *
@@ -245,4 +271,59 @@ public class LwModelToYAMLDumper implements ModelToYAMLDumper {
         return na;
     }
 
+    private static class BeanModelWriter implements CamelContextAware {
+
+        private final StringWriter buffer;
+        private CamelContext camelContext;
+
+        public BeanModelWriter(StringWriter buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public CamelContext getCamelContext() {
+            return camelContext;
+        }
+
+        @Override
+        public void setCamelContext(CamelContext camelContext) {
+            this.camelContext = camelContext;
+        }
+
+        public void start() {
+            // noop
+        }
+
+        public void stop() {
+            // noop
+        }
+
+        public void writeBeans(List<RegistryBeanDefinition> beans) {
+            if (beans.isEmpty()) {
+                return;
+            }
+            buffer.write("- beans:\n");
+            for (RegistryBeanDefinition b : beans) {
+                doWriteRegistryBeanDefinition(b);
+            }
+        }
+
+        private void doWriteRegistryBeanDefinition(RegistryBeanDefinition b) {
+            buffer.write(String.format("    - name: %s%n", b.getName()));
+            buffer.write(String.format("      type: %s%n", b.getType()));
+            if (b.getProperties() != null && !b.getProperties().isEmpty()) {
+                buffer.write(String.format("      properties:%n"));
+                for (Map.Entry<String, Object> entry : b.getProperties().entrySet()) {
+                    String key = entry.getKey();
+                    Object value = entry.getValue();
+                    if (value instanceof String) {
+                        buffer.write(String.format("        %s: \"%s\"%n", key, value));
+                    } else {
+                        buffer.write(String.format("        %s: %s%n", key, value));
+                    }
+                }
+            }
+        }
+    }
+
 }
diff --git a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
index 91cd9354341..2cfb1a2341c 100644
--- a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
+++ b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
@@ -32,6 +32,7 @@ import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.builder.RouteConfigurationBuilder;
 import org.apache.camel.dsl.support.RouteBuilderLoaderSupport;
+import org.apache.camel.model.Model;
 import org.apache.camel.model.RouteConfigurationDefinition;
 import org.apache.camel.model.RouteConfigurationsDefinition;
 import org.apache.camel.model.RouteDefinition;
@@ -320,6 +321,11 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
                 }
                 getCamelContext().getRegistry().unbind(name);
                 getCamelContext().getRegistry().bind(name, target);
+
+                // register bean in model
+                Model model = getCamelContext().getCamelContextExtension().getContextPlugin(Model.class);
+                model.addRegistryBean(def);
+
             } catch (Exception e) {
                 if (delayIfFailed) {
                     delayedRegistrations.add(def);
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
index 5cd0301f3eb..4be1c650219 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
@@ -24,6 +24,7 @@ import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
 import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
 import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
 import org.apache.camel.dsl.yaml.common.YamlSupport;
+import org.apache.camel.model.Model;
 import org.apache.camel.model.app.RegistryBeanDefinition;
 import org.apache.camel.spi.CamelContextCustomizer;
 import org.apache.camel.spi.annotations.YamlIn;
@@ -74,6 +75,11 @@ public class BeansDeserializer extends YamlDeserializerSupport implements Constr
                         camelContext.getRegistry().bind(
                                 name,
                                 newInstance(bean, camelContext));
+
+                        // register bean in model
+                        Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
+                        model.addRegistryBean(bean);
+
                     } catch (Exception e) {
                         throw new RuntimeException(e);
                     }