codeconsole opened a new pull request, #16134:
URL: https://github.com/apache/grails-core/pull/16134

   ## What
   
   Tag libraries are described as they are compiled, and that description 
resolves tag calls in code compiled afterwards. A call whose namespace and tag 
are known becomes a direct invocation instead of being dispatched through the 
metaclass, and nothing is installed onto a metaclass to make dispatch work.
   
   ### Defining tags
   
   ```groovy
   class GreetingTagLib {
       static namespace = 'greet'
   
       def hello(Map attrs) {
           out << "Hello ${attrs.name}"
       }
   
       def wrapped(Map attrs, Closure body) {
           out << '<div>' << body() << '</div>'
       }
   }
   ```
   
   Compiling this writes `META-INF/grails/taglibs/GreetingTagLib.properties` 
into the artifact. Descriptions from every jar on the classpath combine, so a 
plugin's tags are resolvable by an application depending on it with no extra 
build configuration. For an application's own tag libraries, 
`generateTagLibraryIndex` writes the description from `grails-app/taglib` 
before compilation, so tags an application declares resolve in the same 
compilation that defines them.
   
   ### Calling tags
   
   In a tag library or a controller:
   
   ```groovy
   class BookController {
       def index() {
           String markup = g.createLink(controller: 'book')   // compiled into 
a direct invocation
           String other  = greet.hello(name: 'Grails')        // likewise
       }
   }
   ```
   
   A tag no compiled tag library declares is reported as a compilation warning:
   
   ```
   No such tag [mesage] in namespace [g]. Known tags: createLink, message, ...
   ```
   
   `-Dgrails.views.gsp.strictTagChecking=true` turns that into an error.
   
   ### Deprecation
   
   Defining a tag as a `Closure` field now warns at compile time. It still 
works, but a closure carries no signature, so calls to it cannot be resolved 
when the caller is compiled:
   
   ```groovy
   // Deprecated
   Closure hello = { Map attrs -> out << "Hello ${attrs.name}" }
   
   // Compiled
   def hello(Map attrs) { out << "Hello ${attrs.name}" }
   ```
   
   ## Why
   
   Profiling a running application attributed roughly 25% of samples on a 
tag-heavy page to reflective and metaclass tag dispatch, and about 10% to 
`ExpandoMetaClass` read-lock contention. A tag invocation measured ~1.26µs 
against ~0.20µs for a `${}` expression and ~0.09µs for literal HTML.
   
   Every caller used to mutate its own `ExpandoMetaClass` the first time it 
used a tag; every namespace dispatcher was built with a metaclass carrying a 
method per tag; and plugin bootstrap installed every tag onto every tag 
library. None of that remains.
   
   Measured on a page performing 400 tag invocations, 8 concurrent, 105k warmup 
requests, same publish flow both sides:
   
   | | ms/req |
   |---|---|
   | `8.0.x` | 0.5213 |
   | this branch | 0.4699 |
   
   ## What is not rewritten
   
   Anything that cannot be resolved when compiled is dispatched exactly as 
before:
   
   - attributes assembled at runtime — `g.createLink(attrs)`
   - a namespace no compiled tag library declares, which is what keeps a tag 
library registered at runtime working
   - a tag declared by more than one tag library, since which one runs depends 
on registration order
   - a tag defined as a `Closure` field
   - a name something else in scope answers to — a local, parameter, field or 
getter called `g` is that thing
   - an unqualified call such as `message(code: 'x')`, since whether that name 
is a tag or a method of the calling class is decided where it is called
   
   Expressions in a GSP page are checked against the same descriptions, so a 
misspelled tag is reported, but they are **not** rewritten: a page still 
selects the tag by name as it renders. It no longer touches a metaclass to do 
so.
   
   ## Limitations
   
   - **`CompiledTagInvocation` resolves the tag library bean by name at 
runtime.** The descriptor records whether a tag is a method or a closure, which 
is enough to decide whether a call is bindable, but not signatures. Binding to 
a specific method would need a further schema revision.
   - **Unit testing support still installs tag methods onto metaclasses,** 
deliberately: tests call tag methods directly, and the installed methods 
substitute an empty body for a missing one, so `tagLib.someTag(attrs, null)` 
works. A running application does not depend on this.
   - **The performance figure comes from one machine** that showed thermal 
variance during the run. The direction was consistent across two independent 
measurement protocols; the exact percentage should not be treated as precise.
   - **Registering a tag library after startup** uses the descriptor supplied 
rather than the one recorded at compile time, so a reloaded class is described 
by what it now declares. That path is not covered by a test.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to