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

davydotcom pushed a commit to branch feature/taglib-method-actions
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 78c0d84a3b5bf75b9e66fcb09f78d38fba7dacc4
Author: David Estes <[email protected]>
AuthorDate: Wed Feb 25 22:43:28 2026 -0500

    Optimize tag invocation dispatch with concurrent-safe method caching
    
    - add thread-safe ClassValue cache for invokable public tag methods by name
    - remove per-invocation getMethods scans in 
hasInvokableTagMethod/invokeTagMethod
    - optimize TagLibrary.propertyMissing by caching method fallback closures 
in non-dev mode
    - use resolved namespace for default-namespace fallback closures
    
    Co-Authored-By: Oz <[email protected]>
---
 .../groovy/org/grails/taglib/TagMethodInvoker.java | 34 +++++++++++++++-------
 .../main/groovy/grails/artefact/TagLibrary.groovy  | 11 ++++---
 2 files changed, 29 insertions(+), 16 deletions(-)

diff --git 
a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
 
b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
index cf19e2fb4b..4644417709 100644
--- 
a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
+++ 
b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
@@ -26,6 +26,7 @@ import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -34,6 +35,22 @@ import groovy.lang.GroovyObject;
 import groovy.lang.MissingMethodException;
 
 public final class TagMethodInvoker {
+    private static final ClassValue<Map<String, List<Method>>> 
INVOKABLE_METHODS_BY_NAME = new ClassValue<>() {
+        @Override
+        protected Map<String, List<Method>> computeValue(Class<?> type) {
+            Map<String, List<Method>> methodsByName = new HashMap<>();
+            for (Method method : type.getMethods()) {
+                if (isTagMethodCandidate(method)) {
+                    methodsByName.computeIfAbsent(method.getName(), ignored -> 
new ArrayList<>()).add(method);
+                }
+            }
+            Map<String, List<Method>> immutableMethodsByName = new 
HashMap<>(methodsByName.size());
+            for (Map.Entry<String, List<Method>> entry : 
methodsByName.entrySet()) {
+                immutableMethodsByName.put(entry.getKey(), 
Collections.unmodifiableList(entry.getValue()));
+            }
+            return Collections.unmodifiableMap(immutableMethodsByName);
+        }
+    };
     private TagMethodInvoker() {
     }
 
@@ -70,19 +87,16 @@ public final class TagMethodInvoker {
     }
 
     public static boolean hasInvokableTagMethod(GroovyObject tagLib, String 
tagName) {
-        for (Method method : tagLib.getClass().getMethods()) {
-            if (isTagMethodCandidate(method) && 
method.getName().equals(tagName)) {
-                return true;
-            }
-        }
-        return false;
+        List<Method> methods = 
INVOKABLE_METHODS_BY_NAME.get(tagLib.getClass()).get(tagName);
+        return methods != null && !methods.isEmpty();
     }
 
     public static Object invokeTagMethod(GroovyObject tagLib, String tagName, 
Map<?, ?> attrs, Closure<?> body) {
-        for (Method method : tagLib.getClass().getMethods()) {
-            if (!isTagMethodCandidate(method) || 
!method.getName().equals(tagName)) {
-                continue;
-            }
+        List<Method> methods = 
INVOKABLE_METHODS_BY_NAME.get(tagLib.getClass()).get(tagName);
+        if (methods == null) {
+            throw new MissingMethodException(tagName, tagLib.getClass(), new 
Object[] { attrs, body });
+        }
+        for (Method method : methods) {
             Object[] args = toMethodArguments(method, attrs, body);
             if (args != null) {
                 try {
diff --git 
a/grails-gsp/grails-web-taglib/src/main/groovy/grails/artefact/TagLibrary.groovy
 
b/grails-gsp/grails-web-taglib/src/main/groovy/grails/artefact/TagLibrary.groovy
index 74a02b72c3..ffe068ff0d 100644
--- 
a/grails-gsp/grails-web-taglib/src/main/groovy/grails/artefact/TagLibrary.groovy
+++ 
b/grails-gsp/grails-web-taglib/src/main/groovy/grails/artefact/TagLibrary.groovy
@@ -149,13 +149,13 @@ trait TagLibrary implements WebAttributes, 
ServletAttributes, TagLibraryInvoker
         }
         TagLibraryLookup gspTagLibraryLookup = getTagLibraryLookup()
         if (gspTagLibraryLookup != null) {
-            boolean methodTagFallback = false
 
             Object result = gspTagLibraryLookup.lookupNamespaceDispatcher(name)
             if (result == null) {
-                String namespace = getTaglibNamespace()
-                GroovyObject tagLibrary = 
gspTagLibraryLookup.lookupTagLibrary(namespace, name)
+                String resolvedNamespace = getTaglibNamespace()
+                GroovyObject tagLibrary = 
gspTagLibraryLookup.lookupTagLibrary(resolvedNamespace, name)
                 if (tagLibrary == null) {
+                    resolvedNamespace = TagOutput.DEFAULT_NAMESPACE
                     tagLibrary = 
gspTagLibraryLookup.lookupTagLibrary(TagOutput.DEFAULT_NAMESPACE, name)
                 }
 
@@ -164,8 +164,7 @@ trait TagLibrary implements WebAttributes, 
ServletAttributes, TagLibraryInvoker
                     if (tagProperty instanceof Closure) {
                         result = ((Closure<?>) tagProperty).clone()
                     } else if 
(TagMethodInvoker.hasInvokableTagMethod(tagLibrary, name)) {
-                        methodTagFallback = true
-                        final String currentNamespace = namespace
+                        final String currentNamespace = resolvedNamespace
                         result = { Map attrs = [:], Closure body = null ->
                             Object output = 
TagOutput.captureTagOutput(gspTagLibraryLookup, currentNamespace, name, attrs, 
body, OutputContextLookupHelper.lookupOutputContext())
                             boolean gspTagSyntaxCall = attrs instanceof 
GroovyPageAttributes && ((GroovyPageAttributes) attrs).isGspTagSyntaxCall()
@@ -179,7 +178,7 @@ trait TagLibrary implements WebAttributes, 
ServletAttributes, TagLibraryInvoker
                     }
                 }
             }
-            if (result != null && !Environment.isDevelopmentMode() && 
!methodTagFallback) {
+            if (result != null && !Environment.isDevelopmentMode()) {
                 MetaClass mc = 
GrailsMetaClassUtils.getExpandoMetaClass(getClass())
 
                 // Register the property for the already-existing singleton 
instance of the taglib

Reply via email to