You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/08/06 19:22:05 UTC
(grails-core) branch fix/14914-component-scan created (now 5af734f65e)
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a change to branch fix/14914-component-scan
in repository https://gitbox.apache.org/repos/asf/grails-core.git
at 5af734f65e Configure generated applications for component scanning
This branch includes the following new commits:
new 5af734f65e Configure generated applications for component scanning
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(grails-core) 01/01: Configure generated applications for component scanning
Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch fix/14914-component-scan
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 5af734f65eb43855e1f5163f16b13c98dc28a47d
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Thu Aug 6 15:20:09 2026 -0400
Configure generated applications for component scanning
Add base-package Spring component scanning to Forge and profile
Application templates, with generated-source and runtime coverage.
Closes #14914
Assisted-by: opencode:gpt-5.6-sol
---
.../guide/conf/applicationClass/customizing.adoc | 4 +-
.../feature/lang/groovy/application.rocker.raw | 5 +-
.../feature/lang/GrailsApplicationSpec.groovy | 16 +++
.../org/grails/forge/create/CreateAppSpec.groovy | 141 +++++++++++++++++----
.../Application.groovy | 4 +-
.../Application.groovy | 4 +-
6 files changed, 143 insertions(+), 31 deletions(-)
diff --git a/grails-doc/src/en/guide/conf/applicationClass/customizing.adoc b/grails-doc/src/en/guide/conf/applicationClass/customizing.adoc
index 246639c6a6..48fa90a564 100644
--- a/grails-doc/src/en/guide/conf/applicationClass/customizing.adoc
+++ b/grails-doc/src/en/guide/conf/applicationClass/customizing.adoc
@@ -23,7 +23,9 @@ There are several ways in which you can customize the `Application` class.
=== Customizing Scanning
-By default Grails will scan all known source directories for controllers, domain class etc., however if there are packages in other JAR files you wish to scan you can do so by overriding the `packageNames()` method of the `Application` class:
+Newly generated Grails applications include `@ComponentScan(value = 'your.application.package')` on the `Application` class. This scans standard Spring components in the application's base package and its subpackages.
+
+Grails scans its known source directories separately for Grails artefacts such as controllers and domain classes. To add packages to that Grails artefact scan, override the `packageNames()` method of the `Application` class:
[source,groovy]
----
diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/lang/groovy/application.rocker.raw b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/lang/groovy/application.rocker.raw
index 3440e6dcc2..c4250ee5c8 100644
--- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/lang/groovy/application.rocker.raw
+++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/lang/groovy/application.rocker.raw
@@ -31,10 +31,12 @@ import groovy.transform.CompileStatic
import io.micronaut.spring.boot.starter.EnableMicronaut
}
+import org.springframework.context.annotation.ComponentScan
@if(features.contains("spring-boot-starter-security")) {
import org.springframework.context.annotation.Import
-
}
+
+
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
@if(applicationType.equals(ApplicationType.PLUGIN) || applicationType.equals(ApplicationType.WEB_PLUGIN)) {
@@ -46,6 +48,7 @@ import grails.plugins.metadata.PluginSource
@@PluginSource
}
@@CompileStatic
+@@ComponentScan(value = '@project.getPackageName()')
@if(features.contains("grails-micronaut")) {
@@EnableMicronaut
}
diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/lang/GrailsApplicationSpec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/lang/GrailsApplicationSpec.groovy
index 4c00384293..8a50293161 100644
--- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/lang/GrailsApplicationSpec.groovy
+++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/lang/GrailsApplicationSpec.groovy
@@ -42,6 +42,7 @@ class GrailsApplicationSpec extends BeanContextSpec implements CommandOutputFixt
output.containsKey("grails-app/init/example/grails/Application.${Language.GROOVY.extension}".toString())
def applicationGroovyFile = output.get("grails-app/init/example/grails/Application.${Language.GROOVY.extension}".toString())
applicationGroovyFile.contains("@CompileStatic")
+ applicationGroovyFile.contains("@ComponentScan(value = 'example.grails')")
!applicationGroovyFile.contains("@PluginSource")
where:
@@ -58,11 +59,26 @@ class GrailsApplicationSpec extends BeanContextSpec implements CommandOutputFixt
output.containsKey("grails-app/init/example/grails/Application.${Language.GROOVY.extension}".toString())
def applicationGroovyFile = output.get("grails-app/init/example/grails/Application.${Language.GROOVY.extension}".toString())
applicationGroovyFile.contains("@PluginSource")
+ applicationGroovyFile.contains("@ComponentScan(value = 'example.grails')")
where:
applicationType << [ApplicationType.PLUGIN, ApplicationType.WEB_PLUGIN]
}
+ void 'profile Application skeletons configure base-package component scanning'() {
+ given:
+ File profiles = new File('../../grails-profiles').canonicalFile
+
+ expect:
+ ['base', 'plugin'].every { String profile ->
+ File application = new File(profiles, "${profile}/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy")
+ assert application.file, "missing profile Application skeleton ${application}"
+ assert application.text.contains('import org.springframework.context.annotation.ComponentScan')
+ assert application.text.contains("@ComponentScan(value = '@grails.codegen.defaultPackage@')")
+ true
+ }
+ }
+
void "REST-API application should have ApplicationController"() {
when:
def output = generate(ApplicationType.REST_API)
diff --git a/grails-forge/test-core/src/test/groovy/org/grails/forge/create/CreateAppSpec.groovy b/grails-forge/test-core/src/test/groovy/org/grails/forge/create/CreateAppSpec.groovy
index 2637eb8398..e4dd8aba49 100644
--- a/grails-forge/test-core/src/test/groovy/org/grails/forge/create/CreateAppSpec.groovy
+++ b/grails-forge/test-core/src/test/groovy/org/grails/forge/create/CreateAppSpec.groovy
@@ -24,6 +24,7 @@ import org.grails.forge.application.OperatingSystem
import org.grails.forge.options.DevelopmentReloading
import org.grails.forge.options.JdkVersion
import org.grails.forge.utils.CommandSpec
+import spock.lang.Unroll
class CreateAppSpec extends CommandSpec {
@@ -60,72 +61,158 @@ class CreateAppSpec extends CommandSpec {
new File(dir, "grails-app/i18n").exists()
}
- void "test create-app creates a correct Application.groovy"() {
+ @Unroll
+ void "test create-app #applicationType creates a correct Application.groovy"() {
given:
- generateProject(OperatingSystem.MACOS_ARCH64, [], ApplicationType.DEFAULT_OPTION)
+ generateProject(OperatingSystem.MACOS_ARCH64, [], applicationType)
def applicationClassSourceFile = new File(dir, 'grails-app/init/example/grails/Application.groovy')
expect:
applicationClassSourceFile.exists()
- applicationClassSourceFile.text == '''\
+ applicationClassSourceFile.text == applicationSource.stripIndent(8)
+
+ where:
+ applicationType | applicationSource
+ ApplicationType.WEB | '''\
package example.grails
import groovy.transform.CompileStatic
+ import org.springframework.context.annotation.ComponentScan
+
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
@CompileStatic
+ @ComponentScan(value = 'example.grails')
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
}
- '''.stripIndent(8)
- }
+ '''
+ ApplicationType.REST_API | '''\
+ package example.grails
- void "test create-app with micronaut feature"() {
- given:
- // Micronaut features require JDK 25+ because micronaut-core's ScopedValues
- // references java.lang.ScopedValue.CallableOp (JEP 506, finalized in JDK 25).
- generateProject(OperatingSystem.MACOS_ARCH64, ['grails-micronaut'], ApplicationType.WEB,
- DevelopmentReloading.DEFAULT_OPTION, JdkVersion.JDK_25)
+ import groovy.transform.CompileStatic
- def gradleProperties = new File(dir, 'gradle.properties')
- def gradleBuildFile = new File(dir, 'build.gradle')
+ import org.springframework.context.annotation.ComponentScan
- expect:
- gradleProperties.exists()
- gradleProperties.text.contains('micronautPlatformVersion=4.10.16')
- gradleBuildFile.exists()
- gradleBuildFile.text.contains('implementation "org.apache.grails:grails-micronaut"')
- }
+ import grails.boot.GrailsApp
+ import grails.boot.config.GrailsAutoConfiguration
- void "test create-app web-plugin creates a correct Application.groovy"() {
- given:
- generateProject(OperatingSystem.MACOS_ARCH64, [], ApplicationType.WEB_PLUGIN)
+ @CompileStatic
+ @ComponentScan(value = 'example.grails')
+ class Application extends GrailsAutoConfiguration {
+ static void main(String[] args) {
+ GrailsApp.run(Application, args)
+ }
+ }
+ '''
+ ApplicationType.PLUGIN | '''\
+ package example.grails
- def applicationClassSourceFile = new File(dir, 'grails-app/init/example/grails/Application.groovy')
+ import groovy.transform.CompileStatic
- expect:
- applicationClassSourceFile.exists()
- applicationClassSourceFile.text == '''\
+ import org.springframework.context.annotation.ComponentScan
+
+ import grails.boot.GrailsApp
+ import grails.boot.config.GrailsAutoConfiguration
+ import grails.plugins.metadata.PluginSource
+
+ @PluginSource
+ @CompileStatic
+ @ComponentScan(value = 'example.grails')
+ class Application extends GrailsAutoConfiguration {
+ static void main(String[] args) {
+ GrailsApp.run(Application, args)
+ }
+ }
+ '''
+ ApplicationType.WEB_PLUGIN | '''\
package example.grails
import groovy.transform.CompileStatic
+ import org.springframework.context.annotation.ComponentScan
+
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import grails.plugins.metadata.PluginSource
@PluginSource
@CompileStatic
+ @ComponentScan(value = 'example.grails')
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
}
- '''.stripIndent(8)
+ '''
+ }
+
+ void "test generated application scans standard Spring components"() {
+ given:
+ generateProject(OperatingSystem.MACOS_ARCH64)
+ File componentSourceFile = new File(dir, 'src/main/groovy/example/grails/components/ScannedComponent.groovy')
+ componentSourceFile.parentFile.mkdirs()
+ componentSourceFile.text = '''\
+ package example.grails.components
+
+ import org.springframework.stereotype.Component
+
+ @Component
+ class ScannedComponent {
+ String value() {
+ 'scanned'
+ }
+ }
+ '''.stripIndent()
+ File componentSpecFile = new File(dir, 'src/test/groovy/example/grails/ComponentScanningSpec.groovy')
+ componentSpecFile.parentFile.mkdirs()
+ componentSpecFile.text = '''\
+ package example.grails
+
+ import example.grails.components.ScannedComponent
+ import grails.testing.mixin.integration.Integration
+ import org.springframework.beans.factory.annotation.Autowired
+ import spock.lang.Specification
+
+ @Integration
+ class ComponentScanningSpec extends Specification {
+
+ @Autowired
+ ScannedComponent scannedComponent
+
+ void "standard Spring component is injected"() {
+ expect:
+ scannedComponent.value() == 'scanned'
+ }
+ }
+ '''.stripIndent()
+
+ when:
+ String output = executeGradle('test', '--tests', 'example.grails.ComponentScanningSpec').output
+
+ then:
+ output.contains('BUILD SUCCESSFUL')
+ }
+
+ void "test create-app with micronaut feature"() {
+ given:
+ // Micronaut features require JDK 25+ because micronaut-core's ScopedValues
+ // references java.lang.ScopedValue.CallableOp (JEP 506, finalized in JDK 25).
+ generateProject(OperatingSystem.MACOS_ARCH64, ['grails-micronaut'], ApplicationType.WEB,
+ DevelopmentReloading.DEFAULT_OPTION, JdkVersion.JDK_25)
+
+ def gradleProperties = new File(dir, 'gradle.properties')
+ def gradleBuildFile = new File(dir, 'build.gradle')
+
+ expect:
+ gradleProperties.exists()
+ gradleProperties.text.contains('micronautPlatformVersion=4.10.16')
+ gradleBuildFile.exists()
+ gradleBuildFile.text.contains('implementation "org.apache.grails:grails-micronaut"')
}
@Override
diff --git a/grails-profiles/base/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy b/grails-profiles/base/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy
index 13dbec46be..18e9f76c39 100644
--- a/grails-profiles/base/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy
+++ b/grails-profiles/base/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy
@@ -2,12 +2,14 @@ package @grails.codegen.defaultPackage@
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
+import org.springframework.context.annotation.ComponentScan
import groovy.transform.CompileStatic
@CompileStatic
+@ComponentScan(value = '@grails.codegen.defaultPackage@')
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
-}
\ No newline at end of file
+}
diff --git a/grails-profiles/plugin/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy b/grails-profiles/plugin/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy
index 08c9b83087..34814b0f52 100644
--- a/grails-profiles/plugin/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy
+++ b/grails-profiles/plugin/skeleton/grails-app/init/@grails.codegen.defaultPackage.path@/Application.groovy
@@ -3,10 +3,12 @@ package @grails.codegen.defaultPackage@
import grails.boot.*
import grails.boot.config.GrailsAutoConfiguration
import grails.plugins.metadata.*
+import org.springframework.context.annotation.ComponentScan
@PluginSource
+@ComponentScan(value = '@grails.codegen.defaultPackage@')
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
-}
\ No newline at end of file
+}