You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2020/09/04 15:03:59 UTC

[camel] 01/03: CAMEL-14672: Invoke customizers as part of services initialization

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

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

commit 493e93d44ba82d201a3a75e095b43a547b86c271
Author: lburgazzoli <lb...@gmail.com>
AuthorDate: Tue Sep 1 19:40:57 2020 +0200

    CAMEL-14672: Invoke customizers as part of services initialization
---
 .../org/apache/camel/spi/ComponentCustomizer.java  | 209 +++++-
 .../org/apache/camel/spi/DataFormatCustomizer.java | 213 +++++-
 .../org/apache/camel/spi/DataFormatResolver.java   |  10 -
 .../org/apache/camel/spi/LanguageCustomizer.java   | 213 +++++-
 .../org/apache/camel/spi/LifecycleStrategy.java    |  18 +
 .../camel/impl/engine/AbstractCamelContext.java    |  76 +-
 .../impl/engine/CustomizersLifecycleStrategy.java  |  87 +++
 .../impl/engine/DefaultDataFormatResolver.java     |  14 -
 .../camel/impl/engine/DefaultLanguageResolver.java |   7 -
 .../org/apache/camel/support/CustomizersTest.java  | 457 ++++++++++++
 .../org/apache/camel/main/MainCustomizerTest.java  |  99 +++
 .../apache/camel/support/CustomizersSupport.java   | 129 ++++
 .../camel/support/PropertyBindingSupport.java      | 787 ++++++++++-----------
 .../camel/support/PropertyConfigurerHelper.java    |  52 +-
 .../org/apache/camel/util/CollectionHelper.java    |   1 +
 15 files changed, 1901 insertions(+), 471 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java b/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java
index 59dc6c7..2a2cf28 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java
@@ -16,14 +16,217 @@
  */
 package org.apache.camel.spi;
 
+import java.util.function.BiPredicate;
+
 import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingBiConsumer;
+import org.apache.camel.util.function.ThrowingConsumer;
 
+/**
+ * To apply custom configurations to {@link Component} instances.
+ */
 @FunctionalInterface
-public interface ComponentCustomizer<T extends Component> {
+public interface ComponentCustomizer extends Ordered {
+    /**
+     * Create a generic {@link Builder}.
+     *
+     * @return the {@link Builder}
+     */
+    static Builder<Component> builder() {
+        return builder(Component.class);
+    }
+
+    /**
+     * Create a typed {@link Builder} that can process a concrete component type instance.
+     *
+     * @param  type the concrete type of the {@link Component}
+     * @return      the {@link Builder}
+     */
+    static <T extends Component> Builder<T> builder(Class<T> type) {
+        return new Builder<>(type);
+    }
+
+    /**
+     * Create a {@link ComponentCustomizer} that can process a concrete component type instance.
+     *
+     * @param  type     the concrete type of the {@link Component}
+     * @param  consumer the {@link Component} configuration logic
+     * @return          the {@link ComponentCustomizer}
+     */
+    static <T extends Component> ComponentCustomizer forType(Class<T> type, ThrowingConsumer<T, Exception> consumer) {
+        return builder(type).build(consumer);
+    }
+
     /**
      * Customize the specified {@link Component}.
      *
-     * @param component the component to customize
+     * @param name   the unique name of the component
+     * @param target the component to configure
+     */
+    void configure(String name, Component target);
+
+    /**
+     * Checks whether this customizer should be applied to the given {@link Component}.
+     *
+     * @param  name   the unique name of the component
+     * @param  target the component to configure
+     * @return        <tt>true</tt> if the customizer should be applied
+     */
+    default boolean isEnabled(String name, Component target) {
+        return true;
+    }
+
+    @Override
+    default int getOrder() {
+        return 0;
+    }
+
+    // ***************************************
+    //
+    // Filter
+    //
+    // ***************************************
+
+    /**
+     * Used as additional filer mechanism to control if customizers need to be applied or not. Each of this filter is
+     * applied before any of the discovered {@link ComponentCustomizer} is invoked.
+     * </p>
+     * This interface is useful to implement enable/disable logic on a group of customizers.
+     */
+    @FunctionalInterface
+    interface Policy extends BiPredicate<String, Component> {
+        /**
+         * A simple deny-all policy.
+         *
+         * @return false
+         */
+        static Policy none() {
+            return new Policy() {
+                @Override
+                public boolean test(String s, Component target) {
+                    return false;
+                }
+            };
+        }
+
+        /**
+         * A simple allow-all policy.
+         *
+         * @return true
+         */
+        static Policy any() {
+            return new Policy() {
+                @Override
+                public boolean test(String s, Component target) {
+                    return true;
+                }
+            };
+        }
+    }
+
+    // ***************************************
+    //
+    // Builders
+    //
+    // ***************************************
+
+    /**
+     * A fluent builder to create a {@link ComponentCustomizer} instance.
+     *
+     * @param <T> the concrete type of the {@link Component}.
      */
-    void customize(T component);
+    class Builder<T extends Component> {
+        private final Class<T> type;
+        private BiPredicate<String, Component> condition;
+        private int order;
+
+        public Builder(Class<T> type) {
+            this.type = type;
+        }
+
+        public Builder<T> withOrder(int order) {
+            this.order = order;
+
+            return this;
+        }
+
+        public Builder<T> withCondition(BiPredicate<String, Component> condition) {
+            this.condition = condition;
+
+            return this;
+        }
+
+        public ComponentCustomizer build(ThrowingConsumer<T, Exception> consumer) {
+            return build(new ThrowingBiConsumer<String, T, Exception>() {
+                @Override
+                public void accept(String name, T target) throws Exception {
+                    consumer.accept(target);
+                }
+            });
+        }
+
+        public ComponentCustomizer build(ThrowingBiConsumer<String, T, Exception> consumer) {
+            final int order = this.order;
+            final BiPredicate<String, Component> condition = condition();
+
+            return new ComponentCustomizer() {
+                @SuppressWarnings("unchecked")
+                @Override
+                public void configure(String name, Component target) {
+                    ObjectHelper.notNull(name, "component name");
+                    ObjectHelper.notNull(target, "component instance");
+
+                    try {
+                        consumer.accept(name, (T) target);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                @Override
+                public boolean isEnabled(String name, Component target) {
+                    ObjectHelper.notNull(name, "component name");
+                    ObjectHelper.notNull(target, "component instance");
+
+                    return condition.test(name, target);
+                }
+
+                @Override
+                public int getOrder() {
+                    return order;
+                }
+            };
+        }
+
+        private BiPredicate<String, Component> condition() {
+            if (type.equals(Component.class)) {
+                return this.condition != null
+                        ? this.condition
+                        : new BiPredicate<String, Component>() {
+                            @Override
+                            public boolean test(String s, Component language) {
+                                return true;
+                            }
+                        };
+            }
+
+            if (condition == null) {
+                return new BiPredicate<String, Component>() {
+                    @Override
+                    public boolean test(String name, Component target) {
+                        return type.isAssignableFrom(target.getClass());
+                    }
+                };
+            }
+
+            return new BiPredicate<String, Component>() {
+                @Override
+                public boolean test(String name, Component target) {
+                    return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+                }
+            };
+        }
+    }
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java
index 7e128f6..7507f75 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java
@@ -16,12 +16,217 @@
  */
 package org.apache.camel.spi;
 
+import java.util.function.BiPredicate;
+
+import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingBiConsumer;
+import org.apache.camel.util.function.ThrowingConsumer;
+
+/**
+ * To apply custom configurations to {@link DataFormat} instances.
+ */
 @FunctionalInterface
-public interface DataFormatCustomizer<T extends DataFormat> {
+public interface DataFormatCustomizer extends Ordered {
+    /**
+     * Create a generic {@link Builder}.
+     *
+     * @return the {@link Builder}
+     */
+    static Builder<DataFormat> builder() {
+        return builder(DataFormat.class);
+    }
+
+    /**
+     * Create a typed {@link Builder} that can process a concrete data format type instance.
+     *
+     * @param  type the concrete type of the {@link Component}
+     * @return      the {@link ComponentCustomizer.Builder}
+     */
+    static <T extends DataFormat> Builder<T> builder(Class<T> type) {
+        return new Builder<>(type);
+    }
+
     /**
-     * Customize the specified {@link DataFormat}
+     * Create a {@link DataFormatCustomizer} that can process a concrete data format type instance.
      *
-     * @param dataformat the dataformat to customize
+     * @param  type     the concrete type of the {@link DataFormat}
+     * @param  consumer the {@link DataFormat} configuration logic
+     * @return          the {@link DataFormatCustomizer}
      */
-    void customize(T dataformat);
+    static <T extends DataFormat> DataFormatCustomizer forType(Class<T> type, ThrowingConsumer<T, Exception> consumer) {
+        return builder(type).build(consumer);
+    }
+
+    /**
+     * Customize the specified {@link DataFormat}.
+     *
+     * @param name   the unique name of the data format
+     * @param target the data format to configure
+     */
+    void configure(String name, DataFormat target);
+
+    /**
+     * Checks whether this customizer should be applied to the given {@link DataFormat}.
+     *
+     * @param  name   the unique name of the data format
+     * @param  target the data format to configure
+     * @return        <tt>true</tt> if the customizer should be applied
+     */
+    default boolean isEnabled(String name, DataFormat target) {
+        return true;
+    }
+
+    @Override
+    default int getOrder() {
+        return 0;
+    }
+
+    // ***************************************
+    //
+    // Filter
+    //
+    // ***************************************
+
+    /**
+     * Used as additional filer mechanism to control if customizers need to be applied or not. Each of this filter is
+     * applied before any of the discovered {@link DataFormatCustomizer} is invoked.
+     * </p>
+     * This interface is useful to implement enable/disable logic on a group of customizers.
+     */
+    @FunctionalInterface
+    interface Policy extends BiPredicate<String, DataFormat> {
+        /**
+         * A simple deny-all policy.
+         *
+         * @return false
+         */
+        static Policy none() {
+            return new Policy() {
+                @Override
+                public boolean test(String s, DataFormat target) {
+                    return false;
+                }
+            };
+        }
+
+        /**
+         * A simple allow-all policy.
+         *
+         * @return true
+         */
+        static Policy any() {
+            return new Policy() {
+                @Override
+                public boolean test(String s, DataFormat target) {
+                    return true;
+                }
+            };
+        }
+    }
+
+    // ***************************************
+    //
+    // Builders
+    //
+    // ***************************************
+
+    /**
+     * A fluent builder to create a {@link DataFormatCustomizer} instance.
+     *
+     * @param <T> the concrete type of the {@link DataFormat}.
+     */
+    class Builder<T extends DataFormat> {
+        private final Class<T> type;
+        private BiPredicate<String, DataFormat> condition;
+        private int order;
+
+        public Builder(Class<T> type) {
+            this.type = type;
+        }
+
+        public Builder<T> withOrder(int order) {
+            this.order = order;
+
+            return this;
+        }
+
+        public Builder<T> withCondition(BiPredicate<String, DataFormat> condition) {
+            this.condition = condition;
+
+            return this;
+        }
+
+        public DataFormatCustomizer build(ThrowingConsumer<T, Exception> consumer) {
+            return build(new ThrowingBiConsumer<String, T, Exception>() {
+                @Override
+                public void accept(String name, T target) throws Exception {
+                    consumer.accept(target);
+                }
+            });
+        }
+
+        public DataFormatCustomizer build(ThrowingBiConsumer<String, T, Exception> consumer) {
+            final int order = this.order;
+            final BiPredicate<String, DataFormat> condition = condition();
+
+            return new DataFormatCustomizer() {
+                @SuppressWarnings("unchecked")
+                @Override
+                public void configure(String name, DataFormat target) {
+                    ObjectHelper.notNull(name, "data format name");
+                    ObjectHelper.notNull(target, "data format instance");
+
+                    try {
+                        consumer.accept(name, (T) target);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                @Override
+                public boolean isEnabled(String name, DataFormat target) {
+                    ObjectHelper.notNull(name, "data format name");
+                    ObjectHelper.notNull(target, "data format instance");
+
+                    return condition.test(name, target);
+                }
+
+                @Override
+                public int getOrder() {
+                    return order;
+                }
+            };
+        }
+
+        private BiPredicate<String, DataFormat> condition() {
+            if (type.equals(DataFormat.class)) {
+                return this.condition != null
+                        ? this.condition
+                        : new BiPredicate<String, DataFormat>() {
+                            @Override
+                            public boolean test(String s, DataFormat language) {
+                                return true;
+                            }
+                        };
+            }
+
+            if (condition == null) {
+                return new BiPredicate<String, DataFormat>() {
+                    @Override
+                    public boolean test(String name, DataFormat target) {
+                        return type.isAssignableFrom(target.getClass());
+                    }
+                };
+            }
+
+            return new BiPredicate<String, DataFormat>() {
+                @Override
+                public boolean test(String name, DataFormat target) {
+                    return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+                }
+            };
+        }
+    }
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java
index 12b47ad..492f247 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java
@@ -22,16 +22,6 @@ import org.apache.camel.CamelContext;
  * Represents a resolver of data formats.
  */
 public interface DataFormatResolver {
-
-    /**
-     * Resolves the given data format given its name.
-     *
-     * @param  name    the name of the data format to lookup in {@link org.apache.camel.spi.Registry} or create
-     * @param  context the camel context
-     * @return         the data format or <tt>null</tt> if not possible to resolve
-     */
-    DataFormat resolveDataFormat(String name, CamelContext context);
-
     /**
      * Creates the given data format given its name.
      *
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java b/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java
index 34b3218..76c2ea8 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java
@@ -16,12 +16,217 @@
  */
 package org.apache.camel.spi;
 
+import java.util.function.BiPredicate;
+
+import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingBiConsumer;
+import org.apache.camel.util.function.ThrowingConsumer;
+
+/**
+ * To apply custom configurations to {@link Language} instances.
+ */
 @FunctionalInterface
-public interface LanguageCustomizer<T extends Language> {
+public interface LanguageCustomizer extends Ordered {
+    /**
+     * Create a generic {@link Builder}.
+     *
+     * @return the {@link Builder}
+     */
+    static Builder<Language> builder() {
+        return builder(Language.class);
+    }
+
+    /**
+     * Create a typed {@link Builder} that can process a concrete language type instance.
+     *
+     * @param  type the concrete type of the {@link Component}
+     * @return      the {@link ComponentCustomizer.Builder}
+     */
+    static <T extends Language> Builder<T> builder(Class<T> type) {
+        return new Builder<>(type);
+    }
+
     /**
-     * Customize the specified {@link Language}
+     * Create a {@link DataFormatCustomizer} that can process a concrete language type instance.
      *
-     * @param language the language to customize
+     * @param  type     the concrete type of the {@link Language}
+     * @param  consumer the {@link Language} configuration logic
+     * @return          the {@link LanguageCustomizer}
      */
-    void customize(T language);
+    static <T extends Language> LanguageCustomizer forType(Class<T> type, ThrowingConsumer<T, Exception> consumer) {
+        return builder(type).build(consumer);
+    }
+
+    /**
+     * Customize the specified {@link Language}.
+     *
+     * @param name   the unique name of the language
+     * @param target the language to configure
+     */
+    void configure(String name, Language target);
+
+    /**
+     * Checks whether this customizer should be applied to the given {@link Language}.
+     *
+     * @param  name   the unique name of the language
+     * @param  target the language to configure
+     * @return        <tt>true</tt> if the customizer should be applied
+     */
+    default boolean isEnabled(String name, Language target) {
+        return true;
+    }
+
+    @Override
+    default int getOrder() {
+        return 0;
+    }
+
+    // ***************************************
+    //
+    // Filter
+    //
+    // ***************************************
+
+    /**
+     * Used as additional filer mechanism to control if customizers need to be applied or not. Each of this filter is
+     * applied before any of the discovered {@link LanguageCustomizer} is invoked.
+     * </p>
+     * This interface is useful to implement enable/disable logic on a group of customizers.
+     */
+    @FunctionalInterface
+    interface Policy extends BiPredicate<String, Language> {
+        /**
+         * A simple deny-all policy.
+         *
+         * @return false
+         */
+        static Policy none() {
+            return new Policy() {
+                @Override
+                public boolean test(String s, Language target) {
+                    return false;
+                }
+            };
+        }
+
+        /**
+         * A simple allow-all policy.
+         *
+         * @return true
+         */
+        static Policy any() {
+            return new Policy() {
+                @Override
+                public boolean test(String s, Language target) {
+                    return true;
+                }
+            };
+        }
+    }
+
+    // ***************************************
+    //
+    // Builders
+    //
+    // ***************************************
+
+    /**
+     * A fluent builder to create a {@link LanguageCustomizer} instance.
+     *
+     * @param <T> the concrete type of the {@link Language}.
+     */
+    class Builder<T extends Language> {
+        private final Class<T> type;
+        private BiPredicate<String, Language> condition;
+        private int order;
+
+        public Builder(Class<T> type) {
+            this.type = type;
+        }
+
+        public Builder<T> withOrder(int order) {
+            this.order = order;
+
+            return this;
+        }
+
+        public Builder<T> withCondition(BiPredicate<String, Language> condition) {
+            this.condition = condition;
+
+            return this;
+        }
+
+        public LanguageCustomizer build(ThrowingConsumer<T, Exception> consumer) {
+            return build(new ThrowingBiConsumer<String, T, Exception>() {
+                @Override
+                public void accept(String name, T target) throws Exception {
+                    consumer.accept(target);
+                }
+            });
+        }
+
+        public LanguageCustomizer build(ThrowingBiConsumer<String, T, Exception> consumer) {
+            final int order = this.order;
+            final BiPredicate<String, Language> condition = this.condition != null ? this.condition : (name, target) -> true;
+
+            return new LanguageCustomizer() {
+                @SuppressWarnings("unchecked")
+                @Override
+                public void configure(String name, Language target) {
+                    ObjectHelper.notNull(name, "language name");
+                    ObjectHelper.notNull(target, "language instance");
+
+                    try {
+                        consumer.accept(name, (T) target);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                @Override
+                public boolean isEnabled(String name, Language target) {
+                    ObjectHelper.notNull(name, "language name");
+                    ObjectHelper.notNull(target, "language instance");
+
+                    return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+                }
+
+                @Override
+                public int getOrder() {
+                    return order;
+                }
+            };
+        }
+
+        private BiPredicate<String, Language> condition() {
+            if (type.equals(Language.class)) {
+                return this.condition != null
+                        ? this.condition
+                        : new BiPredicate<String, Language>() {
+                            @Override
+                            public boolean test(String s, Language language) {
+                                return true;
+                            }
+                        };
+            }
+
+            if (condition == null) {
+                return new BiPredicate<String, Language>() {
+                    @Override
+                    public boolean test(String name, Language target) {
+                        return type.isAssignableFrom(target.getClass());
+                    }
+                };
+            }
+
+            return new BiPredicate<String, Language>() {
+                @Override
+                public boolean test(String name, Language target) {
+                    return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+                }
+            };
+        }
+    }
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java
index a31edec..f837cae 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java
@@ -144,6 +144,24 @@ public interface LifecycleStrategy {
     void onEndpointRemove(Endpoint endpoint);
 
     /**
+     * Notification on {@link DataFormat} being resolved from the {@link Registry}
+     *
+     * @param name       the unique name of the {@link DataFormat}
+     * @param dataFormat the resolved {@link DataFormat}
+     */
+    default void onDataFormatCreated(String name, DataFormat dataFormat) {
+    }
+
+    /**
+     * Notification on a {@link Language} instance being resolved.
+     *
+     * @param name     the unique name of the {@link Language}
+     * @param language the created {@link Language}
+     */
+    default void onLanguageCreated(String name, Language language) {
+    }
+
+    /**
      * Notification on adding a {@link Service}.
      *
      * @param context the camel context
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index f8a9252..5a6a30f 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -151,6 +151,7 @@ import org.apache.camel.support.LRUCacheFactory;
 import org.apache.camel.support.NormalizedUri;
 import org.apache.camel.support.OrderedComparator;
 import org.apache.camel.support.ProcessorEndpoint;
+import org.apache.camel.support.ResolverHelper;
 import org.apache.camel.support.jsse.SSLContextParameters;
 import org.apache.camel.support.service.BaseService;
 import org.apache.camel.support.service.ServiceHelper;
@@ -186,6 +187,7 @@ public abstract class AbstractCamelContext extends BaseService
     private final List<StartupListener> startupListeners = new CopyOnWriteArrayList<>();
     private final DeferServiceStartupListener deferStartupListener = new DeferServiceStartupListener();
     private final Map<String, Language> languages = new ConcurrentHashMap<>();
+    private final Map<String, DataFormat> dataformats = new ConcurrentHashMap<>();
     private final List<LifecycleStrategy> lifecycleStrategies = new CopyOnWriteArrayList<>();
     private final ThreadLocal<Boolean> isStartingRoutes = new ThreadLocal<>();
     private final ThreadLocal<Boolean> isSetupRoutes = new ThreadLocal<>();
@@ -329,6 +331,9 @@ public abstract class AbstractCamelContext extends BaseService
         // add a default LifecycleStrategy that discover strategies on the registry and invoke them
         this.lifecycleStrategies.add(new OnCamelContextLifecycleStrategy());
 
+        // add a default LifecycleStrategy to customize services using customizers from registry
+        this.lifecycleStrategies.add(new CustomizersLifecycleStrategy(this));
+
         if (build) {
             try {
                 build();
@@ -1730,22 +1735,38 @@ public abstract class AbstractCamelContext extends BaseService
     public Language resolveLanguage(String language) {
         Language answer;
         synchronized (languages) {
-            answer = languages.get(language);
+            // as first iteration, check if there is a language instance for the given name
+            // bound to the registry
+            answer = ResolverHelper.lookupLanguageInRegistryWithFallback(getCamelContextReference(), language);
+            if (answer != null) {
+                Language old = languages.put(language, answer);
+                // if the language has already been loaded, thus it is already registered
+                // in the local language cache, we can return it as it has already been
+                // initialized and configured
+                if (old == answer) {
+                    return answer;
+                }
+            } else {
+                answer = languages.get(language);
 
-            // check if the language is singleton, if so return the shared
-            // instance
-            if (IsSingleton.test(answer)) {
-                return answer;
+                // check if the language is singleton, if so return the shared
+                // instance
+                if (IsSingleton.test(answer)) {
+                    return answer;
+                } else {
+                    answer = null;
+                }
             }
 
-            // language not known or not singleton, then use resolver
-            answer = getLanguageResolver().resolveLanguage(language, getCamelContextReference());
+            if (answer == null) {
+                // language not known or not singleton, then use resolver
+                answer = getLanguageResolver().resolveLanguage(language, getCamelContextReference());
+            }
 
-            // inject CamelContext if aware
             if (answer != null) {
-                if (answer instanceof CamelContextAware) {
-                    ((CamelContextAware) answer).setCamelContext(getCamelContextReference());
-                }
+                // inject CamelContext if aware
+                CamelContextAware.trySetCamelContext(answer, getCamelContextReference());
+
                 if (answer instanceof Service) {
                     try {
                         startService((Service) answer);
@@ -1754,6 +1775,10 @@ public abstract class AbstractCamelContext extends BaseService
                     }
                 }
 
+                for (LifecycleStrategy strategy : lifecycleStrategies) {
+                    strategy.onLanguageCreated(language, answer);
+                }
+
                 languages.put(language, answer);
             }
         }
@@ -3739,14 +3764,25 @@ public abstract class AbstractCamelContext extends BaseService
 
     @Override
     public DataFormat resolveDataFormat(String name) {
-        DataFormat answer = getDataFormatResolver().resolveDataFormat(name, getCamelContextReference());
+        final DataFormat answer = dataformats.computeIfAbsent(name, new Function<String, DataFormat>() {
+            @Override
+            public DataFormat apply(String s) {
+                DataFormat df = ResolverHelper.lookupDataFormatInRegistryWithFallback(getCamelContextReference(), name);
 
-        // inject CamelContext if aware
-        if (answer instanceof CamelContextAware) {
-            ((CamelContextAware) answer).setCamelContext(getCamelContextReference());
-        }
+                if (df != null) {
+                    // inject CamelContext if aware
+                    CamelContextAware.trySetCamelContext(df, getCamelContextReference());
 
-        return answer;
+                    for (LifecycleStrategy strategy : lifecycleStrategies) {
+                        strategy.onDataFormatCreated(name, df);
+                    }
+                }
+
+                return df;
+            }
+        });
+
+        return answer != null ? answer : createDataFormat(name);
     }
 
     @Override
@@ -3754,8 +3790,10 @@ public abstract class AbstractCamelContext extends BaseService
         DataFormat answer = getDataFormatResolver().createDataFormat(name, getCamelContextReference());
 
         // inject CamelContext if aware
-        if (answer instanceof CamelContextAware) {
-            ((CamelContextAware) answer).setCamelContext(getCamelContextReference());
+        CamelContextAware.trySetCamelContext(answer, getCamelContextReference());
+
+        for (LifecycleStrategy strategy : lifecycleStrategies) {
+            strategy.onDataFormatCreated(name, answer);
         }
 
         return answer;
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/CustomizersLifecycleStrategy.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/CustomizersLifecycleStrategy.java
new file mode 100644
index 0000000..d2b5855
--- /dev/null
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/CustomizersLifecycleStrategy.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.impl.engine;
+
+import java.util.Comparator;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatCustomizer;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.LanguageCustomizer;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.support.LifecycleStrategySupport;
+
+class CustomizersLifecycleStrategy extends LifecycleStrategySupport {
+    private final CamelContext camelContext;
+
+    public CustomizersLifecycleStrategy(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    public void onComponentAdd(String name, Component component) {
+        Registry registry = this.camelContext.getRegistry();
+        if (registry == null) {
+            return;
+        }
+
+        Set<ComponentCustomizer.Policy> filters = registry.findByType(ComponentCustomizer.Policy.class);
+        if (filters.isEmpty() || filters.stream().allMatch(filter -> filter.test(name, component))) {
+            registry.findByType(ComponentCustomizer.class).stream()
+                    .sorted(Comparator.comparingInt(Ordered::getOrder))
+                    .filter(customizer -> customizer.isEnabled(name, component))
+                    .forEach(customizer -> customizer.configure(name, component));
+        }
+    }
+
+    @Override
+    public void onDataFormatCreated(String name, DataFormat dataFormat) {
+        Registry registry = this.camelContext.getRegistry();
+        if (registry == null) {
+            return;
+        }
+
+        Set<DataFormatCustomizer.Policy> filters = registry.findByType(DataFormatCustomizer.Policy.class);
+        if (filters.isEmpty() || filters.stream().allMatch(filter -> filter.test(name, dataFormat))) {
+            registry.findByType(DataFormatCustomizer.class).stream()
+                    .sorted(Comparator.comparingInt(Ordered::getOrder))
+                    .filter(customizer -> customizer.isEnabled(name, dataFormat))
+                    .forEach(customizer -> customizer.configure(name, dataFormat));
+        }
+    }
+
+    @Override
+    public void onLanguageCreated(String name, Language language) {
+        Registry registry = this.camelContext.getRegistry();
+        if (registry == null) {
+            return;
+        }
+
+        Set<LanguageCustomizer.Policy> filters = registry.findByType(LanguageCustomizer.Policy.class);
+        if (filters.isEmpty() || filters.stream().allMatch(filter -> filter.test(name, language))) {
+            registry.findByType(LanguageCustomizer.class).stream()
+                    .sorted(Comparator.comparingInt(Ordered::getOrder))
+                    .filter(customizer -> customizer.isEnabled(name, language))
+                    .forEach(customizer -> customizer.configure(name, language));
+        }
+    }
+}
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java
index 4d01e3e..525cba8 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java
@@ -33,20 +33,6 @@ public class DefaultDataFormatResolver implements DataFormatResolver {
     private FactoryFinder dataformatFactory;
 
     @Override
-    public DataFormat resolveDataFormat(String name, CamelContext context) {
-        // lookup in registry first
-        DataFormat dataFormat = ResolverHelper.lookupDataFormatInRegistryWithFallback(context, name);
-
-        if (dataFormat == null) {
-            // If not found in the registry, try to create a new instance using
-            // a DataFormatFactory or from resources
-            dataFormat = createDataFormat(name, context);
-        }
-
-        return dataFormat;
-    }
-
-    @Override
     public DataFormat createDataFormat(String name, CamelContext context) {
         DataFormat dataFormat = null;
 
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java
index 4043f9e..d596361 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java
@@ -23,7 +23,6 @@ import org.apache.camel.NoSuchLanguageException;
 import org.apache.camel.spi.FactoryFinder;
 import org.apache.camel.spi.Language;
 import org.apache.camel.spi.LanguageResolver;
-import org.apache.camel.support.ResolverHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,12 +41,6 @@ public class DefaultLanguageResolver implements LanguageResolver {
 
     @Override
     public Language resolveLanguage(String name, CamelContext context) {
-        // lookup in registry first
-        Language languageReg = ResolverHelper.lookupLanguageInRegistryWithFallback(context, name);
-        if (languageReg != null) {
-            return languageReg;
-        }
-
         Class<?> type = null;
         try {
             type = findLanguage(name, context);
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/CustomizersTest.java b/core/camel-core/src/test/java/org/apache/camel/support/CustomizersTest.java
new file mode 100644
index 0000000..151f67d
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/CustomizersTest.java
@@ -0,0 +1,457 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.log.LogComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.language.tokenizer.TokenizeLanguage;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatCustomizer;
+import org.apache.camel.spi.DataFormatFactory;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.LanguageCustomizer;
+import org.apache.camel.support.processor.DefaultExchangeFormatter;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.apache.camel.util.CollectionHelper.propertiesOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CustomizersTest {
+
+    // *****************************
+    //
+    // Helpers
+    //
+    // *****************************
+
+    public static Stream<Arguments> disableLanguageCustomizationProperties() {
+        return Stream.of(
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true",
+                        "camel.customizer.language.enabled", "true",
+                        "camel.customizer.language.tokenize.enabled", "false")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true",
+                        "camel.customizer.language.enabled", "false")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false")));
+    }
+
+    public static Stream<Arguments> enableLanguageCustomizationProperties() {
+        return Stream.of(
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false",
+                        "camel.customizer.language.enabled", "false",
+                        "camel.customizer.language.tokenize.enabled", "true")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false",
+                        "camel.customizer.language.enabled", "true")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true")));
+    }
+
+    public static Stream<Arguments> disableDataFormatCustomizationProperties() {
+        return Stream.of(
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true",
+                        "camel.customizer.dataformat.enabled", "true",
+                        "camel.customizer.dataformat.my-df.enabled", "false")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true",
+                        "camel.customizer.dataformat.enabled", "false")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false")));
+    }
+
+    public static Stream<Arguments> enableDataFormatCustomizationProperties() {
+        return Stream.of(
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false",
+                        "camel.customizer.dataformat.enabled", "false",
+                        "camel.customizer.dataformat.my-df.enabled", "true")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false",
+                        "camel.customizer.dataformat.enabled", "true")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true")));
+    }
+
+    public static Stream<Arguments> disableComponentCustomizationProperties() {
+        return Stream.of(
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true",
+                        "camel.customizer.component.enabled", "true",
+                        "camel.customizer.component.log.enabled", "false")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true",
+                        "camel.customizer.component.enabled", "false")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false")));
+    }
+
+    public static Stream<Arguments> enableComponentCustomizationProperties() {
+        return Stream.of(
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false",
+                        "camel.customizer.component.enabled", "false",
+                        "camel.customizer.component.log.enabled", "true")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "false",
+                        "camel.customizer.component.enabled", "true")),
+                Arguments.of(propertiesOf(
+                        "camel.customizer.enabled", "true")));
+    }
+
+    // *****************************
+    //
+    // Component
+    //
+    // *****************************
+
+    @Test
+    public void testComponentCustomization() {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "log-customizer",
+                ComponentCustomizer.forType(
+                        LogComponent.class,
+                        target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+        assertTrue(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+    }
+
+    @Test
+    public void testComponentCustomizationWithFilter() {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "customizer-filter",
+                ComponentCustomizer.Policy.none());
+        context.getRegistry().bind(
+                "log-customizer",
+                ComponentCustomizer.forType(
+                        LogComponent.class,
+                        target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+        assertFalse(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+    }
+
+    @Test
+    public void testComponentCustomizationWithFluentBuilder() {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "log-customizer",
+                ComponentCustomizer.forType(
+                        LogComponent.class,
+                        target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+        assertTrue(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+    }
+
+    @ParameterizedTest
+    @MethodSource("disableComponentCustomizationProperties")
+    public void testComponentCustomizationDisabledByProperty(Properties properties) {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getPropertiesComponent().setInitialProperties(properties);
+
+        context.getRegistry().bind(
+                "customizer-filter",
+                new CustomizersSupport.ComponentCustomizationEnabledPolicy());
+        context.getRegistry().bind(
+                "log-customizer",
+                ComponentCustomizer.forType(
+                        LogComponent.class,
+                        target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+        assertFalse(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+    }
+
+    @ParameterizedTest
+    @MethodSource("enableComponentCustomizationProperties")
+    public void testComponentCustomizationEnabledByProperty(Properties properties) {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getPropertiesComponent().setInitialProperties(properties);
+
+        context.getRegistry().bind(
+                "customizer-filter",
+                new CustomizersSupport.ComponentCustomizationEnabledPolicy());
+        context.getRegistry().bind(
+                "log-customizer",
+                ComponentCustomizer.forType(
+                        LogComponent.class,
+                        target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+        assertTrue(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+    }
+
+    // *****************************
+    //
+    // Data Format
+    //
+    // *****************************
+
+    @Test
+    public void testDataFormatCustomization() {
+        AtomicInteger counter = new AtomicInteger();
+
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "my-df",
+                (DataFormatFactory) MyDataFormat::new);
+        context.getRegistry().bind(
+                "my-df-customizer",
+                DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(counter.incrementAndGet())));
+
+        DataFormat df1 = context.resolveDataFormat("my-df");
+        DataFormat df2 = context.resolveDataFormat("my-df");
+
+        assertNotEquals(df1, df2);
+
+        assertTrue(df1 instanceof MyDataFormat);
+        assertEquals(1, ((MyDataFormat) df1).getId());
+        assertTrue(df2 instanceof MyDataFormat);
+        assertEquals(2, ((MyDataFormat) df2).getId());
+    }
+
+    @Test
+    public void testDataFormatCustomizationWithFilter() {
+        AtomicInteger counter = new AtomicInteger();
+
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "customizer-filter",
+                DataFormatCustomizer.Policy.none());
+        context.getRegistry().bind(
+                "my-df",
+                (DataFormatFactory) MyDataFormat::new);
+        context.getRegistry().bind(
+                "my-df-customizer",
+                DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(counter.incrementAndGet())));
+
+        DataFormat df1 = context.resolveDataFormat("my-df");
+        DataFormat df2 = context.resolveDataFormat("my-df");
+
+        assertNotEquals(df1, df2);
+
+        assertTrue(df1 instanceof MyDataFormat);
+        assertEquals(0, ((MyDataFormat) df1).getId());
+        assertTrue(df2 instanceof MyDataFormat);
+        assertEquals(0, ((MyDataFormat) df2).getId());
+    }
+
+    @ParameterizedTest
+    @MethodSource("disableDataFormatCustomizationProperties")
+    public void testDataFormatCustomizationDisabledByProperty(Properties properties) {
+        AtomicInteger counter = new AtomicInteger();
+
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getPropertiesComponent().setInitialProperties(properties);
+
+        context.getRegistry().bind(
+                "customizer-filter",
+                new CustomizersSupport.DataFormatCustomizationEnabledPolicy());
+        context.getRegistry().bind(
+                "my-df",
+                (DataFormatFactory) MyDataFormat::new);
+        context.getRegistry().bind(
+                "my-df-customizer",
+                DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(counter.incrementAndGet())));
+
+        DataFormat df1 = context.resolveDataFormat("my-df");
+        assertEquals(0, ((MyDataFormat) df1).getId());
+    }
+
+    @ParameterizedTest
+    @MethodSource("enableDataFormatCustomizationProperties")
+    public void testDataFormatCustomizationEnabledByProperty(Properties properties) {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getPropertiesComponent().setInitialProperties(properties);
+
+        context.getRegistry().bind(
+                "customizer-filter",
+                new CustomizersSupport.DataFormatCustomizationEnabledPolicy());
+        context.getRegistry().bind(
+                "my-df",
+                (DataFormatFactory) MyDataFormat::new);
+        context.getRegistry().bind(
+                "my-df-customizer",
+                DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(1)));
+
+        DataFormat df1 = context.resolveDataFormat("my-df");
+        assertEquals(1, ((MyDataFormat) df1).getId());
+    }
+
+    // *****************************
+    //
+    // Language
+    //
+    // *****************************
+
+    @Test
+    public void testLanguageCustomizationFromRegistry() {
+        AtomicInteger counter = new AtomicInteger();
+
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "tokenize",
+                new TokenizeLanguage());
+        context.getRegistry().bind(
+                "tokenize-customizer",
+                LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("" + counter.incrementAndGet())));
+
+        Language l1 = context.resolveLanguage("tokenize");
+        assertTrue(l1 instanceof TokenizeLanguage);
+        assertEquals("1", ((TokenizeLanguage) l1).getGroup());
+
+        Language l2 = context.resolveLanguage("tokenize");
+        assertTrue(l2 instanceof TokenizeLanguage);
+        assertEquals("1", ((TokenizeLanguage) l2).getGroup());
+
+        // as the language is resolved via the registry, then the instance is the same
+        // even if it is not a singleton
+        assertSame(l1, l2);
+    }
+
+    @Test
+    public void testLanguageCustomizationFromResource() {
+        AtomicInteger counter = new AtomicInteger();
+
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "tokenize-customizer",
+                LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("" + counter.incrementAndGet())));
+
+        Language l1 = context.resolveLanguage("tokenize");
+        assertTrue(l1 instanceof TokenizeLanguage);
+        assertEquals("1", ((TokenizeLanguage) l1).getGroup());
+
+        Language l2 = context.resolveLanguage("tokenize");
+        assertTrue(l2 instanceof TokenizeLanguage);
+        assertEquals("2", ((TokenizeLanguage) l2).getGroup());
+
+        assertNotSame(l1, l2);
+    }
+
+    @Test
+    public void testLanguageCustomizationFromResourceWithFilter() {
+        AtomicInteger counter = new AtomicInteger();
+
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getRegistry().bind(
+                "customizer-filter",
+                LanguageCustomizer.Policy.none());
+        context.getRegistry().bind(
+                "tokenize-customizer",
+                LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("" + counter.incrementAndGet())));
+
+        Language l1 = context.resolveLanguage("tokenize");
+        assertTrue(l1 instanceof TokenizeLanguage);
+        assertNull(((TokenizeLanguage) l1).getGroup());
+
+        Language l2 = context.resolveLanguage("tokenize");
+        assertTrue(l2 instanceof TokenizeLanguage);
+        assertNull(((TokenizeLanguage) l2).getGroup());
+
+        assertNotSame(l1, l2);
+    }
+
+    @ParameterizedTest
+    @MethodSource("disableLanguageCustomizationProperties")
+    public void testLanguageCustomizationDisabledByProperty(Properties initialProperties) {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getPropertiesComponent().setInitialProperties(initialProperties);
+
+        context.getRegistry().bind(
+                "customizer-filter",
+                new CustomizersSupport.LanguageCustomizationEnabledPolicy());
+        context.getRegistry().bind(
+                "tokenize-customizer",
+                LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("something")));
+
+        assertNotEquals("something", ((TokenizeLanguage) context.resolveLanguage("tokenize")).getGroup());
+    }
+
+    @ParameterizedTest
+    @MethodSource("enableLanguageCustomizationProperties")
+    public void testLanguageCustomizationEnabledByProperty(Properties initialProperties) {
+        DefaultCamelContext context = new DefaultCamelContext();
+        context.getPropertiesComponent().setInitialProperties(initialProperties);
+
+        context.getRegistry().bind(
+                "customizer-filter",
+                new CustomizersSupport.LanguageCustomizationEnabledPolicy());
+        context.getRegistry().bind(
+                "tokenize-customizer",
+                LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("something")));
+
+        assertEquals("something", ((TokenizeLanguage) context.resolveLanguage("tokenize")).getGroup());
+    }
+
+    // *****************************
+    //
+    // Model
+    //
+    // *****************************
+
+    public static class MyDataFormat implements DataFormat {
+        private int id;
+
+        @Override
+        public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
+        }
+
+        @Override
+        public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
+            return null;
+        }
+
+        @Override
+        public void start() {
+        }
+
+        @Override
+        public void stop() {
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+    }
+
+    public static class MyExchangeFormatter extends DefaultExchangeFormatter {
+    }
+}
diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java
new file mode 100644
index 0000000..c4c8031
--- /dev/null
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.main;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.component.log.LogComponent;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.support.CustomizersSupport;
+import org.apache.camel.support.processor.DefaultExchangeFormatter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MainCustomizerTest {
+    @Test
+    public void testComponentCustomizer() {
+        Main main = new Main();
+
+        try {
+            main.configure().addConfigurationClass(MyConfiguration.class);
+            main.start();
+
+            LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class);
+            assertTrue(component.getExchangeFormatter() instanceof MyFormatter);
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void testComponentCustomizerDisabledWithPolicy() {
+        Main main = new Main();
+
+        try {
+            main.bind("my-filter", ComponentCustomizer.Policy.none());
+            main.configure().addConfigurationClass(MyConfiguration.class);
+            main.start();
+
+            LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class);
+            assertFalse(component.getExchangeFormatter() instanceof MyFormatter);
+        } finally {
+            main.stop();
+        }
+    }
+
+    @Test
+    public void testComponentCustomizerDisabledWithProperties() {
+        Main main = new Main();
+
+        try {
+            main.addInitialProperty("camel.customizer.component.log.enabled", "false");
+            main.bind("my-filter", ComponentCustomizer.Policy.any());
+            main.configure().addConfigurationClass(MyConfiguration.class);
+            main.start();
+
+            LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class);
+            assertFalse(component.getExchangeFormatter() instanceof MyFormatter);
+        } finally {
+            main.stop();
+        }
+    }
+
+    // ****************************
+    //
+    // Helpers
+    //
+    // ****************************
+
+    public static class MyConfiguration {
+        @BindToRegistry
+        public ComponentCustomizer logCustomizer() {
+            return ComponentCustomizer.builder(LogComponent.class)
+                    .build(component -> component.setExchangeFormatter(new MyFormatter()));
+        }
+
+        @BindToRegistry
+        public ComponentCustomizer.Policy componentCustomizationPolicy() {
+            return new CustomizersSupport.ComponentCustomizationEnabledPolicy();
+        }
+    }
+
+    public static class MyFormatter extends DefaultExchangeFormatter {
+    }
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/CustomizersSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/CustomizersSupport.java
new file mode 100644
index 0000000..6f28ad8
--- /dev/null
+++ b/core/camel-support/src/main/java/org/apache/camel/support/CustomizersSupport.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Component;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatCustomizer;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.LanguageCustomizer;
+
+public final class CustomizersSupport {
+    private CustomizersSupport() {
+    }
+
+    /**
+     * Determine the value of the "enabled" flag for a hierarchy of properties.
+     *
+     * @param  camelContext the {@link CamelContext}
+     * @param  prefixes     an ordered list of prefixed (less restrictive to more restrictive)
+     * @return              the value of the key `enabled` for most restrictive prefix
+     */
+    public static boolean isEnabled(CamelContext camelContext, String... prefixes) {
+        boolean answer = true;
+
+        // Loop over all the prefixes to find out the value of the key `enabled`
+        // for the most restrictive prefix.
+        for (String prefix : prefixes) {
+            String property = prefix.endsWith(".") ? prefix + "enabled" : prefix + ".enabled";
+
+            // evaluate the value of the current prefix using the parent one as
+            // default value so if the `enabled` property is not set, the parent
+            // one is used.
+            answer = camelContext.getPropertiesComponent()
+                    .resolveProperty(property)
+                    .map(Boolean::valueOf)
+                    .orElse(answer);
+        }
+
+        return answer;
+    }
+
+    /**
+     * Base class for policies
+     */
+    private static class CamelContextAwarePolicy implements CamelContextAware {
+        private CamelContext camelContext;
+
+        @Override
+        public CamelContext getCamelContext() {
+            return this.camelContext;
+        }
+
+        @Override
+        public void setCamelContext(CamelContext camelContext) {
+            this.camelContext = camelContext;
+        }
+    }
+
+    /**
+     * A {@link ComponentCustomizer.Policy} that uses a hierarchical lists of properties to determine if customization
+     * is enabled for the given {@link org.apache.camel.Component}.
+     */
+    public static final class ComponentCustomizationEnabledPolicy
+            extends CamelContextAwarePolicy
+            implements ComponentCustomizer.Policy {
+
+        @Override
+        public boolean test(String name, Component target) {
+            return isEnabled(
+                    getCamelContext(),
+                    "camel.customizer",
+                    "camel.customizer.component",
+                    "camel.customizer.component." + name);
+        }
+    }
+
+    /**
+     * A {@link DataFormatCustomizer.Policy} that uses a hierarchical lists of properties to determine if customization
+     * is enabled for the given {@link org.apache.camel.spi.DataFormat}.
+     */
+    public static final class DataFormatCustomizationEnabledPolicy
+            extends CamelContextAwarePolicy
+            implements DataFormatCustomizer.Policy {
+
+        @Override
+        public boolean test(String name, DataFormat target) {
+            return isEnabled(
+                    getCamelContext(),
+                    "camel.customizer",
+                    "camel.customizer.dataformat",
+                    "camel.customizer.dataformat." + name);
+        }
+    }
+
+    /**
+     * A {@link LanguageCustomizer.Policy} that uses a hierarchical lists of properties to determine if customization is
+     * enabled for the given {@link org.apache.camel.spi.Language}.
+     */
+    public static final class LanguageCustomizationEnabledPolicy
+            extends CamelContextAwarePolicy
+            implements LanguageCustomizer.Policy {
+
+        @Override
+        public boolean test(String name, Language target) {
+            return isEnabled(
+                    getCamelContext(),
+                    "camel.customizer",
+                    "camel.customizer.language",
+                    "camel.customizer.language." + name);
+        }
+    }
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
index fc896bc..5fca48b 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
@@ -34,13 +34,11 @@ import java.util.Set;
 import java.util.TreeMap;
 
 import org.apache.camel.CamelContext;
-import org.apache.camel.Component;
 import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.PropertyBindingException;
 import org.apache.camel.spi.BeanIntrospection;
 import org.apache.camel.spi.PropertyConfigurer;
 import org.apache.camel.spi.PropertyConfigurerGetter;
-import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.StringQuoteHelper;
 
@@ -88,233 +86,6 @@ import static org.apache.camel.util.StringHelper.startsWithIgnoreCase;
  */
 public final class PropertyBindingSupport {
 
-    /**
-     * To use a fluent builder style to configure this property binding support.
-     */
-    public static class Builder {
-
-        private CamelContext camelContext;
-        private Object target;
-        private Map<String, Object> properties;
-        private boolean removeParameters = true;
-        private boolean flattenProperties;
-        private boolean mandatory;
-        private boolean nesting = true;
-        private boolean deepNesting = true;
-        private boolean reference = true;
-        private boolean placeholder = true;
-        private boolean fluentBuilder = true;
-        private boolean allowPrivateSetter = true;
-        private boolean ignoreCase;
-        private String optionPrefix;
-        private boolean reflection = true;
-        private PropertyConfigurer configurer;
-
-        /**
-         * CamelContext to be used
-         */
-        public Builder withCamelContext(CamelContext camelContext) {
-            this.camelContext = camelContext;
-            return this;
-        }
-
-        /**
-         * Target object that should have parameters bound
-         */
-        public Builder withTarget(Object target) {
-            this.target = target;
-            return this;
-        }
-
-        /**
-         * The properties to use for binding
-         */
-        public Builder withProperties(Map<String, Object> properties) {
-            if (this.properties == null) {
-                this.properties = properties;
-            } else {
-                // there may be existing options so add those if missing
-                // we need to mutate existing as we are may be removing bound properties
-                this.properties.forEach(properties::putIfAbsent);
-                this.properties = properties;
-            }
-            return this;
-        }
-
-        /**
-         * Adds property to use for binding
-         */
-        public Builder withProperty(String key, Object value) {
-            if (this.properties == null) {
-                this.properties = new LinkedHashMap<>();
-            }
-            this.properties.put(key, value);
-            return this;
-        }
-
-        /**
-         * Whether parameters should be removed when its bound
-         */
-        public Builder withRemoveParameters(boolean removeParameters) {
-            this.removeParameters = removeParameters;
-            return this;
-        }
-
-        /**
-         * Whether properties should be flattened (when properties is a map of maps).
-         */
-        public Builder withFlattenProperties(boolean flattenProperties) {
-            this.flattenProperties = flattenProperties;
-            return this;
-        }
-
-        /**
-         * Whether all parameters should be mandatory and successfully bound
-         */
-        public Builder withMandatory(boolean mandatory) {
-            this.mandatory = mandatory;
-            return this;
-        }
-
-        /**
-         * Whether nesting is in use
-         */
-        public Builder withNesting(boolean nesting) {
-            this.nesting = nesting;
-            return this;
-        }
-
-        /**
-         * Whether deep nesting is in use, where Camel will attempt to walk as deep as possible by creating new objects
-         * in the OGNL graph if a property has a setter and the object can be created from a default no-arg constructor.
-         */
-        public Builder withDeepNesting(boolean deepNesting) {
-            this.deepNesting = deepNesting;
-            return this;
-        }
-
-        /**
-         * Whether reference parameter (syntax starts with #) is in use
-         */
-        public Builder withReference(boolean reference) {
-            this.reference = reference;
-            return this;
-        }
-
-        /**
-         * Whether to use Camels property placeholder to resolve placeholders on keys and values
-         */
-        public Builder withPlaceholder(boolean placeholder) {
-            this.placeholder = placeholder;
-            return this;
-        }
-
-        /**
-         * Whether fluent builder is allowed as a valid getter/setter
-         */
-        public Builder withFluentBuilder(boolean fluentBuilder) {
-            this.fluentBuilder = fluentBuilder;
-            return this;
-        }
-
-        /**
-         * Whether properties should be filtered by prefix. * Note that the prefix is removed from the key before the
-         * property is bound.
-         */
-        public Builder withAllowPrivateSetter(boolean allowPrivateSetter) {
-            this.allowPrivateSetter = allowPrivateSetter;
-            return this;
-        }
-
-        /**
-         * Whether to ignore case in the property names (keys).
-         */
-        public Builder withIgnoreCase(boolean ignoreCase) {
-            this.ignoreCase = ignoreCase;
-            return this;
-        }
-
-        /**
-         * Whether properties should be filtered by prefix. Note that the prefix is removed from the key before the
-         * property is bound.
-         */
-        public Builder withOptionPrefix(String optionPrefix) {
-            this.optionPrefix = optionPrefix;
-            return this;
-        }
-
-        /**
-         * Whether to use the configurer to configure the properties.
-         */
-        public Builder withConfigurer(PropertyConfigurer configurer) {
-            this.configurer = configurer;
-            return this;
-        }
-
-        /**
-         * Whether to allow using reflection (when there is no configurer available).
-         */
-        public Builder withReflection(boolean reflection) {
-            this.reflection = reflection;
-            return this;
-        }
-
-        /**
-         * Binds the properties to the target object, and removes the property that was bound from properties.
-         *
-         * @return true if one or more properties was bound
-         */
-        public boolean bind() {
-            // mandatory parameters
-            org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext");
-            org.apache.camel.util.ObjectHelper.notNull(target, "target");
-
-            if (properties == null || properties.isEmpty()) {
-                return false;
-            }
-
-            return doBindProperties(camelContext, target, removeParameters ? properties : new HashMap<>(properties),
-                    optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
-                    nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
-        }
-
-        /**
-         * Binds the properties to the target object, and removes the property that was bound from properties.
-         *
-         * @param  camelContext the camel context
-         * @param  target       the target object
-         * @param  properties   the properties where the bound properties will be removed from
-         * @return              true if one or more properties was bound
-         */
-        public boolean bind(CamelContext camelContext, Object target, Map<String, Object> properties) {
-            CamelContext context = camelContext != null ? camelContext : this.camelContext;
-            Object obj = target != null ? target : this.target;
-            Map<String, Object> prop = properties != null ? properties : this.properties;
-
-            return doBindProperties(context, obj, removeParameters ? prop : new HashMap<>(prop),
-                    optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
-                    nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
-        }
-
-        /**
-         * Binds the property to the target object.
-         *
-         * @param  camelContext the camel context
-         * @param  target       the target object
-         * @param  key          the property key
-         * @param  value        the property value
-         * @return              true if the property was bound
-         */
-        public boolean bind(CamelContext camelContext, Object target, String key, Object value) {
-            Map<String, Object> properties = new HashMap<>(1);
-            properties.put(key, value);
-
-            return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, false, mandatory,
-                    nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
-        }
-
-    }
-
     private PropertyBindingSupport() {
     }
 
@@ -322,21 +93,6 @@ public final class PropertyBindingSupport {
         return new Builder();
     }
 
-    @FunctionalInterface
-    public interface OnAutowiring {
-
-        /**
-         * Callback when a property was autowired on a bean
-         *
-         * @param target       the targeted bean
-         * @param propertyName the name of the property
-         * @param propertyType the type of the property
-         * @param value        the property value
-         */
-        void onAutowire(Object target, String propertyName, Class propertyType, Object value);
-
-    }
-
     /**
      * This will discover all the properties on the target, and automatic bind the properties that are null by looking
      * up in the registry to see if there is a single instance of the same type as the property. This is used for
@@ -391,38 +147,18 @@ public final class PropertyBindingSupport {
         Map<String, Object> properties = new LinkedHashMap<>();
 
         // if there a configurer
-        PropertyConfigurer configurer = null;
-        PropertyConfigurerGetter getter;
-        if (target instanceof Component) {
-            // the component needs to be initialized to have the configurer ready
-            ServiceHelper.initService(target);
-            configurer = ((Component) target).getComponentPropertyConfigurer();
-        }
-        if (configurer == null) {
-            String name = target.getClass().getName();
-            if (target instanceof ExtendedCamelContext) {
-                // special for camel context itself as we have an extended configurer
-                name = ExtendedCamelContext.class.getName();
-            }
-
-            if (isNotEmpty(name)) {
-                // see if there is a configurer for it
-                configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
-                        .resolvePropertyConfigurer(name, camelContext);
-            }
-        }
+        PropertyConfigurer configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, target);
 
         // use configurer to get all the current options and its values
         Map<String, Object> getterAllOption = null;
         if (configurer instanceof PropertyConfigurerGetter) {
-            getter = (PropertyConfigurerGetter) configurer;
-            final PropertyConfigurerGetter lambdaGetter = getter;
+            final PropertyConfigurerGetter getter = (PropertyConfigurerGetter) configurer;
             final Object lambdaTarget = target;
             getterAllOption = getter.getAllOptions(target);
             getterAllOption.forEach((key, type) -> {
                 // we only need the complex types
                 if (isComplexUserType((Class) type)) {
-                    Object value = lambdaGetter.getOptionValue(lambdaTarget, key, true);
+                    Object value = getter.getOptionValue(lambdaTarget, key, true);
                     properties.put(key, value);
                 }
             });
@@ -570,139 +306,6 @@ public final class PropertyBindingSupport {
     }
 
     /**
-     * Used for making it easier to support using option prefix in property binding and to remove the bound properties
-     * from the input map.
-     */
-    private static class OptionPrefixMap extends LinkedHashMap<String, Object> {
-
-        private final String optionPrefix;
-        private final Map<String, Object> originalMap;
-
-        public OptionPrefixMap(Map<String, Object> map, String optionPrefix) {
-            this.originalMap = map;
-            this.optionPrefix = optionPrefix;
-            // copy from original map into our map without the option prefix
-            map.forEach((k, v) -> {
-                if (startsWithIgnoreCase(k, optionPrefix)) {
-                    put(k.substring(optionPrefix.length()), v);
-                } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
-                    put(k.substring(optionPrefix.length() + 1), v);
-                }
-            });
-        }
-
-        @Override
-        public Object remove(Object key) {
-            // we only need to care about the remove method,
-            // so we can remove the corresponding key from the original map
-            Set<String> toBeRemoved = new HashSet<>();
-            originalMap.forEach((k, v) -> {
-                if (startsWithIgnoreCase(k, optionPrefix)) {
-                    toBeRemoved.add(k);
-                } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
-                    toBeRemoved.add(k);
-                }
-            });
-            toBeRemoved.forEach(originalMap::remove);
-
-            return super.remove(key);
-        }
-
-    }
-
-    /**
-     * Used for flatten properties when they are a map of maps
-     */
-    private static class FlattenMap extends LinkedHashMap<String, Object> {
-
-        private final Map<String, Object> originalMap;
-
-        public FlattenMap(Map<String, Object> map) {
-            this.originalMap = map;
-            flatten("", originalMap);
-        }
-
-        @SuppressWarnings("unchecked")
-        private void flatten(String prefix, Map<?, Object> map) {
-            for (Map.Entry<?, Object> entry : map.entrySet()) {
-                String key = entry.getKey().toString();
-                boolean optional = key.startsWith("?");
-                if (optional) {
-                    key = key.substring(1);
-                }
-                Object value = entry.getValue();
-                String keyPrefix = (optional ? "?" : "") + (prefix.isEmpty() ? key : prefix + "." + key);
-                if (value instanceof Map) {
-                    flatten(keyPrefix, (Map<?, Object>) value);
-                } else {
-                    put(keyPrefix, value);
-                }
-            }
-        }
-
-        @Override
-        public Object remove(Object key) {
-            // we only need to care about the remove method,
-            // so we can remove the corresponding key from the original map
-
-            // walk key with dots to remove right node
-            String[] parts = key.toString().split("\\.");
-            Map map = originalMap;
-            for (int i = 0; i < parts.length; i++) {
-                String part = parts[i];
-                Object obj = map.get(part);
-                if (i == parts.length - 1) {
-                    map.remove(part);
-                } else if (obj instanceof Map) {
-                    map = (Map) obj;
-                }
-            }
-
-            // remove empty middle maps
-            Object answer = super.remove(key);
-            if (super.isEmpty()) {
-                originalMap.clear();
-            }
-            return answer;
-        }
-
-    }
-
-    /**
-     * Used for sorting the property keys when doing property binding. We need to sort the keys in a specific order so
-     * we process the binding in a way that allows us to walk down the OGNL object graph and build empty nodes on the
-     * fly, and as well handle map/list and array types as well.
-     */
-    private static final class PropertyBindingKeyComparator implements Comparator<String> {
-
-        private final Map<String, Object> map;
-
-        private PropertyBindingKeyComparator(Map<String, Object> map) {
-            this.map = map;
-        }
-
-        @Override
-        public int compare(String o1, String o2) {
-            // 1) sort by nested level (shortest OGNL graph first)
-            int n1 = StringHelper.countChar(o1, '.');
-            int n2 = StringHelper.countChar(o2, '.');
-            if (n1 != n2) {
-                return Integer.compare(n1, n2);
-            }
-            // 2) sort by reference (as it may refer to other beans in the OGNL graph)
-            Object v1 = map.get(o1);
-            Object v2 = map.get(o2);
-            boolean ref1 = v1 instanceof String && ((String) v1).startsWith("#");
-            boolean ref2 = v2 instanceof String && ((String) v2).startsWith("#");
-            if (ref1 != ref2) {
-                return Boolean.compare(ref1, ref2);
-            }
-            // 3) sort by name
-            return o1.compareTo(o2);
-        }
-    }
-
-    /**
      * Binds the properties with the given prefix to the target object, and removes the property that was bound from
      * properties. Note that the prefix is removed from the key before the property is bound.
      *
@@ -798,8 +401,7 @@ public final class PropertyBindingSupport {
 
         if (configurer == null) {
             // do we have a configurer by any chance
-            configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
-                    .resolvePropertyConfigurer(newClass.getName(), camelContext);
+            configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, newClass);
         }
 
         // we should only walk and create OGNL path for the middle graph
@@ -862,11 +464,9 @@ public final class PropertyBindingSupport {
                     Class<?> collectionType = (Class<?>) ((PropertyConfigurerGetter) configurer)
                             .getCollectionValueType(newTarget, undashKey(key), ignoreCase);
                     if (collectionType != null) {
-                        configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
-                                .resolvePropertyConfigurer(collectionType.getName(), camelContext);
+                        configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, collectionType);
                     } else {
-                        configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
-                                .resolvePropertyConfigurer(prop.getClass().getName(), camelContext);
+                        configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, prop.getClass());
                     }
                 }
                 // prepare for next iterator
@@ -2052,4 +1652,379 @@ public final class PropertyBindingSupport {
         return key;
     }
 
+    @FunctionalInterface
+    public interface OnAutowiring {
+
+        /**
+         * Callback when a property was autowired on a bean
+         *
+         * @param target       the targeted bean
+         * @param propertyName the name of the property
+         * @param propertyType the type of the property
+         * @param value        the property value
+         */
+        void onAutowire(Object target, String propertyName, Class propertyType, Object value);
+
+    }
+
+    /**
+     * To use a fluent builder style to configure this property binding support.
+     */
+    public static class Builder {
+
+        private CamelContext camelContext;
+        private Object target;
+        private Map<String, Object> properties;
+        private boolean removeParameters = true;
+        private boolean flattenProperties;
+        private boolean mandatory;
+        private boolean nesting = true;
+        private boolean deepNesting = true;
+        private boolean reference = true;
+        private boolean placeholder = true;
+        private boolean fluentBuilder = true;
+        private boolean allowPrivateSetter = true;
+        private boolean ignoreCase;
+        private String optionPrefix;
+        private boolean reflection = true;
+        private PropertyConfigurer configurer;
+
+        /**
+         * CamelContext to be used
+         */
+        public Builder withCamelContext(CamelContext camelContext) {
+            this.camelContext = camelContext;
+            return this;
+        }
+
+        /**
+         * Target object that should have parameters bound
+         */
+        public Builder withTarget(Object target) {
+            this.target = target;
+            return this;
+        }
+
+        /**
+         * The properties to use for binding
+         */
+        public Builder withProperties(Map<String, Object> properties) {
+            if (this.properties == null) {
+                this.properties = properties;
+            } else {
+                // there may be existing options so add those if missing
+                // we need to mutate existing as we are may be removing bound properties
+                this.properties.forEach(properties::putIfAbsent);
+                this.properties = properties;
+            }
+            return this;
+        }
+
+        /**
+         * Adds property to use for binding
+         */
+        public Builder withProperty(String key, Object value) {
+            if (this.properties == null) {
+                this.properties = new LinkedHashMap<>();
+            }
+            this.properties.put(key, value);
+            return this;
+        }
+
+        /**
+         * Whether parameters should be removed when its bound
+         */
+        public Builder withRemoveParameters(boolean removeParameters) {
+            this.removeParameters = removeParameters;
+            return this;
+        }
+
+        /**
+         * Whether properties should be flattened (when properties is a map of maps).
+         */
+        public Builder withFlattenProperties(boolean flattenProperties) {
+            this.flattenProperties = flattenProperties;
+            return this;
+        }
+
+        /**
+         * Whether all parameters should be mandatory and successfully bound
+         */
+        public Builder withMandatory(boolean mandatory) {
+            this.mandatory = mandatory;
+            return this;
+        }
+
+        /**
+         * Whether nesting is in use
+         */
+        public Builder withNesting(boolean nesting) {
+            this.nesting = nesting;
+            return this;
+        }
+
+        /**
+         * Whether deep nesting is in use, where Camel will attempt to walk as deep as possible by creating new objects
+         * in the OGNL graph if a property has a setter and the object can be created from a default no-arg constructor.
+         */
+        public Builder withDeepNesting(boolean deepNesting) {
+            this.deepNesting = deepNesting;
+            return this;
+        }
+
+        /**
+         * Whether reference parameter (syntax starts with #) is in use
+         */
+        public Builder withReference(boolean reference) {
+            this.reference = reference;
+            return this;
+        }
+
+        /**
+         * Whether to use Camels property placeholder to resolve placeholders on keys and values
+         */
+        public Builder withPlaceholder(boolean placeholder) {
+            this.placeholder = placeholder;
+            return this;
+        }
+
+        /**
+         * Whether fluent builder is allowed as a valid getter/setter
+         */
+        public Builder withFluentBuilder(boolean fluentBuilder) {
+            this.fluentBuilder = fluentBuilder;
+            return this;
+        }
+
+        /**
+         * Whether properties should be filtered by prefix. * Note that the prefix is removed from the key before the
+         * property is bound.
+         */
+        public Builder withAllowPrivateSetter(boolean allowPrivateSetter) {
+            this.allowPrivateSetter = allowPrivateSetter;
+            return this;
+        }
+
+        /**
+         * Whether to ignore case in the property names (keys).
+         */
+        public Builder withIgnoreCase(boolean ignoreCase) {
+            this.ignoreCase = ignoreCase;
+            return this;
+        }
+
+        /**
+         * Whether properties should be filtered by prefix. Note that the prefix is removed from the key before the
+         * property is bound.
+         */
+        public Builder withOptionPrefix(String optionPrefix) {
+            this.optionPrefix = optionPrefix;
+            return this;
+        }
+
+        /**
+         * Whether to use the configurer to configure the properties.
+         */
+        public Builder withConfigurer(PropertyConfigurer configurer) {
+            this.configurer = configurer;
+            return this;
+        }
+
+        /**
+         * Whether to allow using reflection (when there is no configurer available).
+         */
+        public Builder withReflection(boolean reflection) {
+            this.reflection = reflection;
+            return this;
+        }
+
+        /**
+         * Binds the properties to the target object, and removes the property that was bound from properties.
+         *
+         * @return true if one or more properties was bound
+         */
+        public boolean bind() {
+            // mandatory parameters
+            org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext");
+            org.apache.camel.util.ObjectHelper.notNull(target, "target");
+
+            if (properties == null || properties.isEmpty()) {
+                return false;
+            }
+
+            return doBindProperties(camelContext, target, removeParameters ? properties : new HashMap<>(properties),
+                    optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
+                    nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
+        }
+
+        /**
+         * Binds the properties to the target object, and removes the property that was bound from properties.
+         *
+         * @param  camelContext the camel context
+         * @param  target       the target object
+         * @param  properties   the properties where the bound properties will be removed from
+         * @return              true if one or more properties was bound
+         */
+        public boolean bind(CamelContext camelContext, Object target, Map<String, Object> properties) {
+            CamelContext context = camelContext != null ? camelContext : this.camelContext;
+            Object obj = target != null ? target : this.target;
+            Map<String, Object> prop = properties != null ? properties : this.properties;
+
+            return doBindProperties(context, obj, removeParameters ? prop : new HashMap<>(prop),
+                    optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
+                    nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
+        }
+
+        /**
+         * Binds the property to the target object.
+         *
+         * @param  camelContext the camel context
+         * @param  target       the target object
+         * @param  key          the property key
+         * @param  value        the property value
+         * @return              true if the property was bound
+         */
+        public boolean bind(CamelContext camelContext, Object target, String key, Object value) {
+            Map<String, Object> properties = new HashMap<>(1);
+            properties.put(key, value);
+
+            return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, false, mandatory,
+                    nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
+        }
+
+    }
+
+    /**
+     * Used for making it easier to support using option prefix in property binding and to remove the bound properties
+     * from the input map.
+     */
+    private static class OptionPrefixMap extends LinkedHashMap<String, Object> {
+
+        private final String optionPrefix;
+        private final Map<String, Object> originalMap;
+
+        public OptionPrefixMap(Map<String, Object> map, String optionPrefix) {
+            this.originalMap = map;
+            this.optionPrefix = optionPrefix;
+            // copy from original map into our map without the option prefix
+            map.forEach((k, v) -> {
+                if (startsWithIgnoreCase(k, optionPrefix)) {
+                    put(k.substring(optionPrefix.length()), v);
+                } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
+                    put(k.substring(optionPrefix.length() + 1), v);
+                }
+            });
+        }
+
+        @Override
+        public Object remove(Object key) {
+            // we only need to care about the remove method,
+            // so we can remove the corresponding key from the original map
+            Set<String> toBeRemoved = new HashSet<>();
+            originalMap.forEach((k, v) -> {
+                if (startsWithIgnoreCase(k, optionPrefix)) {
+                    toBeRemoved.add(k);
+                } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
+                    toBeRemoved.add(k);
+                }
+            });
+            toBeRemoved.forEach(originalMap::remove);
+
+            return super.remove(key);
+        }
+
+    }
+
+    /**
+     * Used for flatten properties when they are a map of maps
+     */
+    private static class FlattenMap extends LinkedHashMap<String, Object> {
+
+        private final Map<String, Object> originalMap;
+
+        public FlattenMap(Map<String, Object> map) {
+            this.originalMap = map;
+            flatten("", originalMap);
+        }
+
+        @SuppressWarnings("unchecked")
+        private void flatten(String prefix, Map<?, Object> map) {
+            for (Map.Entry<?, Object> entry : map.entrySet()) {
+                String key = entry.getKey().toString();
+                boolean optional = key.startsWith("?");
+                if (optional) {
+                    key = key.substring(1);
+                }
+                Object value = entry.getValue();
+                String keyPrefix = (optional ? "?" : "") + (prefix.isEmpty() ? key : prefix + "." + key);
+                if (value instanceof Map) {
+                    flatten(keyPrefix, (Map<?, Object>) value);
+                } else {
+                    put(keyPrefix, value);
+                }
+            }
+        }
+
+        @Override
+        public Object remove(Object key) {
+            // we only need to care about the remove method,
+            // so we can remove the corresponding key from the original map
+
+            // walk key with dots to remove right node
+            String[] parts = key.toString().split("\\.");
+            Map map = originalMap;
+            for (int i = 0; i < parts.length; i++) {
+                String part = parts[i];
+                Object obj = map.get(part);
+                if (i == parts.length - 1) {
+                    map.remove(part);
+                } else if (obj instanceof Map) {
+                    map = (Map) obj;
+                }
+            }
+
+            // remove empty middle maps
+            Object answer = super.remove(key);
+            if (super.isEmpty()) {
+                originalMap.clear();
+            }
+            return answer;
+        }
+
+    }
+
+    /**
+     * Used for sorting the property keys when doing property binding. We need to sort the keys in a specific order so
+     * we process the binding in a way that allows us to walk down the OGNL object graph and build empty nodes on the
+     * fly, and as well handle map/list and array types as well.
+     */
+    private static final class PropertyBindingKeyComparator implements Comparator<String> {
+
+        private final Map<String, Object> map;
+
+        private PropertyBindingKeyComparator(Map<String, Object> map) {
+            this.map = map;
+        }
+
+        @Override
+        public int compare(String o1, String o2) {
+            // 1) sort by nested level (shortest OGNL graph first)
+            int n1 = StringHelper.countChar(o1, '.');
+            int n2 = StringHelper.countChar(o2, '.');
+            if (n1 != n2) {
+                return Integer.compare(n1, n2);
+            }
+            // 2) sort by reference (as it may refer to other beans in the OGNL graph)
+            Object v1 = map.get(o1);
+            Object v2 = map.get(o2);
+            boolean ref1 = v1 instanceof String && ((String) v1).startsWith("#");
+            boolean ref2 = v2 instanceof String && ((String) v2).startsWith("#");
+            if (ref1 != ref2) {
+                return Boolean.compare(ref1, ref2);
+            }
+            // 3) sort by name
+            return o1.compareTo(o2);
+        }
+    }
+
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java
index 97b7ee2..b04397c 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java
@@ -17,8 +17,10 @@
 package org.apache.camel.support;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
 import org.apache.camel.ExtendedCamelContext;
-import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.ObjectHelper;
 
 /**
@@ -39,13 +41,55 @@ public final class PropertyConfigurerHelper {
      * @param  target  the target object for which we need a {@link org.apache.camel.spi.PropertyConfigurer}
      * @return         the resolved configurer, or <tt>null</tt> if no configurer could be found
      */
-    public static GeneratedPropertyConfigurer resolvePropertyConfigurer(CamelContext context, Object target) {
+    public static PropertyConfigurer resolvePropertyConfigurer(CamelContext context, Object target) {
         ObjectHelper.notNull(target, "target");
         ObjectHelper.notNull(context, "context");
 
+        PropertyConfigurer configurer = null;
+
+        if (target instanceof Component) {
+            // the component needs to be initialized to have the configurer ready
+            ServiceHelper.initService(target);
+            configurer = ((Component) target).getComponentPropertyConfigurer();
+        }
+
+        if (configurer == null) {
+            String name = target.getClass().getName();
+            if (target instanceof ExtendedCamelContext) {
+                // special for camel context itself as we have an extended configurer
+                name = ExtendedCamelContext.class.getName();
+            }
+
+            // see if there is a configurer for it
+            configurer = context.adapt(ExtendedCamelContext.class)
+                    .getConfigurerResolver()
+                    .resolvePropertyConfigurer(name, context);
+        }
+
+        return configurer;
+    }
+
+    /**
+     * Resolves the given configurer.
+     *
+     * @param  context    the camel context
+     * @param  targetType the target object type for which we need a {@link org.apache.camel.spi.PropertyConfigurer}
+     * @return            the resolved configurer, or <tt>null</tt> if no configurer could be found
+     */
+    public static PropertyConfigurer resolvePropertyConfigurer(CamelContext context, Class<?> targetType) {
+        ObjectHelper.notNull(targetType, "targetType");
+        ObjectHelper.notNull(context, "context");
+
+        String name = targetType.getName();
+        if (ExtendedCamelContext.class.isAssignableFrom(targetType)) {
+            // special for camel context itself as we have an extended configurer
+            name = ExtendedCamelContext.class.getName();
+        }
+
+        // see if there is a configurer for it
         return context.adapt(ExtendedCamelContext.class)
                 .getConfigurerResolver()
-                .resolvePropertyConfigurer(target.getClass().getName(), context);
+                .resolvePropertyConfigurer(name, context);
     }
 
     /**
@@ -60,7 +104,7 @@ public final class PropertyConfigurerHelper {
         ObjectHelper.notNull(target, "target");
         ObjectHelper.notNull(context, "context");
 
-        GeneratedPropertyConfigurer configurer = resolvePropertyConfigurer(context, target);
+        PropertyConfigurer configurer = resolvePropertyConfigurer(context, target);
         if (type.isInstance(configurer)) {
             return type.cast(configurer);
         }
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java
index 39ccc3d..89dc186 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java
@@ -164,6 +164,7 @@ public final class CollectionHelper {
     /**
      * Build a map from varargs.
      */
+    @SuppressWarnings("unchecked")
     public static <K, V> Map<K, V> mapOf(Supplier<Map<K, V>> creator, K key, V value, Object... keyVals) {
         Map<K, V> map = creator.get();
         map.put(key, value);