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]