@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class GrailsBeansASTTransformation extends java.lang.Object implements ASTTransformation, CompilationUnitAware
Rewrites the beans closure DSL on a GrailsBeans-annotated
class into real @Bean factory methods, at compile time.
Recognises three kinds of top-level statement inside the beans closure:
bean(["name", ] Type) { ... }, optionally chained with any combination of
.conditionalOnMissingBean(...) (positional types, named annotation attributes, or bare),
.conditionalOnMissingBeanName(...) (backs off by this bean's own name, set
automatically), .primary(), .lazy(),
.scope("name"), .staticMethod() (a static factory method, for
BeanFactoryPostProcessor/BeanPostProcessor beans), and (repeatably)
.annotate(AnnotationType[, attr: value, ...]). Synthesises a public method, returning
the declared type, annotated @org.springframework.context.annotation.Bean("name") plus
whichever qualifiers were chained. Its parameters are always the DSL closure's own, annotations
included. Its body is the closure's, except where that body is empty - or the closure is omitted
altogether - in which case a new Type(...) call over those same parameters is synthesised
instead, leaving the compiler to select the constructor from their types exactly as it would for a
body written out by hand. The generated method's name is an implementation detail: it matches the
bean name when that is a usable Java identifier not already taken by an existing or generated
member, and falls back to a synthesized <type>$N name otherwise (a non-identifier name
like "my-service", a reserved keyword, or a collision - a bean named toString
never overrides Object.toString()) - Spring resolves the bean by its @Bean("name")
value either way, never by the method name. One bean name may be declared by several
bean(...) statements when every declaration carries its own discriminating condition
(see validateSharedBeanNames).field(["name", ] Type), optionally chained with .value(...) (config
injection: key + default, a bare key, or a verbatim placeholder/SpEL string) and/or (repeatably)
.annotate(AnnotationType[, attr: value, ...]). Declares a private field on the
generated class, for state shared across bean methods.method(["name", ] Type) { ... }, chainable with .annotate(...) only
(.value(...) is field-specific).
Declares a private helper method on the generated class, for logic shared across bean methods,
lifted from the DSL closure the same way bean(...) is.Fields and helper methods declared this way are ordinary private members of the generated
class - bean(...) closures reference them the same way a hand-written @Bean
method would reference a sibling field or method on its @Configuration class. The
beans property itself is removed so no closure survives into the compiled class.
When the annotated class extends grails.plugins.Plugin, the generated members land
on a new sibling class instead of on the plugin class itself - named by swapping a
*GrailsPlugin suffix for AutoConfiguration, or appending
AutoConfiguration otherwise, and placed in the plugin's own package unless
@GrailsBeans(autoConfigurationName = ...) names another. A Plugin subclass is
instantiated by DefaultGrailsPlugin via plain reflection, never as a Spring bean, so
it cannot carry @Bean methods or a meaningful @AutoConfiguration annotation
of its own.
@AutoConfiguration and every annotation that gates or configures it - the
@Conditional* family, @Import/@ImportAutoConfiguration/
@ImportResource, @ComponentScan, @EnableConfigurationProperties,
@PropertySource/@PropertySources, and
@AutoConfigureOrder/Before/After - found on the plugin class are moved
onto the generated sibling, since that is the only place any of them has any effect; annotations
outside that set can be named explicitly via @GrailsBeans(moveAnnotations = ...). This lets a
plugin author keep bean definitions in the familiar *GrailsPlugin.groovy file while
everything else about the plugin class - doWithApplicationContext, onChange,
watchedResources, etc. - continues to work exactly as it does today.
@CompileStatic/@GrailsCompileStatic on the plugin class is propagated to the
generated sibling. Since the sibling is created after Groovy schedules local annotation
transforms, this transformation invokes Groovy's static-compilation transform directly after
generating the sibling's members. This is the same approach used by other Grails AST transforms
that generate code after local transform discovery.
| Modifiers | Name | Description |
|---|---|---|
static java.lang.String |
RESOLVED_TARGET_DIRECTORY_METADATA |
Class-node metadata carrying the compilation's output directory, seeded by the global Grails transform. |
| Type Params | Return Type | Name and description |
|---|---|---|
|
public static boolean |
isUnitTest(ClassNode classNode)A class implementing the testing support's GrailsUnitTest trait, as every Grails
testing trait does. |
|
public static ClosureExpression |
movedInitializer(ClassNode classNode, FieldNode field)The closure Spock moved off field into one of its generated methods, left where it is;
null when there is none. |
|
public static void |
reclaimMovedInitializer(ClassNode classNode, PropertyNode beansProperty)Puts back a beans closure that Spock moved off its field. |
|
public static boolean |
reportSharedBeans(ClassNode classNode, SourceUnit source)Reports a Spock specification's @Shared beans block, which would otherwise be dropped
without a word: Spock renames a shared field and takes its property away before this runs, so
there is no beans property left to find. |
|
public void |
setCompilationUnit(CompilationUnit compilationUnit) |
|
public void |
visit(ASTNode[] nodes, SourceUnit source) |
|
public void |
visitClosureExpression(ClosureExpression expression) |
|
public void |
visitClosureExpression(ClosureExpression expression) |
|
public void |
visitClosureExpression(ClosureExpression expression) |
|
public void |
visitConstructorCallExpression(ConstructorCallExpression call) |
|
public void |
visitConstructorCallExpression(ConstructorCallExpression call) |
|
public void |
visitConstructorCallExpression(ConstructorCallExpression call) |
|
public void |
visitConstructorCallExpression(ConstructorCallExpression call) |
|
public void |
visitMethodCallExpression(MethodCallExpression call) |
|
public void |
visitMethodCallExpression(MethodCallExpression inner) |
|
public void |
visitPropertyExpression(PropertyExpression expression) |
|
public void |
visitVariableExpression(VariableExpression expression) |
| Methods inherited from class | Name |
|---|---|
class java.lang.Object |
java.lang.Object#equals(java.lang.Object), java.lang.Object#getClass(), java.lang.Object#hashCode(), java.lang.Object#notify(), java.lang.Object#notifyAll(), java.lang.Object#toString(), java.lang.Object#wait(), java.lang.Object#wait(long), java.lang.Object#wait(long, int) |
Class-node metadata carrying the compilation's output directory, seeded by the global Grails transform. It resolves the directory for Groovy-Eclipse, where the compiler configuration either has none or has one relative to the Eclipse project, and that resolution lives in grails-core - which this module cannot depend on.
A class implementing the testing support's GrailsUnitTest trait, as every Grails
testing trait does. Checked with ClassNode.implementsInterface, which follows every
super-interface and superclass, so a spec inheriting the trait from a base spec counts.
The closure Spock moved off field into one of its generated methods, left where it is;
null when there is none. For deciding whether a block is the DSL before claiming it.
Puts back a beans closure that Spock moved off its field. Spock compiles a
specification's field initializers into a generated method, as field = value
statements, before this transformation runs; left there, the block would be found empty here,
and the statement would outlive the field this removes. The statement is found by the field
it assigns, not by where Spock put it.
Reports a Spock specification's @Shared beans block, which would otherwise be dropped
without a word: Spock renames a shared field and takes its property away before this runs, so
there is no beans property left to find. Only a closure that declares beans is
reported; an unrelated shared beans field is not the DSL and is left alone.