You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2020/10/08 14:08:11 UTC

[camel] branch master updated: CAMEL-15652: Add bean scope to method call expression and bean function in simple language.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7bffd02  CAMEL-15652: Add bean scope to method call expression and bean function in simple language.
7bffd02 is described below

commit 7bffd02619c008e5b8943d940937f13f4dd91f95
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Oct 8 16:06:36 2020 +0200

    CAMEL-15652: Add bean scope to method call expression and bean function in simple language.
---
 .../apache/camel/catalog/docs/bean-language.adoc   |   3 +-
 .../org/apache/camel/language/bean/bean.json       |   1 +
 .../camel-bean/src/main/docs/bean-language.adoc    |   3 +-
 .../component/bean/ConstantTypeBeanHolder.java     |   2 +-
 .../camel/component/bean/RequestBeanHolder.java    |   3 +
 .../apache/camel/language/bean/BeanExpression.java |  64 ++++++++----
 .../apache/camel/language/bean/BeanLanguage.java   |  39 ++++++--
 .../org/apache/camel/model/language/method.json    |   1 +
 .../org/apache/camel/builder/ExpressionClause.java |  26 +++++
 .../camel/builder/ExpressionClauseSupport.java     |  30 ++++++
 .../camel/model/language/MethodCallExpression.java |  24 +++++
 .../language/MethodCallExpressionReifier.java      |   3 +-
 .../simple/ast/SimpleFunctionExpression.java       |  53 +++++++++-
 .../bean/MethodCallBeanFunctionScopeTest.java      |  46 +++++++++
 .../bean/SimpleLanguageBeanFunctionScopeTest.java  | 109 +++++++++++++++++++++
 .../java/org/apache/camel/xml/in/ModelParser.java  |   1 +
 .../modules/languages/pages/bean-language.adoc     |   3 +-
 .../ROOT/pages/camel-3x-upgrade-guide-3_6.adoc     |  10 ++
 18 files changed, 390 insertions(+), 31 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/bean-language.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/bean-language.adoc
index 6655d7b..8b68f89 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/bean-language.adoc
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/bean-language.adoc
@@ -27,7 +27,7 @@ xref:xpath-language.adoc[XPath] or xref:xquery-language.adoc[XQuery] from the me
 == Bean Language options
 
 // language options: START
-The Bean method language supports 4 options, which are listed below.
+The Bean method language supports 5 options, which are listed below.
 
 
 
@@ -37,6 +37,7 @@ The Bean method language supports 4 options, which are listed below.
 | ref |  | String | Reference to bean to lookup in the registry
 | method |  | String | Name of method to call
 | beanType |  | String | Class name of the bean to use
+| scope | Singleton | String | Scope of bean. When using singleton scope (default) the bean is created or looked up only once and reused for the lifetime of the endpoint. The bean should be thread-safe in case concurrent threads is calling the bean at the same time. When using request scope the bean is created or looked up once per request (exchange). This can be used if you want to store state on a bean while processing a request and you want to call the same bean instance multiple time [...]
 | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks
 |===
 // language options: END
diff --git a/components/camel-bean/src/generated/resources/org/apache/camel/language/bean/bean.json b/components/camel-bean/src/generated/resources/org/apache/camel/language/bean/bean.json
index 8636e8d..772bec9 100644
--- a/components/camel-bean/src/generated/resources/org/apache/camel/language/bean/bean.json
+++ b/components/camel-bean/src/generated/resources/org/apache/camel/language/bean/bean.json
@@ -19,6 +19,7 @@
     "ref": { "kind": "attribute", "displayName": "Ref", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Reference to bean to lookup in the registry" },
     "method": { "kind": "attribute", "displayName": "Method", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Name of method to call" },
     "beanType": { "kind": "attribute", "displayName": "Bean Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Class name of the bean to use" },
+    "scope": { "kind": "attribute", "displayName": "Scope", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "Prototype", "Request", "Singleton" ], "deprecated": false, "secret": false, "defaultValue": "Singleton", "description": "Scope of bean. When using singleton scope (default) the bean is created or looked up only once and reused for the lifetime of the endpoint. The bean should be thread-safe in case concurrent threads is calling the bean at the same tim [...]
     "trim": { "kind": "attribute", "displayName": "Trim", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" },
     "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Sets the id of this node" }
   }
diff --git a/components/camel-bean/src/main/docs/bean-language.adoc b/components/camel-bean/src/main/docs/bean-language.adoc
index 6655d7b..8b68f89 100644
--- a/components/camel-bean/src/main/docs/bean-language.adoc
+++ b/components/camel-bean/src/main/docs/bean-language.adoc
@@ -27,7 +27,7 @@ xref:xpath-language.adoc[XPath] or xref:xquery-language.adoc[XQuery] from the me
 == Bean Language options
 
 // language options: START
-The Bean method language supports 4 options, which are listed below.
+The Bean method language supports 5 options, which are listed below.
 
 
 
@@ -37,6 +37,7 @@ The Bean method language supports 4 options, which are listed below.
 | ref |  | String | Reference to bean to lookup in the registry
 | method |  | String | Name of method to call
 | beanType |  | String | Class name of the bean to use
+| scope | Singleton | String | Scope of bean. When using singleton scope (default) the bean is created or looked up only once and reused for the lifetime of the endpoint. The bean should be thread-safe in case concurrent threads is calling the bean at the same time. When using request scope the bean is created or looked up once per request (exchange). This can be used if you want to store state on a bean while processing a request and you want to call the same bean instance multiple time [...]
 | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks
 |===
 // language options: END
diff --git a/components/camel-bean/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java b/components/camel-bean/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
index c8ff0ca..0662aa9 100644
--- a/components/camel-bean/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
+++ b/components/camel-bean/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
@@ -60,7 +60,7 @@ public class ConstantTypeBeanHolder implements BeanTypeHolder {
      *
      * @return a new {@link org.apache.camel.component.bean.BeanHolder} that has cached the lookup of the bean.
      */
-    public ConstantBeanHolder createCacheHolder() throws Exception {
+    public ConstantBeanHolder createCacheHolder() {
         Object bean = getBean(null);
         return new ConstantBeanHolder(bean, beanInfo);
     }
diff --git a/components/camel-bean/src/main/java/org/apache/camel/component/bean/RequestBeanHolder.java b/components/camel-bean/src/main/java/org/apache/camel/component/bean/RequestBeanHolder.java
index df98660..a17b766 100644
--- a/components/camel-bean/src/main/java/org/apache/camel/component/bean/RequestBeanHolder.java
+++ b/components/camel-bean/src/main/java/org/apache/camel/component/bean/RequestBeanHolder.java
@@ -47,6 +47,9 @@ public class RequestBeanHolder implements BeanHolder {
 
     @Override
     public Object getBean(Exchange exchange) throws NoSuchBeanException {
+        if (exchange == null) {
+            return holder.getBean(null);
+        }
         Object bean = exchange.getProperty(key);
         if (bean == null) {
             bean = holder.getBean(exchange);
diff --git a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
index 22173b4..48fd6ec 100644
--- a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
+++ b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
@@ -19,6 +19,7 @@ package org.apache.camel.language.bean;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.camel.BeanScope;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
@@ -38,8 +39,8 @@ import org.apache.camel.component.bean.MethodNotFoundException;
 import org.apache.camel.component.bean.ParameterMappingStrategy;
 import org.apache.camel.component.bean.ParameterMappingStrategyHelper;
 import org.apache.camel.component.bean.RegistryBean;
+import org.apache.camel.component.bean.RequestBeanHolder;
 import org.apache.camel.spi.Language;
-import org.apache.camel.support.CamelContextHelper;
 import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.LanguageSupport;
 import org.apache.camel.util.KeyValueHolder;
@@ -64,6 +65,7 @@ public class BeanExpression implements Expression, Predicate {
     private String method;
     private BeanHolder beanHolder;
     private boolean ognlMethod;
+    private BeanScope scope = BeanScope.Singleton;
 
     public BeanExpression(Object bean, String method) {
         this.bean = bean;
@@ -102,6 +104,14 @@ public class BeanExpression implements Expression, Predicate {
         return method;
     }
 
+    public BeanScope getScope() {
+        return scope;
+    }
+
+    public void setScope(BeanScope scope) {
+        this.scope = scope;
+    }
+
     public ParameterMappingStrategy getParameterMappingStrategy() {
         return parameterMappingStrategy;
     }
@@ -134,25 +144,22 @@ public class BeanExpression implements Expression, Predicate {
         if (beanComponent == null) {
             beanComponent = context.getComponent("bean", BeanComponent.class);
         }
+        if (beanName != null && beanName.startsWith("type:")) {
+            // its a reference to a fqn class so load the class and use type instead
+            String fqn = beanName.substring(5);
+            try {
+                type = context.getClassResolver().resolveMandatoryClass(fqn);
+                beanName = null;
+            } catch (ClassNotFoundException e) {
+                throw new NoSuchBeanException(beanName, e);
+            }
+        }
         if (beanHolder == null) {
             beanHolder = createBeanHolder(context, parameterMappingStrategy, beanComponent);
         }
+
         // lets see if we can do additional validation that the bean has valid method during creation of the expression
-        Object target = bean;
-        if (bean == null && type == null && beanName != null) {
-            if (beanName.startsWith("type:")) {
-                // its a reference to a fqn class so load the class and use type instead
-                String fqn = beanName.substring(5);
-                try {
-                    type = context.getClassResolver().resolveMandatoryClass(fqn);
-                    beanName = null;
-                } catch (ClassNotFoundException e) {
-                    throw new NoSuchBeanException(beanName, e);
-                }
-            } else {
-                target = CamelContextHelper.mandatoryLookup(context, beanName);
-            }
-        }
+        Object target = beanHolder.getBean(null);
         if (method != null) {
             validateHasMethod(context, target, type, method);
 
@@ -272,13 +279,32 @@ public class BeanExpression implements Expression, Predicate {
     private BeanHolder createBeanHolder(
             CamelContext context, ParameterMappingStrategy parameterMappingStrategy, BeanComponent beanComponent) {
         // either use registry lookup or a constant bean
-        BeanHolder holder;
+        BeanHolder holder = null;
         if (bean != null) {
             holder = new ConstantBeanHolder(bean, context, parameterMappingStrategy, beanComponent);
         } else if (beanName != null) {
-            holder = new RegistryBean(context, beanName, parameterMappingStrategy, beanComponent);
+            RegistryBean rb = new RegistryBean(context, beanName, parameterMappingStrategy, beanComponent);
+            if (scope == BeanScope.Singleton) {
+                // cache holder as its singleton
+                holder = rb.createCacheHolder();
+            } else if (scope == BeanScope.Request) {
+                // wrap in registry scoped
+                holder = new RequestBeanHolder(rb);
+            } else {
+                // prototype scope will lookup bean on each access
+                holder = rb;
+            }
         } else if (type != null) {
-            holder = new ConstantTypeBeanHolder(type, context, parameterMappingStrategy, beanComponent);
+            ConstantTypeBeanHolder th = new ConstantTypeBeanHolder(type, context, parameterMappingStrategy, beanComponent);
+            if (scope == BeanScope.Singleton && ObjectHelper.hasDefaultPublicNoArgConstructor(type)) {
+                // we can only cache if we can create an instance of the bean, and for that we need a public constructor
+                holder = th.createCacheHolder();
+            } else if (scope == BeanScope.Request) {
+                // wrap in registry scoped
+                holder = new RequestBeanHolder(th);
+            } else {
+                holder = th;
+            }
         } else {
             throw new IllegalArgumentException("Either bean, beanName or type should be set on " + this);
         }
diff --git a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanLanguage.java b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanLanguage.java
index c2ede52..abb2bec 100644
--- a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanLanguage.java
+++ b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanLanguage.java
@@ -16,6 +16,10 @@
  */
 package org.apache.camel.language.bean;
 
+import java.net.URISyntaxException;
+import java.util.Map;
+
+import org.apache.camel.BeanScope;
 import org.apache.camel.Expression;
 import org.apache.camel.Predicate;
 import org.apache.camel.RuntimeCamelException;
@@ -27,6 +31,7 @@ import org.apache.camel.spi.Language;
 import org.apache.camel.support.ExpressionToPredicateAdapter;
 import org.apache.camel.support.LanguageSupport;
 import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.URISupport;
 
 /**
  * A <a href="http://camel.apache.org/bean-language.html">bean language</a> which uses a simple text notation to invoke
@@ -50,6 +55,7 @@ public class BeanLanguage extends LanguageSupport implements StaticService {
     private Class<?> beanType;
     private String ref;
     private String method;
+    private BeanScope scope = BeanScope.Singleton;
 
     public BeanLanguage() {
     }
@@ -86,6 +92,14 @@ public class BeanLanguage extends LanguageSupport implements StaticService {
         this.method = method;
     }
 
+    public BeanScope getScope() {
+        return scope;
+    }
+
+    public void setScope(BeanScope scope) {
+        this.scope = scope;
+    }
+
     @Override
     public Predicate createPredicate(String expression) {
         return ExpressionToPredicateAdapter.toPredicate(createExpression(expression));
@@ -120,7 +134,9 @@ public class BeanLanguage extends LanguageSupport implements StaticService {
         if (answer == null) {
             throw new IllegalArgumentException("Bean language requires bean, beanType, or ref argument");
         }
-
+        if (properties.length == 5) {
+            answer.setScope(property(BeanScope.class, properties, 4, scope));
+        }
         answer.setBeanComponent(beanComponent);
         answer.setParameterMappingStrategy(parameterMappingStrategy);
         answer.setSimple(simple);
@@ -131,6 +147,7 @@ public class BeanLanguage extends LanguageSupport implements StaticService {
     @Override
     public Expression createExpression(String expression) {
         BeanExpression answer;
+        String beanScope = null;
 
         // favour using the configured options
         if (bean != null) {
@@ -140,14 +157,19 @@ public class BeanLanguage extends LanguageSupport implements StaticService {
         } else if (ref != null) {
             answer = new BeanExpression(ref, method);
         } else {
+            // we support different syntax for bean function
             String beanName = expression;
             String method = null;
-
-            // we support both the .method name and the ?method= syntax
-            // as the ?method= syntax is very common for the bean component
-            if (expression.contains("?method=")) {
+            if (expression.contains("?method=") || expression.contains("?scope=")) {
                 beanName = StringHelper.before(expression, "?");
-                method = StringHelper.after(expression, "?method=");
+                String query = StringHelper.after(expression, "?");
+                try {
+                    Map<String, Object> map = URISupport.parseQuery(query);
+                    method = (String) map.get("method");
+                    beanScope = (String) map.get("scope");
+                } catch (URISyntaxException e) {
+                    throw RuntimeCamelException.wrapRuntimeException(e);
+                }
             } else {
                 //first check case :: because of my.own.Bean::method
                 int doubleColonIndex = expression.indexOf("::");
@@ -177,6 +199,11 @@ public class BeanLanguage extends LanguageSupport implements StaticService {
             }
         }
 
+        if (beanScope != null) {
+            answer.setScope(getCamelContext().getTypeConverter().tryConvertTo(BeanScope.class, beanScope));
+        } else {
+            answer.setScope(scope);
+        }
         answer.setBeanComponent(beanComponent);
         answer.setParameterMappingStrategy(parameterMappingStrategy);
         answer.setSimple(simple);
diff --git a/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/method.json b/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/method.json
index 3140fae..f5bb766 100644
--- a/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/method.json
+++ b/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/method.json
@@ -15,6 +15,7 @@
     "ref": { "kind": "attribute", "displayName": "Ref", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Reference to bean to lookup in the registry" },
     "method": { "kind": "attribute", "displayName": "Method", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Name of method to call" },
     "beanType": { "kind": "attribute", "displayName": "Bean Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Class name of the bean to use" },
+    "scope": { "kind": "attribute", "displayName": "Scope", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "Prototype", "Request", "Singleton" ], "deprecated": false, "secret": false, "defaultValue": "Singleton", "description": "Scope of bean. When using singleton scope (default) the bean is created or looked up only once and reused for the lifetime of the endpoint. The bean should be thread-safe in case concurrent threads is calling the bean at the same tim [...]
     "trim": { "kind": "attribute", "displayName": "Trim", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" },
     "id": { "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Sets the id of this node" }
   }
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClause.java b/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClause.java
index 7a13d81..6bf04de 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClause.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClause.java
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
+import org.apache.camel.BeanScope;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.Expression;
@@ -248,6 +249,31 @@ public class ExpressionClause<T> implements Expression, Predicate {
      * Evaluates an expression using the <a href="http://camel.apache.org/bean-language.html">bean language</a> which
      * basically means the bean is invoked to determine the expression value.
      *
+     * @param  bean   the name of the bean looked up the registry
+     * @param  scope  the scope of the bean
+     * @return        the builder to continue processing the DSL
+     */
+    public T method(String bean, BeanScope scope) {
+        return delegate.method(bean, scope);
+    }
+
+    /**
+     * Evaluates an expression using the <a href="http://camel.apache.org/bean-language.html">bean language</a> which
+     * basically means the bean is invoked to determine the expression value.
+     *
+     * @param  bean   the name of the bean looked up the registry
+     * @param  method the name of the method to invoke on the bean
+     * @param  scope  the scope of the bean
+     * @return        the builder to continue processing the DSL
+     */
+    public T method(String bean, String method, BeanScope scope) {
+        return delegate.method(bean, method, scope);
+    }
+
+    /**
+     * Evaluates an expression using the <a href="http://camel.apache.org/bean-language.html">bean language</a> which
+     * basically means the bean is invoked to determine the expression value.
+     *
      * @param  instance the instance of the bean
      * @param  method   the name of the method to invoke on the bean
      * @return          the builder to continue processing the DSL
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
index 64efe5b..7577674 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
@@ -18,6 +18,7 @@ package org.apache.camel.builder;
 
 import java.util.Map;
 
+import org.apache.camel.BeanScope;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
 import org.apache.camel.ExpressionFactory;
@@ -210,6 +211,35 @@ public class ExpressionClauseSupport<T> {
      * Evaluates an expression using the <a href="http://camel.apache.org/bean-language.html>bean language</a> which
      * basically means the bean is invoked to determine the expression value.
      *
+     * @param  bean   the name of the bean looked up the registry
+     * @param  scope  the scope of the bean
+     * @return        the builder to continue processing the DSL
+     */
+    public T method(String bean, BeanScope scope) {
+        MethodCallExpression exp = new MethodCallExpression(bean);
+        exp.setScope(scope.name());
+        return expression(exp);
+    }
+
+    /**
+     * Evaluates an expression using the <a href="http://camel.apache.org/bean-language.html>bean language</a> which
+     * basically means the bean is invoked to determine the expression value.
+     *
+     * @param  bean   the name of the bean looked up the registry
+     * @param  method the name of the method to invoke on the bean
+     * @param  scope  the scope of the bean
+     * @return        the builder to continue processing the DSL
+     */
+    public T method(String bean, String method, BeanScope scope) {
+        MethodCallExpression exp = new MethodCallExpression(bean, method);
+        exp.setScope(scope.name());
+        return expression(exp);
+    }
+
+    /**
+     * Evaluates an expression using the <a href="http://camel.apache.org/bean-language.html>bean language</a> which
+     * basically means the bean is invoked to determine the expression value.
+     *
      * @param  instance the instance of the bean
      * @param  method   the name of the method to invoke on the bean
      * @return          the builder to continue processing the DSL
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/model/language/MethodCallExpression.java b/core/camel-core-engine/src/main/java/org/apache/camel/model/language/MethodCallExpression.java
index 4be866e..d357ab3 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/model/language/MethodCallExpression.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/model/language/MethodCallExpression.java
@@ -38,6 +38,9 @@ public class MethodCallExpression extends ExpressionDefinition {
     private String method;
     @XmlAttribute(name = "beanType")
     private String beanTypeName;
+    @XmlAttribute
+    @Metadata(defaultValue = "Singleton", enums = "Singleton,Request,Prototype")
+    private String scope;
     @XmlTransient
     private Class<?> beanType;
     @XmlTransient
@@ -130,6 +133,27 @@ public class MethodCallExpression extends ExpressionDefinition {
         this.beanTypeName = beanTypeName;
     }
 
+    public String getScope() {
+        return scope;
+    }
+
+    /**
+     * Scope of bean.
+     *
+     * When using singleton scope (default) the bean is created or looked up only once and reused for the lifetime of
+     * the endpoint. The bean should be thread-safe in case concurrent threads is calling the bean at the same time.
+     * When using request scope the bean is created or looked up once per request (exchange). This can be used if you
+     * want to store state on a bean while processing a request and you want to call the same bean instance multiple
+     * times while processing the request. The bean does not have to be thread-safe as the instance is only called from
+     * the same request. When using delegate scope, then the bean will be looked up or created per call. However in case
+     * of lookup then this is delegated to the bean registry such as Spring or CDI (if in use), which depends on their
+     * configuration can act as either singleton or prototype scope. so when using delegate then this depends on the
+     * delegated registry.
+     */
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
+
     public Object getInstance() {
         return instance;
     }
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/MethodCallExpressionReifier.java b/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/MethodCallExpressionReifier.java
index 011a3ec..288941c 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/MethodCallExpressionReifier.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/MethodCallExpressionReifier.java
@@ -31,11 +31,12 @@ public class MethodCallExpressionReifier extends ExpressionReifier<MethodCallExp
     }
 
     protected Object[] createProperties() {
-        Object[] properties = new Object[4];
+        Object[] properties = new Object[5];
         properties[0] = definition.getInstance();
         properties[1] = parseString(definition.getMethod());
         properties[2] = definition.getBeanType();
         properties[3] = parseString(definition.getRef());
+        properties[4] = parseString(definition.getScope());
         return properties;
     }
 
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index b7ba5f4..3361eab 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -16,10 +16,12 @@
  */
 package org.apache.camel.language.simple.ast;
 
+import java.net.URISyntaxException;
 import java.util.Map;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
+import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.language.simple.SimpleExpressionBuilder;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
@@ -28,6 +30,7 @@ import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.OgnlHelper;
 import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.URISupport;
 
 /**
  * Represents one of built-in functions of the <a href="http://camel.apache.org/simple.html">simple language</a>
@@ -188,7 +191,55 @@ public class SimpleFunctionExpression extends LiteralExpression {
         remainder = ifStartsWithReturnRemainder("bean:", function);
         if (remainder != null) {
             Language bean = camelContext.resolveLanguage("bean");
-            return bean.createExpression(remainder);
+            String ref = remainder;
+            Object method = null;
+            Object scope = null;
+
+            // we support different syntax for bean function
+            if (remainder.contains("?method=") || remainder.contains("?scope=")) {
+                ref = StringHelper.before(remainder, "?");
+                String query = StringHelper.after(remainder, "?");
+                try {
+                    Map<String, Object> map = URISupport.parseQuery(query);
+                    method = map.get("method");
+                    scope = map.get("scope");
+                } catch (URISyntaxException e) {
+                    throw RuntimeCamelException.wrapRuntimeException(e);
+                }
+            } else {
+                //first check case :: because of my.own.Bean::method
+                int doubleColonIndex = remainder.indexOf("::");
+                //need to check that not inside params
+                int beginOfParameterDeclaration = remainder.indexOf('(');
+                if (doubleColonIndex > 0 && (!remainder.contains("(") || doubleColonIndex < beginOfParameterDeclaration)) {
+                    ref = remainder.substring(0, doubleColonIndex);
+                    method = remainder.substring(doubleColonIndex + 2);
+                } else {
+                    int idx = remainder.indexOf('.');
+                    if (idx > 0) {
+                        ref = remainder.substring(0, idx);
+                        method = remainder.substring(idx + 1);
+                    }
+                }
+            }
+
+            Class<?> type = null;
+            if (ref != null && ref.startsWith("type:")) {
+                try {
+                    type = camelContext.getClassResolver().resolveMandatoryClass(ref.substring(5));
+                    ref = null;
+                } catch (ClassNotFoundException e) {
+                    throw RuntimeCamelException.wrapRuntimeException(e);
+                }
+            }
+
+            // there are parameters then map them into properties
+            Object[] properties = new Object[5];
+            properties[2] = type;
+            properties[3] = ref;
+            properties[1] = method;
+            properties[4] = scope;
+            return bean.createExpression(null, properties);
         }
 
         // properties: prefix
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallBeanFunctionScopeTest.java b/core/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallBeanFunctionScopeTest.java
new file mode 100644
index 0000000..3ddacb9
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallBeanFunctionScopeTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.component.bean;
+
+import org.apache.camel.BeanScope;
+import org.apache.camel.builder.RouteBuilder;
+
+public class MethodCallBeanFunctionScopeTest extends SimpleLanguageBeanFunctionScopeTest {
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:single").choice().when().method("foo", BeanScope.Singleton).to("mock:result")
+                        .otherwise().to("mock:other");
+
+                from("direct:proto").choice().when().method("foo", BeanScope.Prototype).to("mock:result")
+                        .otherwise().to("mock:other");
+
+                from("direct:request")
+                        .to("direct:sub")
+                        .to("direct:sub")
+                        .to("direct:sub");
+
+                from("direct:sub").choice().when().method("foo", BeanScope.Request).to("mock:result")
+                        .otherwise().to("mock:other");
+
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/bean/SimpleLanguageBeanFunctionScopeTest.java b/core/camel-core/src/test/java/org/apache/camel/component/bean/SimpleLanguageBeanFunctionScopeTest.java
new file mode 100644
index 0000000..0583db9
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/component/bean/SimpleLanguageBeanFunctionScopeTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.component.bean;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.support.SimpleRegistry;
+import org.junit.jupiter.api.Test;
+
+public class SimpleLanguageBeanFunctionScopeTest extends ContextTestSupport {
+
+    @Test
+    public void testSingleton() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("A", "B");
+        getMockEndpoint("mock:other").expectedBodiesReceived("C");
+
+        template.sendBody("direct:single", "A");
+        template.sendBody("direct:single", "B");
+        template.sendBody("direct:single", "C");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testPrototype() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("A", "B", "C");
+
+        template.sendBody("direct:proto", "A");
+        template.sendBody("direct:proto", "B");
+        template.sendBody("direct:proto", "C");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testRequest() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("A", "A", "B", "B", "C", "C");
+        getMockEndpoint("mock:other").expectedBodiesReceived("A", "B", "C");
+
+        template.sendBody("direct:request", "A");
+        template.sendBody("direct:request", "B");
+        template.sendBody("direct:request", "C");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected Registry createRegistry() throws Exception {
+        Registry wrapper = new SimpleRegistry() {
+            @Override
+            public Object lookupByName(String name) {
+                // create a new instance so its prototype scoped from the backing registry
+                if ("foo".equals(name)) {
+                    return new MyBean();
+                }
+                return super.lookupByName(name);
+            }
+        };
+        return wrapper;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:single").choice().when().simple("${bean:foo?scope=Singleton}").to("mock:result")
+                        .otherwise().to("mock:other");
+
+                from("direct:proto").choice().when().simple("${bean:foo?scope=Prototype}").to("mock:result")
+                        .otherwise().to("mock:other");
+
+                from("direct:request")
+                        .to("direct:sub")
+                        .to("direct:sub")
+                        .to("direct:sub");
+
+                from("direct:sub").choice().when().simple("${bean:foo?scope=Request}").to("mock:result")
+                        .otherwise().to("mock:other");
+
+            }
+        };
+    }
+
+    public static class MyBean {
+
+        private int counter;
+
+        public boolean bar(String body) {
+            return ++counter < 3;
+        }
+    }
+
+}
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index 7e99db4..29ad26b 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -2376,6 +2376,7 @@ public class ModelParser extends BaseParser {
                 case "beanType": def.setBeanTypeName(val); break;
                 case "method": def.setMethod(val); break;
                 case "ref": def.setRef(val); break;
+                case "scope": def.setScope(val); break;
                 default: return expressionDefinitionAttributeHandler().accept(def, key, val);
             }
             return true;
diff --git a/docs/components/modules/languages/pages/bean-language.adoc b/docs/components/modules/languages/pages/bean-language.adoc
index a3ff4da6..7fa8dd4 100644
--- a/docs/components/modules/languages/pages/bean-language.adoc
+++ b/docs/components/modules/languages/pages/bean-language.adoc
@@ -29,7 +29,7 @@ xref:xpath-language.adoc[XPath] or xref:xquery-language.adoc[XQuery] from the me
 == Bean Language options
 
 // language options: START
-The Bean method language supports 4 options, which are listed below.
+The Bean method language supports 5 options, which are listed below.
 
 
 
@@ -39,6 +39,7 @@ The Bean method language supports 4 options, which are listed below.
 | ref |  | String | Reference to bean to lookup in the registry
 | method |  | String | Name of method to call
 | beanType |  | String | Class name of the bean to use
+| scope | Singleton | String | Scope of bean. When using singleton scope (default) the bean is created or looked up only once and reused for the lifetime of the endpoint. The bean should be thread-safe in case concurrent threads is calling the bean at the same time. When using request scope the bean is created or looked up once per request (exchange). This can be used if you want to store state on a bean while processing a request and you want to call the same bean instance multiple time [...]
 | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks
 |===
 // language options: END
diff --git a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_6.adoc b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_6.adoc
index dbf815d..916c229 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_6.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-3x-upgrade-guide-3_6.adoc
@@ -28,6 +28,16 @@ The `contains` and `not contains` operator now handles numerics naturally. For e
 would return `false` if the numbers header has the value `-123`, as the operator now compares numerically. Previously it
 would compare with String based values.
 
+=== Bean component
+
+We have now implemented bean scope (singleton, request, and prototype) for all kinds of using the bean component, whether
+it's the bean endpoint, bean language (method call), bean EIP, or the bean function in the simple language (eg `${bean:foo}`).
+
+The default scope is now singleton. Previously the bean function in simple language would be prototype scope (always lookup the bean on each use).
+With singleton scope we improve the performance by avoiding the repetitive lookup of the same bean on each usage.
+
+You can use the old behaviour by setting the scope to prototype.
+
 === API components upgrade
 
 The `camel-braintree`, `camel-twilio` and `camel-zendesk` has updated to newer versions and regenerated their API