You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@openwebbeans.apache.org by "Thomas Andraschko (Jira)" <ji...@apache.org> on 2026/07/06 14:45:00 UTC

[jira] [Commented] (OWB-1464) Improvide WebBeansELResolver#findDottedName

    [ https://issues.apache.org/jira/browse/OWB-1464?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18094032#comment-18094032 ] 

Thomas Andraschko commented on OWB-1464:
----------------------------------------

Subject: [PATCH] Improve performance
---
Index: webbeans-el22/src/main/java/org/apache/webbeans/el22/WebBeansELResolver.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/webbeans-el22/src/main/java/org/apache/webbeans/el22/WebBeansELResolver.java b/webbeans-el22/src/main/java/org/apache/webbeans/el22/WebBeansELResolver.java
--- a/webbeans-el22/src/main/java/org/apache/webbeans/el22/WebBeansELResolver.java	(revision aa59630b4458f8aff9d50c375279935e45f69a02)
+++ b/webbeans-el22/src/main/java/org/apache/webbeans/el22/WebBeansELResolver.java	(date 1783349005867)
@@ -30,9 +30,13 @@
 import org.apache.webbeans.el.ELContextStore;
 
 import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.stream.Collectors;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 
 /**
@@ -55,12 +59,28 @@
 public class WebBeansELResolver extends ELResolver
 {
     private final WebBeansContext webBeansContext;
-    private Set<String> dotNamedBeansNegativeCache;
+
+    /**
+     * beanName (or dotted-prefix) -> {@code true}, for names which are known to never resolve
+     * to a dot-named {@link Bean}. Backed by a {@link ConcurrentHashMap} instead of a
+     * {@link java.util.concurrent.CopyOnWriteArraySet} because misses can be caused by
+     * arbitrary/high-cardinality EL identifiers (e.g. per-row expressions), which would
+     * otherwise degrade to an O(n) array copy on every single new entry.
+     */
+    private final Map<String, Boolean> dotNamedBeansNegativeCache = new ConcurrentHashMap<>();
+
+    /**
+     * Lazily built, immutable-after-publish index of all {@link Bean}s whose name contains a dot,
+     * keyed by their full name. Only such beans can ever be candidates for
+     * {@link #findDottedName(ELContext, Object, BeanManagerImpl, ELContextStore, String)}, so
+     * indexing just this (usually tiny or empty) subset avoids a linear scan over
+     * <b>all</b> managed beans on every EL root-property access.
+     */
+    private volatile NavigableMap<String, Set<Bean<?>>> dottedBeanNameIndex;
 
     public WebBeansELResolver()
     {
         webBeansContext = WebBeansContext.getInstance();
-        dotNamedBeansNegativeCache = new CopyOnWriteArraySet<>();
     }
     
     /**
@@ -160,20 +180,17 @@
                                   final ELContextStore elContextStore, final String beanName)
     {
         final String fqBeanName = base == null ? beanName : base + "." + beanName;
-        if (dotNamedBeansNegativeCache.contains(fqBeanName))
+        if (dotNamedBeansNegativeCache.containsKey(fqBeanName))
         {
             return null;
         }
 
-        final Set<Bean<?>> anyBeanName = beanManager.getBeans().stream()
-                .filter(b -> b.getName() != null)
-                .filter(b -> b.getName().equals(fqBeanName) || b.getName().startsWith(fqBeanName + "."))
-                .collect(Collectors.toSet());
+        final Set<Bean<?>> anyBeanName = getDottedBeanCandidates(beanManager, fqBeanName);
 
         // no exact and no startsWith match
         if (anyBeanName.isEmpty())
         {
-            dotNamedBeansNegativeCache.add(fqBeanName);
+            dotNamedBeansNegativeCache.put(fqBeanName, Boolean.TRUE);
             return null;
         }
 
@@ -188,6 +205,77 @@
         return new WrappedValueExpressionNode(fqBeanName);
     }
 
+    /**
+     * Returns all beans whose name is either exactly {@code fqBeanName} or starts with
+     * {@code fqBeanName + "."}, using a sorted index over dot-named beans only, instead of
+     * scanning every managed bean.
+     */
+    private Set<Bean<?>> getDottedBeanCandidates(final BeanManagerImpl beanManager, final String fqBeanName)
+    {
+        NavigableMap<String, Set<Bean<?>>> index = getDottedBeanNameIndex(beanManager);
+        if (index.isEmpty())
+        {
+            return Collections.emptySet();
+        }
+
+        String prefix = fqBeanName + '.';
+        Set<Bean<?>> result = null;
+
+        Set<Bean<?>> exact = index.get(fqBeanName);
+        if (exact != null)
+        {
+            result = new HashSet<>(exact);
+        }
+
+        for (Map.Entry<String, Set<Bean<?>>> entry : index.tailMap(prefix, true).entrySet())
+        {
+            if (!entry.getKey().startsWith(prefix))
+            {
+                break;
+            }
+            if (result == null)
+            {
+                result = new HashSet<>();
+            }
+            result.addAll(entry.getValue());
+        }
+
+        return result == null ? Collections.emptySet() : result;
+    }
+
+    /**
+     * Lazily builds (once) an index of all beans with a dot in their name, sorted by name so that
+     * prefix-matches can be found via a small {@link NavigableMap#tailMap(Object, boolean)} range
+     * instead of iterating over every bean in the application. This mirrors the permanent-cache
+     * assumption already used by {@link #dotNamedBeansNegativeCache}: once the container is up and
+     * serving EL expressions, the deployed bean set is stable.
+     */
+    private NavigableMap<String, Set<Bean<?>>> getDottedBeanNameIndex(final BeanManagerImpl beanManager)
+    {
+        NavigableMap<String, Set<Bean<?>>> index = dottedBeanNameIndex;
+        if (index == null)
+        {
+            synchronized (this)
+            {
+                index = dottedBeanNameIndex;
+                if (index == null)
+                {
+                    index = new TreeMap<>();
+                    for (Bean<?> bean : beanManager.getBeans())
+                    {
+                        final String name = bean.getName();
+                        if (name != null && name.indexOf('.') >= 0)
+                        {
+                            index.computeIfAbsent(name, k -> new HashSet<>()).add(bean);
+                        }
+                    }
+                    dottedBeanNameIndex = index;
+                }
+            }
+        }
+        return index;
+    }
+
     protected Object getNormalScopedContextualInstance(BeanManagerImpl manager, ELContextStore store, ELContext context,
                                                        Bean<?> bean, String beanName)
     {


> Improvide WebBeansELResolver#findDottedName
> -------------------------------------------
>
>                 Key: OWB-1464
>                 URL: https://issues.apache.org/jira/browse/OWB-1464
>             Project: OpenWebBeans
>          Issue Type: Improvement
>            Reporter: Thomas Andraschko
>            Priority: Major
>             Fix For: 4.1.1
>
>
> Old:
> findDottedName()-only: 20000 beans, 20000 distinct lookups => 16283 ms (814150.0 ns/lookup)
> New:
> findDottedName()-only: 20000 beans, 20000 distinct lookups => 19 ms (950.0 ns/lookup)



--
This message was sent by Atlassian Jira
(v8.20.10#820010)