Compare commits

..
64 Commits
Author SHA1 Message Date
Brian Clozel 1195bbd50d Release v6.1.18 2025-03-13 08:29:08 +01:00
Juergen Hoeller 9ea049ad6f Upgrade to Reactor 2023.0.16
Includes Netty 4.1.119 and Jetty 12.0.17

Closes gh-34579
2025-03-12 13:28:03 +01:00
Juergen Hoeller 1e31cd685e Avoid getTargetConnection call on transaction-aware Connection close
Closes gh-34484

(cherry picked from commit c64dae3623)
2025-02-28 14:21:55 +01:00
Juergen Hoeller 0d60f266ad Defensively call isShutdown method for executor description
Closes gh-34514

(cherry picked from commit 559ea6c480)
2025-02-28 14:21:41 +01:00
Juergen Hoeller a018ae6e02 Lenient fallback when cached WeakReference returns null
Closes gh-34423
2025-02-26 10:25:07 +01:00
Juergen Hoeller ea419d2dcd Avoid unnecessary CGLIB processing on configuration classes
Closes gh-34486

(cherry picked from commit aff9ac72ec)
2025-02-25 18:42:18 +01:00
Brian Clozel c02d07fdef Fix multiple Content-Language values in MockHttpServletResponse
Prior to this commit, `MockHttpServletResponse` would only support
adding a `Content-Language` once. Adding multiple header values would
always replace the content-language property in the response and the
entire header value.

This commit ensures that this behavior is supported.

Fixes gh-34491
2025-02-25 17:57:27 +01:00
Juergen Hoeller 7460be617b Consistent default ClassLoader fallback in hint classes
Closes gh-34470
2025-02-23 15:23:51 +01:00
Brian Clozel ccf4b028d2 Fix null value support in ContentCachingResponseWrapper
Prior to this commit, calling `setHeader` on the response wrapper would
have a separate code path for the "Content-Length" header. This did not
support calls with `null` values and would result in an exception.

This commit ensures that the cached content length value is reset in
this case and that the call is forwarded properly to the superclass.

Fixes gh-34465
2025-02-21 14:36:09 +01:00
Brian Clozel b5c89c91a9 Handle null values in MockHttpServletResponse#setHeader
Prior to this commit, `MockHttpServletResponse#setHeader` would not
remove the header entry when given a `null` value, as documented in the
Servlet API.
This commit ensures that this behavior is enforced.

Fixes gh-34466
2025-02-21 14:35:37 +01:00
Sébastien Deleuze 01ae0a7c26 Fix broken antora task
See https://github.com/spring-io/antora-extensions/pull/43

Closes gh-34455
2025-02-19 16:45:08 +01:00
Juergen Hoeller b48d72cbd0 Apply fallback in case of initial SmartClassLoader mismatch as well
See gh-34423

(cherry picked from commit 6786e1c3e5)
2025-02-18 20:53:21 +01:00
Juergen Hoeller 48628194dc Apply fallback in case of any exception coming out of createClass
Closes gh-34423

(cherry picked from commit 93134fd4d1)
2025-02-18 15:41:38 +01:00
Juergen Hoeller 3cf1fbf222 Mark XML-configured executor/scheduler as infrastructure bean
Closes gh-34015

(cherry picked from commit d0ceefedc6)
2025-02-18 15:41:17 +01:00
Stéphane Nicoll ecd943c7ec Next development version (v6.1.18-SNAPSHOT) 2025-02-13 13:23:32 +01:00
Juergen Hoeller 9d6d67188a Upgrade to Jetty 12.0.16, Netty 4.1.118, XStream 1.4.21, AssertJ 3.27.3, Checkstyle 10.21.2 2025-02-11 22:47:24 +01:00
Juergen Hoeller 24fd0940bd Align with SmartClassLoader handling for AOP proxy classes
Closes gh-34274

(cherry picked from commit f53da04717)
2025-02-11 22:19:43 +01:00
Sam Brannen 5b928f47a8 Finish incomplete sentences
(cherry picked from commit 9d3374b28d)
2025-02-11 12:32:27 +01:00
Stéphane Nicoll 5e52baee86 Upgrade to Reactor 2023.0.15
Closes gh-34406
2025-02-11 11:44:35 +01:00
Stéphane Nicoll c10596a1fd Upgrade to RSocket 1.1.5
Closes gh-34405
2025-02-11 11:43:04 +01:00
Sam Brannen a68b7289f0 Improve warning for unexpected use of value attribute as @⁠Component name
Prior to this commit, if a String 'value' attribute of an annotation
was annotated with @⁠AliasFor and explicitly configured to alias an
attribute other than @⁠Component.value, the value was still used as the
@⁠Component name, but the warning message that was logged stated that
the 'value' attribute should be annotated with
@⁠AliasFor(annotation=Component.class). However, it is not possible to
annotate an annotation attribute twice with @⁠AliasFor.

To address that, this commit revises the logic in
AnnotationBeanNameGenerator so that it issues a log message similar to
the following in such scenarios.

WARN o.s.c.a.AnnotationBeanNameGenerator - Although the 'value'
attribute in @⁠example.MyStereotype declares @⁠AliasFor for an
attribute other than @⁠Component's 'value' attribute, the value is
still used as the @⁠Component name based on convention. As of Spring
Framework 7.0, such a 'value' attribute will no longer be used as the
@⁠Component name.

See gh-34346
Closes gh-34317

(cherry picked from commit 17a94fb110)
2025-02-10 13:32:28 +01:00
Sam Brannen a5b9e667e3 Polishing
(cherry picked from commit 2fcae65853)
2025-02-10 13:32:20 +01:00
Branden Clark 78fe55f4d1 Check hasNext on sessionIds in UserDestinationResult
Closes gh-34333

Signed-off-by: Branden Clark <brandenrayclark@gmail.com>
2025-02-10 11:22:28 +00:00
Brian Clozel 65913c3655 Fix "Nth day of week" Quartz-style cron expressions
Prior to this commit, `CronExpression` would support Quartz-style
expressions with "Nth occurence of a  dayOfWeek" semantics by using the
`TemporalAdjusters.dayOfWeekInMonth` JDK support. This method will
return the Nth occurence starting with the month of the given temporal,
but in some cases will overflow to the next or previous month.
This behavior is not expected for our cron expression support.

This commit ensures that when an overflow happens (meaning, the
resulting date is not in the same month as the input temporal), we
should instead have another attempt at finding a valid month for this
expression.

Fixes gh-34377
2025-02-06 18:34:24 +01:00
Sam Brannen b7a996a64b Clarify component scanning of abstract classes with @⁠Lookup methods
Due to changes in gh-19118, classes that contain @⁠Lookup methods are
no longer required to be concrete classes for use with component
scanning; however, the reference documentation still states that such
classes must not be abstract.

This commit therefore removes the outdated reference documentation and
updates the corresponding Javadoc.

See gh-19118
Closes gh-34367

(cherry picked from commit 819a7c86c1)
2025-02-05 13:44:28 +01:00
Stéphane Nicoll 5b1a7c7f21 Handle arbitrary JoinPoint argument index
Closes gh-34369
2025-02-05 11:55:11 +01:00
Brian Clozel ebd80bdd6c Delete failing Freemarker test
This test was already ignored as of Java 21 because of a Java behavior
change, and now it started failing as of 17.0.14.
This commit removes the test entirely.
2025-02-04 10:03:50 +01:00
Juergen Hoeller a4fc68b8e8 Explicitly set custom ClassLoader on CGLIB Enhancer
Closes gh-34274

(cherry picked from commit 1b18928bf0)
2025-02-03 15:37:04 +01:00
Sam Brannen c333946b0b Restore property binding support for a Map that implements Iterable
The changes in commit c20a2e4763 introduced a regression with regard to
binding to a Map property when the Map also happens to implement
Iterable.

Although that is perhaps not a very common scenario, this commit
reorders the if-blocks in AbstractNestablePropertyAccessor's
getPropertyValue(PropertyTokenHolder) method so that a Map is
considered before an Iterable, thereby allowing an Iterable-Map to be
accessed as a Map.

See gh-907
Closes gh-34332

(cherry picked from commit b9e43d05bd)
2025-01-29 17:46:19 +01:00
rstoyanchev b0a8a3ec5f Enhance DisconnectedClientHelper exception type checks
We now look for the target exception types in cause chain as well,
but return false if we encounter a RestClient or WebClient
exception in the chain.

Closes gh-34264
2025-01-21 12:25:45 +00:00
rstoyanchev bb5be2119c Restore exception phrase in DisconnectedClientHelper
Effectively revert 203fa7, and add implementation comment and tests.

See gh-34264
2025-01-21 12:25:35 +00:00
Brian Clozel cf662368a5 Fix Wrong parentId tracking in JFR application startup
This commit fixes the tracking of the main event parentId for the Java
Flight Recorder implementation variant.

Fixes gh-34254
2025-01-13 20:22:07 +01:00
rstoyanchev e1b06ccfaa Improve logging in ReactiveTypeHandler
See gh-34188
2025-01-06 12:33:38 +00:00
rstoyanchev 9eefdb8041 Synchronize in WebAsyncManager onError/onTimeout
On connection loss, in a race between application thread and onError
callback trying to set the DeferredResult and dispatch, the onError
callback must not exit until dispatch completes. Currently, it may do
so because the DeferredResult has checks to bypasses locking or even
trying to dispatch if result is already set.

Closes gh-34192
2025-01-06 12:33:17 +00:00
rstoyanchev c50cb10964 Minor refactoring in WebAsyncManager
There is no need to set the DeferredResult from WebAsyncManager in an
onError notification because it is already done from the Lifecycle
interceptor in DeferredResult.

See gh-34192
2025-01-06 12:33:02 +00:00
rstoyanchev 245341231f Polishing in WebAsyncManager
See gh-34192
2025-01-06 12:32:40 +00:00
Sébastien Deleuze 875cc828cf Upgrade to Java 17.0.13 2024-12-30 12:14:52 +01:00
Stéphane Nicoll 898d3ec86a Backport tests for exact match resolution
See gh-34124
2024-12-23 12:15:44 +01:00
Tran Ngoc Nhan 8ccaabe778 Fix broken links in the web reference documentation
Backport of gh-34115.

Closes gh-34139
2024-12-23 11:22:17 +01:00
Sam Brannen 9c934b5019 Support varargs-only MethodHandle as SpEL function
Prior to this commit, if a MethodHandle was registered as a custom
function in the Spring Expression Language (SpEL) for a static method
that accepted only a variable argument list (for example,
`static String func(String... args)`), attempting to invoke the
registered function within a SpEL expression resulted in a
ClassCastException because the varargs array was unnecessarily wrapped
in an Object[].

This commit modifies the logic in FunctionReference's internal
executeFunctionViaMethodHandle() method to address that.

Closes gh-34109
2024-12-18 15:34:27 +01:00
Brian Clozel 4b45338ae6 Improve PathMatcher/PatternParser XML configuration
Prior to this commit, the MVC namespace for the XML Spring configuration
model would use the `PathMatcher` bean instance when provided like this:

```
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"/>
<mvc:annotation-driven>
  <mvc:path-matching path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<mvc:resources mapping="/resources/**" location="classpath:/static/"/>
```

With this configuration, the handler mapping for annotated controller
would use the given `AntPathMatcher` instance but the handler mapping
for resources would still use the default, which is `PathPatternParser`
since 6.0.

This commit ensures that when a custom `path-matcher` is defined, it's
consistently used for all MVC handler mappings as an alias to the
well-known bean name. This allows to use `AntPathMatcher` consistently
while working on a migration path to `PathPatternParser`

This commit also adds a new XML attribute to the path matching
configuration that makes it possible to use a custom `PathPatternParser`
instance:

```
<bean id="patternParser" class="org.springframework.web.util.pattern.PathPatternParser"/>
<mvc:annotation-driven>
  <mvc:path-matching pattern-parser="patternParser"/>
</mvc:annotation-driven>
```

Closes gh-34102
See gh-34064
2024-12-17 10:30:18 +01:00
Juergen Hoeller 95003e3512 Avoid logger serialization behind shared EntityManager proxy
See gh-34084
2024-12-12 19:52:21 +01:00
Stéphane Nicoll 68368621f3 Prevent execution of Antora jobs on forks
Closes gh-34083
2024-12-12 17:39:59 +01:00
Brian Clozel 890a8f4311 Next development version (v6.1.17-SNAPSHOT) 2024-12-12 10:04:11 +01:00
Juergen Hoeller 0976d1a252 Upgrade to RxJava 3.1.10 and Checkstyle 10.20.2 2024-12-11 17:49:18 +01:00
Juergen Hoeller 2c0ce790d8 Avoid deprecated ListenableFuture name for internal class 2024-12-11 17:47:11 +01:00
Juergen Hoeller d512aafc36 Avoid javadoc references to deprecated types/methods
(cherry picked from commit 68997d8416)
2024-12-11 17:47:03 +01:00
Sam Brannen faa000f330 Log alias removal in DefaultListableBeanFactory
Prior to this commit, information was logged when a bean definition
overrode an existing bean definition, but nothing was logged when the
registration of a bean definition resulted in the removal of an alias.

With this commit, an INFO message is now logged whenever an alias is
removed in DefaultListableBeanFactory.

Closes gh-34070

(cherry picked from commit 41d9f21ab9)
2024-12-11 15:05:42 +01:00
Juergen Hoeller daf1b3d7a7 Remove unnecessary downcast to DefaultListableBeanFactory
See gh-33920
See gh-25952
2024-12-10 22:33:01 +01:00
Juergen Hoeller a48897a241 Log provider setup failure at info level without stacktrace
Closes gh-33979

(cherry picked from commit 3e3ca74020)
2024-12-10 22:28:38 +01:00
Stéphane Nicoll 7ffa12f90f Upgrade to Reactor 2023.0.13
Closes gh-34049
2024-12-10 14:09:57 +01:00
Stéphane Nicoll 0e74ffa144 Start building against Reactor 2023.0.13 snapshots
See gh-34049
2024-12-08 10:47:15 +01:00
Juergen Hoeller 357195289d Unit test for match against unresolvable wildcard
See gh-33982
2024-12-05 17:53:30 +01:00
Sébastien Deleuze 4fbca99f20 Remove unnecessary HandshakeHandlerRuntimeHints
Those hints are not needed anymore as of Spring Framework 6.1.
Backport of gh-34032.

Closes gh-34033
2024-12-05 15:46:31 +01:00
Juergen Hoeller c48ca5151f Upgrade to Gradle 8.11.1 2024-12-04 17:36:11 +01:00
Juergen Hoeller aaf2e8fbe6 Polishing
(cherry picked from commit edf7f3cd43)
2024-12-04 17:35:50 +01:00
Juergen Hoeller 3635ff0c17 Consistent fallback to NoUpgradeStrategyWebSocketService
Closes gh-33970

(cherry picked from commit 58c64cba2c)
2024-12-04 16:55:51 +01:00
Simon Baslé f20e76e227 Upgrade to Undertow 2.3.18.Final
Closes gh-33976
2024-11-27 14:31:46 +01:00
Simon Baslé dd32f95192 Dispatch in UndertowHttpHandlerAdapter
This ensures that the reactive handling of the request is dispatched
from the Undertow IO thread, marking the exchange as async rather than
ending it once the Undertow `handleRequest` method returns.

See gh-33885
Closes gh-33969
2024-11-26 16:58:21 +01:00
Tran Ngoc Nhan 75a920bc9f Fix a typo in the filters documentation
Backport of gh-33959
Closes gh-33971
2024-11-26 16:17:18 +01:00
Sam Brannen 92874adae9 Polish SpEL documentation
(cherry picked from commit 7196f3f554)
2024-11-18 11:43:14 +01:00
Sam Brannen 55167b7f50 Fix SpEL examples in reference guide
Closes gh-33907

(cherry picked from commit a12d40e10b)
2024-11-18 11:43:05 +01:00
Sam Brannen 3129afba19 Upgrade to Gradle 8.11
Closes gh-33895

(cherry picked from commit bb32df0a06)
2024-11-15 16:08:05 +01:00
Sam Brannen 1366d2926b Upgrade to antora-ui-spring version 0.4.18
Closes gh-33892

(cherry picked from commit fe8573c0ab)
2024-11-15 14:34:00 +01:00
10520 changed files with 131841 additions and 285741 deletions
+17
View File
@@ -0,0 +1,17 @@
<!--
!!! For Security Vulnerabilities, please go to https://spring.io/security-policy !!!
-->
**Affects:** \<Spring Framework version>
---
<!--
Thanks for taking the time to create an issue. Please read the following:
- Questions should be asked on Stack Overflow.
- For bugs, specify affected versions and explain what you are trying to do.
- For enhancements, provide context and describe the problem.
Issue or Pull Request? Create only one, not both. GitHub treats them as the same.
If unsure, start with an issue, and if you submit a pull request later, the
issue will be closed as superseded.
-->
-6
View File
@@ -1,6 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Community Support
url: https://stackoverflow.com/tags/spring
about: Please ask and answer questions on StackOverflow with the tag `spring`.
-23
View File
@@ -1,23 +0,0 @@
---
name: General
about: Bugs, enhancements, documentation, tasks.
title: ''
labels: ''
assignees: ''
---
<!--
Have you considered asking for help on stackoverflow.com?
** Bug Reports **
Please submit issues against OSS supported versions, see https://spring.io/projects/spring-framework#support
Please provide a minimal sample application that reproduces the problem for faster issue triage.
** Enhancements requests **
Before explaining how you would like things to work,
please describe a concrete use case for this feature and how you have tried to solve this so far.
-->
@@ -0,0 +1,20 @@
name: Await HTTP Resource
description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)'
inputs:
url:
description: 'URL of the resource to await'
required: true
runs:
using: composite
steps:
- name: Await HTTP resource
shell: bash
run: |
url=${{ inputs.url }}
echo "Waiting for $url"
until curl --fail --head --silent ${{ inputs.url }} > /dev/null
do
echo "."
sleep 60
done
echo "$url is available"
+1 -23
View File
@@ -1,18 +1,6 @@
name: 'Build'
description: 'Builds the project, optionally publishing it to a local deployment repository'
inputs:
commercial-release-repository-url:
description: 'URL of the release repository'
required: false
commercial-repository-password:
description: 'Password for authentication with the commercial repository'
required: false
commercial-repository-username:
description: 'Username for authentication with the commercial repository'
required: false
commercial-snapshot-repository-url:
description: 'URL of the snapshot repository'
required: false
develocity-access-key:
description: 'Access key for authentication with ge.spring.io'
required: false
@@ -31,7 +19,7 @@ inputs:
java-version:
description: 'Java version to compile and test with'
required: false
default: '25'
default: '17'
publish:
description: 'Whether to publish artifacts ready for deployment to Artifactory'
required: false
@@ -58,21 +46,11 @@ runs:
id: build
if: ${{ inputs.publish == 'false' }}
shell: bash
env:
COMMERCIAL_RELEASE_REPO_URL: ${{ inputs.commercial-release-repository-url }}
COMMERCIAL_REPO_PASSWORD: ${{ inputs.commercial-repository-password }}
COMMERCIAL_REPO_USERNAME: ${{ inputs.commercial-repository-username }}
COMMERCIAL_SNAPSHOT_REPO_URL: ${{ inputs.commercial-snapshot-repository-url }}
run: ./gradlew check antora
- name: Publish
id: publish
if: ${{ inputs.publish == 'true' }}
shell: bash
env:
COMMERCIAL_RELEASE_REPO_URL: ${{ inputs.commercial-release-repository-url }}
COMMERCIAL_REPO_PASSWORD: ${{ inputs.commercial-repository-password }}
COMMERCIAL_REPO_USERNAME: ${{ inputs.commercial-repository-username }}
COMMERCIAL_SNAPSHOT_REPO_URL: ${{ inputs.commercial-snapshot-repository-url }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Read Version From gradle.properties
id: read-version
@@ -1,20 +1,9 @@
name: Create GitHub Release
description: 'Create the release on GitHub with a changelog'
inputs:
commercial:
description: 'Whether to generate the changelog for the commercial release'
required: true
latest:
description: 'Whether the release is the latest release'
required: false
default: 'false'
milestone:
description: 'Name of the GitHub milestone for which a release will be created'
required: true
pre-release:
description: 'Whether the release is a pre-release (a milestone or release candidate)'
required: false
default: 'false'
token:
description: 'Token to use for authentication with GitHub'
required: true
@@ -22,13 +11,13 @@ runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@f7d7a87a3e7c627ecb8c26cf086c38ac5a939721 #v0.0.14
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
with:
config-file: ${{ inputs.commercial && '.github/actions/create-github-release/changelog-generator-commercial.yml' || '.github/actions/create-github-release/changelog-generator-oss.yml' }}
config-file: .github/actions/create-github-release/changelog-generator.yml
milestone: ${{ inputs.milestone }}
token: ${{ inputs.token }}
- name: Create GitHub Release
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md ${{ inputs.pre-release == 'true' && '--prerelease' || format('--latest={0}', inputs.latest) }}
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
@@ -1,33 +0,0 @@
changelog:
repository: spring-projects/spring-framework-commercial
sections:
- title: ":warning: Attention Required"
labels:
- "for: upgrade-attention"
summary:
mode: "member-comment"
config:
prefix: "Attention Required:"
- title: ":star: New Features"
labels:
- "type: enhancement"
- title: ":lady_beetle: Bug Fixes"
labels:
- "type: bug"
- "type: regression"
- title: ":notebook_with_decorative_cover: Documentation"
labels:
- "type: documentation"
- title: ":hammer: Dependency Upgrades"
sort: "title"
labels:
- "type: dependency-upgrade"
contributors:
exclude:
names:
- "bclozel"
- "jhoeller"
- "rstoyanchev"
- "sbrannen"
- "sdeleuze"
- "snicoll"
@@ -1,13 +1,6 @@
changelog:
repository: spring-projects/spring-framework
sections:
- title: ":warning: Attention Required"
labels:
- "for: upgrade-attention"
summary:
mode: "member-comment"
config:
prefix: "Attention Required:"
- title: ":star: New Features"
labels:
- "type: enhancement"
@@ -27,7 +20,9 @@ changelog:
names:
- "bclozel"
- "jhoeller"
- "poutsma"
- "rstoyanchev"
- "sbrannen"
- "sdeleuze"
- "simonbasle"
- "snicoll"
+13 -13
View File
@@ -19,35 +19,35 @@ inputs:
java-version:
description: 'Java version to use for the build'
required: false
default: '25'
default: '17'
runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '25' || '' }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # 6.2.0
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-provider: basic
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
develocity-token-expiry: 4
- name: Configure Gradle Properties
shell: bash
run: |
echo 'systemProp.user.name=spring-builds+github' >> $GRADLE_USER_HOME/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $GRADLE_USER_HOME/gradle.properties
echo 'org.gradle.daemon=false' >> $GRADLE_USER_HOME/gradle.properties
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure Toolchain Properties
if: ${{ inputs.java-toolchain == 'true' }}
shell: bash
run: |
echo toolchainVersion=${{ inputs.java-version }} >> $GRADLE_USER_HOME/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $GRADLE_USER_HOME/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $GRADLE_USER_HOME/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $GRADLE_USER_HOME/gradle.properties
echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
@@ -1,7 +0,0 @@
name: Build Release
runs:
using: composite
steps:
- name: Build Release
shell: bash
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository publishAllPublicationsToDeploymentRepository
@@ -1,16 +0,0 @@
artifactory:
artifacts:
- pattern: "/**/framework-api-*.zip"
properties:
zip.deployed: "false"
zip.name: "spring-framework"
- pattern: "/**/framework-api-*-docs.zip"
properties:
zip.type: "docs"
- pattern: "/**/framework-api-*-schema.zip"
properties:
zip.type: "schema"
maven-central:
excludes:
- "org/springframework/framework-api/**"
- "org/springframework/framework-docs/**"
@@ -1,7 +0,0 @@
name: Test Release
runs:
using: composite
steps:
- name: Test Release
shell: bash
run: ./gradlew check
@@ -0,0 +1,43 @@
name: Sync to Maven Central
description: 'Syncs a release to Maven Central and waits for it to be available for use'
inputs:
jfrog-cli-config-token:
description: 'Config token for the JFrog CLI'
required: true
ossrh-s01-staging-profile:
description: 'Staging profile to use when syncing to Central'
required: true
ossrh-s01-token-password:
description: 'Password for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-token-username:
description: 'Username for authentication with s01.oss.sonatype.org'
required: true
spring-framework-version:
description: 'Version of Spring Framework that is being synced to Central'
required: true
runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
shell: bash
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
- name: Sync
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
with:
close: true
create: true
generate-checksums: true
password: ${{ inputs.ossrh-s01-token-password }}
release: true
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
upload: true
username: ${{ inputs.ossrh-s01-token-username }}
- name: Await
uses: ./.github/actions/await-http-resource
with:
url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
@@ -0,0 +1,20 @@
{
"files": [
{
"aql": {
"items.find": {
"$and": [
{
"@build.name": "${buildName}",
"@build.number": "${buildNumber}",
"path": {
"$nmatch": "org/springframework/framework-api/*"
}
}
]
}
},
"target": "nexus/"
}
]
}
-3
View File
@@ -1,3 +0,0 @@
require:
members: false
-18
View File
@@ -1,18 +0,0 @@
workflow:
generator:
project:
java:
versions:
primary: 25
workflows:
release-train:
build:
env:
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
COMMERCIAL_RELEASE_REPO_URL: vars.COMMERCIAL_RELEASE_REPO_URL
test:
env:
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
COMMERCIAL_RELEASE_REPO_URL: vars.COMMERCIAL_RELEASE_REPO_URL
+16 -4
View File
@@ -7,15 +7,27 @@ on:
push:
branches:
- '*.x'
permissions:
contents: read
jobs:
backport-issue:
build:
permissions:
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Create Backport Issue
uses: spring-io/backport-bot@v0.0.3
- name: Check out code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
distribution: 'liberica'
java-version: 17
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_EVENT: ${{ toJSON(github.event) }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
+13 -32
View File
@@ -2,76 +2,57 @@ name: Build and Deploy Snapshot
on:
push:
branches:
- 'main'
- 6.1.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
name: Build and Deploy Snapshot
if: ${{ github.repository == 'spring-projects/spring-framework' || github.repository == 'spring-projects/spring-framework-commercial' }}
runs-on: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }}
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Check Out Code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }}
commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}
commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}
commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }}
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@aba148f1541e09adcf5735af90029fac9a6d3083 # v0.0.5
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: ${{ vars.COMMERCIAL && format('spring-framework-commercial-{0}', '7.1.x') || format('spring-framework-{0}', '7.1.x') }}
build-name: 'spring-framework-6.1.x'
folder: 'deployment-repository'
project: ${{ vars.COMMERCIAL && 'spring' }}
repository: ${{ vars.COMMERCIAL && 'spring-enterprise-maven-dev-local' || 'libs-snapshot-local' }}
uri: ${{ vars.COMMERCIAL_DEPLOY_REPO_URL || 'https://repo.spring.io' }}
username: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_USERNAME || secrets.ARTIFACTORY_USERNAME }}
password: ${{ vars.COMMERCIAL && secrets.COMMERCIAL_ARTIFACTORY_PASSWORD || secrets.ARTIFACTORY_PASSWORD }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-snapshot-local'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
- name: Send Notification
if: always()
uses: ./.github/actions/send-notification
with:
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 25', github.ref_name) }}
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
status: ${{ job.status }}
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
trigger-docs-build:
name: Trigger Docs Build
needs: build-and-deploy-snapshot
if: ${{ !vars.COMMERCIAL }} # remove when commercial support
permissions:
actions: write
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
steps:
- name: Run Deploy Docs Workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }}
verify:
name: Verify
needs: build-and-deploy-snapshot
uses: ./.github/workflows/verify.yml
secrets:
commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}
commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
opensource-repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
opensource-repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
with:
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
+18 -6
View File
@@ -7,18 +7,30 @@ jobs:
name: Build Pull Request
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Check Out Code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set Up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: '17'
- name: Check Out
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Build
id: build
uses: ./.github/actions/build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
- name: Print JVM Thread Dumps When Cancelled
if: cancelled()
uses: ./.github/actions/print-jvm-thread-dumps
- name: Upload Build Reports
if: failure()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
path: '**/build/reports/'
+13 -16
View File
@@ -1,34 +1,35 @@
name: CI
on:
schedule:
- cron: '30 9 * * *'
permissions:
contents: read
push:
branches:
- 6.1.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
if: ${{ github.repository == 'spring-projects/spring-framework' || github.repository == 'spring-projects/spring-framework-commercial' }}
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ${{ matrix.os.id }}
timeout-minutes: 60
strategy:
matrix:
os:
- id: ${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }}
- id: ubuntu-latest
name: Linux
java:
- version: 17
toolchain: true
toolchain: false
- version: 21
toolchain: true
- version: 25
toolchain: false
- version: 27
- version: 22
toolchain: true
- version: 23
toolchain: true
exclude:
- os:
name: Linux
java:
version: 25
version: 17
steps:
- name: Prepare Windows runner
if: ${{ runner.os == 'Windows' }}
@@ -37,15 +38,11 @@ jobs:
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check Out Code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@v4
- name: Build
id: build
uses: ./.github/actions/build
with:
commercial-release-repository-url: ${{ vars.COMMERCIAL_RELEASE_REPO_URL }}
commercial-repository-password: ${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}
commercial-repository-username: ${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}
commercial-snapshot-repository-url: ${{ vars.COMMERCIAL_SNAPSHOT_REPO_URL }}
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
java-early-access: ${{ matrix.java.early-access || 'false' }}
java-distribution: ${{ matrix.java.distribution }}
+35
View File
@@ -0,0 +1,35 @@
name: Deploy Docs
on:
push:
branches:
- 'main'
- '*.x'
- '!gh-pages'
tags:
- 'v*'
repository_dispatch:
types: request-build-reference # legacy
workflow_dispatch:
permissions:
actions: write
jobs:
build:
name: Dispatch docs deployment
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 1
ref: docs-build
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD)
-39
View File
@@ -1,39 +0,0 @@
name: Release Milestone
on:
push:
tags:
- v7.1.0-M[1-9]
- v7.1.0-RC[1-9]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
trigger-docs-build:
name: Trigger Docs Build
permissions:
actions: write
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Determine Version
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Run Deploy Docs Workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }}
create-github-release:
name: Create GitHub Release
needs:
- trigger-docs-build
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
commercial: ${{ vars.COMMERCIAL }}
milestone: ${{ needs.trigger-docs-build.outputs.version }}
pre-release: true
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
-38
View File
@@ -1,38 +0,0 @@
name: Release
on:
push:
tags:
- v7.1.[0-9]+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
trigger-docs-build:
name: Trigger Docs Build
permissions:
actions: write
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Determine Version
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Run Deploy Docs Workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml --repo ${{ github.repository }} -r docs-build -f build-refname=${{ github.ref_name }}
create-github-release:
name: Create GitHub Release
needs:
- trigger-docs-build
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
commercial: ${{ vars.COMMERCIAL }}
latest: true
milestone: ${{ needs.trigger-docs-build.outputs.version }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
-93
View File
@@ -1,93 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Build"
run-name: "${{ inputs.callback-ref }} Build"
"on":
workflow_dispatch:
inputs:
callback:
description: "Repository to which a callback should be made upon completion"
required: true
type: "string"
callback-ref:
description: "Ref in the callback repository to which a callback should be made upon completion"
required: true
type: "string"
release-train-maven-repository-url:
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
required: true
type: "string"
permissions:
contents: "read"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
jobs:
build-release:
name: "Build Release"
runs-on: "ubuntu22-2-8"
steps:
- name: "Prevent Re-runs"
id: "prevent-re-runs"
run: |-
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
echo "Re-runs are prohibited. Use the 'Release Train  Retry' workflow to retry build failures"
exit 1
fi
- name: "Set up Java"
id: "set-up-java"
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
with:
distribution: "liberica"
java-version: "25"
- name: "Check Out Code"
id: "check-out-code"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
- name: "Build Release"
id: "build-release"
uses: "./.github/actions/release-train-build"
env:
COMMERCIAL_RELEASE_REPO_URL: "${{ vars.COMMERCIAL_RELEASE_REPO_URL }}"
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
- name: "Upload Deployment Repository"
id: "upload-deployment-repository"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "deployment-repository"
path: "deployment-repository/**"
- name: "Upload Deployment Spec"
id: "upload-deployment-spec"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
archive: "false"
if-no-files-found: "ignore"
name: "deployment-spec"
path: ".github/actions/release-train-build/deployment-spec.yml"
- name: "Save Build System Caches"
id: "save-build-system-caches"
uses: "actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
with:
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
path: |-
~/.gradle/caches
~/.gradle/wrapper
- name: "Send Callback"
id: "send-callback"
if: "${{ !cancelled() }}"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run callback \
--repo ${{ inputs.callback }} \
--ref ${{ inputs.callback-ref }} \
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
--field deployment-repository-artifact-identifier=${{ steps.upload-deployment-repository.outputs.artifact-id }} \
--field deployment-spec-artifact-identifier=${{ steps.upload-deployment-spec.outputs.artifact-id }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }} \
--field result=${{ job.status == 'success' && 'built' || 'build-failed' }} \
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
-55
View File
@@ -1,55 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Join"
run-name: "${{ inputs.release-train }} Join"
"on":
workflow_dispatch:
inputs:
deployment-destination:
description: "Destination to which the release should be deployed"
options:
- "Maven Central"
- "Spring Enterprise"
required: true
type: "choice"
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
join-release-train:
name: "Join Release Train"
runs-on: "ubuntu-latest"
steps:
- name: "Join Release Train"
id: "join-release-train"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run join \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field commit-hash=${{ github.sha }} \
--field deployment-destination=${{ inputs.deployment-destination == 'Maven Central' && 'maven-central' || 'spring-enterprise' }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
-46
View File
@@ -1,46 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Leave"
run-name: "${{ inputs.release-train }} Leave"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
leave:
name: "Leave"
runs-on: "ubuntu-latest"
steps:
- name: "Leave"
id: "leave"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run leave \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
-47
View File
@@ -1,47 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Ready"
run-name: "${{ inputs.release-train }} Ready"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
ready:
name: "Ready"
runs-on: "ubuntu-latest"
steps:
- name: "Ready"
id: "ready"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run ready \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field commit-hash=${{ github.sha }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
-34
View File
@@ -1,34 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Retry"
run-name: "${{ inputs.release-train }} Retry"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
trigger-retry:
name: "Trigger Retry"
runs-on: "ubuntu-latest"
steps:
- name: "Trigger Retry"
id: "trigger-retry"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run retry \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
-84
View File
@@ -1,84 +0,0 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Test"
run-name: "${{ inputs.callback-ref }} Test"
"on":
workflow_dispatch:
inputs:
callback:
description: "Repository to which a callback should be made upon completion"
required: true
type: "string"
callback-ref:
description: "Ref in the callback repository to which a callback should be made upon completion"
required: true
type: "string"
release-train-maven-repository-url:
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
required: true
type: "string"
permissions:
contents: "read"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
jobs:
test-release:
name: "Test Release"
runs-on: "ubuntu22-2-8"
steps:
- name: "Prevent Re-runs"
id: "prevent-re-runs"
run: |-
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
echo "Re-runs are prohibited. Use the 'Release Train  Retry' workflow to retry test failures"
exit 1
fi
- name: "Set up Java"
id: "set-up-java"
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
with:
distribution: "liberica"
java-version: "25"
- name: "Check Out Code"
id: "check-out-code"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
- name: "Restore Build System Caches"
id: "restore-build-system-caches"
uses: "actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
with:
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
path: |-
~/.gradle/caches
~/.gradle/wrapper
- name: "Test Release"
id: "test-release"
uses: "./.github/actions/release-train-test"
env:
COMMERCIAL_RELEASE_REPO_URL: "${{ vars.COMMERCIAL_RELEASE_REPO_URL }}"
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
- name: "Send Callback"
id: "send-callback"
if: "${{ !cancelled() }}"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run callback \
--repo ${{ inputs.callback }} \
--ref ${{ inputs.callback-ref }} \
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }} \
--field result=${{ job.status == 'success' && 'tested' || 'test-failed' }} \
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: "Upload Build System Reports"
id: "upload-build-system-reports"
if: "${{ failure() }}"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "build-system-reports"
path: "**/build/reports"
+94
View File
@@ -0,0 +1,94 @@
name: Release
on:
push:
tags:
- v6.1.[0-9]+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-stage-release:
name: Build and Stage Release
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-staging-local'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-stage-release
uses: ./.github/workflows/verify.yml
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
sync-to-maven-central:
name: Sync to Maven Central
needs:
- build-and-stage-release
- verify
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Sync to Maven Central
uses: ./.github/actions/sync-to-maven-central
with:
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
ossrh-s01-staging-profile: ${{ secrets.OSSRH_S01_STAGING_PROFILE }}
ossrh-s01-token-password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
ossrh-s01-token-username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
promote-release:
name: Promote Release
needs:
- build-and-stage-release
- sync-to-maven-central
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
create-github-release:
name: Create GitHub Release
needs:
- build-and-stage-release
- promote-release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
milestone: ${{ needs.build-and-stage-release.outputs.version }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
@@ -1,37 +0,0 @@
name: Update Antora UI Spring
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
pull-requests: write
issues: write
contents: write
jobs:
update-antora-ui-spring:
name: Update on Supported Branches
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
strategy:
matrix:
branch: [ '6.2.x', '7.0.x', 'main' ]
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
with:
docs-branch: ${{ matrix.branch }}
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'framework-docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
name: Update on docs-build
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
with:
docs-branch: 'docs-build'
token: ${{ secrets.GITHUB_TOKEN }}
+14 -25
View File
@@ -12,51 +12,42 @@ on:
required: true
type: string
secrets:
commercial-repository-password:
description: 'Password for authentication with the commercial repository'
required: false
commercial-repository-username:
description: 'Username for authentication with the commercial repository'
required: false
google-chat-webhook-url:
description: 'Google Chat Webhook URL'
required: true
opensource-repository-password:
description: 'Password for authentication with the open-source repository'
repository-password:
description: 'Password for authentication with the repository'
required: false
opensource-repository-username:
description: 'Username for authentication with the open-source repository'
repository-username:
description: 'Username for authentication with the repository'
required: false
token:
description: 'Token to use for authentication with GitHub'
required: true
permissions:
contents: read
jobs:
verify:
name: Verify
runs-on: ${{ vars.UBUNTU_SMALL || 'ubuntu-latest' }}
runs-on: ubuntu-latest
steps:
- name: Check Out Release Verification Tests
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@v4
with:
ref: 'v0.0.3'
ref: 'v0.0.2'
repository: spring-projects/spring-framework-release-verification
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@v4
with:
path: send-notification
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-provider: basic
cache-read-only: false
- name: Configure Gradle Properties
shell: bash
@@ -65,17 +56,15 @@ jobs:
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
- name: Run Release Verification Tests
env:
RVT_COMMERCIAL_REPOSITORY_PASSWORD: ${{ secrets.commercial-repository-password }}
RVT_COMMERCIAL_REPOSITORY_USERNAME: ${{ secrets.commercial-repository-username }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.opensource-repository-password }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.opensource-repository-username }}
RVT_RELEASE_TYPE: ${{ vars.COMMERCIAL && 'commercial' || 'oss' }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
RVT_RELEASE_TYPE: oss
RVT_STAGING: ${{ inputs.staging }}
RVT_VERSION: ${{ inputs.version }}
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
if: failure()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
+2 -8
View File
@@ -9,7 +9,6 @@ ivy-cache
argfile*
activemq-data/
classes/
.cursor/
# Log files
jxl.log
@@ -39,12 +38,11 @@ bin
.springBeans
spring-*/src/main/java/META-INF/MANIFEST.MF
# IntelliJ IDEA artifacts and output dirs
# IDEA artifacts and output dirs
*.iml
*.ipr
*.iws
.idea/*
!.idea/icon.svg
.idea
out
test-output
atlassian-ide-plugin.xml
@@ -52,10 +50,6 @@ atlassian-ide-plugin.xml
# VS Code
.vscode/
# Claude artifacts
.claude/*
cached-antora-playbook.yml
node_modules
/.kotlin/
-52
View File
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
<style type="text/css">
.st0{fill:#6DB33F;}
</style>
<title>icon-framework</title>
<g id="Layer_2_1_">
<g id="Layer_1-2">
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=25-librca
java=17.0.13-librca
+8 -9
View File
@@ -1,4 +1,4 @@
# Contributing to the Spring Framework
# Contributing to the Spring Framework
First off, thank you for taking the time to contribute! :+1: :tada:
@@ -16,7 +16,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
### Code of Conduct
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
By participating you are expected to uphold this code.
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
@@ -65,6 +65,10 @@ follow-up reports will need to be created as new issues with a fresh description
#### Submit a Pull Request
1. If you have not previously done so, please sign the
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
automatically when you submit the PR.
1. Should you create an issue first? No, just create the pull request and use the
description to provide context and motivation, as you would for an issue. If you want
to start a discussion first or have already created an issue, once a pull request is
@@ -81,13 +85,8 @@ multiple edits or corrections of the same logical change. See
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
for an overview of streamlining the commit history.
1. All commits must include a _Signed-off-by_ trailer at the end of each commit message
to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post
[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
1. Format commit messages using 55 characters for the subject line, 72 characters per line
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
for the description, followed by the issue fixed, e.g. `Closes gh-22276`. See the
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
for best practices around commit messages, and use `git log` to see some examples.
@@ -120,7 +119,7 @@ source code into your IDE.
The wiki pages
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
define the source file coding standards we use along with some IntelliJ editor settings we customize.
define the source file coding standards we use along with some IDEA editor settings we customize.
### Reference Docs
+3 -3
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=main)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=6.1.x)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
## Code of Conduct
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework/?tab=coc-ov-file#contributor-code-of-conduct). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
## Access to Binaries
@@ -27,7 +27,7 @@ See the [Build from Source](https://github.com/spring-projects/spring-framework/
## Continuous Integration Builds
CI builds are defined with [GitHub Actions workflows](.github/workflows).
Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation.
## Stay in Touch
+11 -5
View File
@@ -1,10 +1,16 @@
# Reporting a Vulnerability
Please, [open a draft security advisory](https://github.com/spring-projects/security-advisories/security/advisories/new) if you need to disclose and discuss a security issue in private with the Spring Framework team. Note that we only accept reports against [supported versions](https://spring.io/projects/spring-framework#support).
For more details, check out our [security policy](https://spring.io/security-policy).
# Security Policy
## JAR signing
Spring Framework JARs released on Maven Central are signed.
You'll find more information about the key here: https://spring.io/GPG-KEY-spring.txt
## Supported Versions
Please see the
[Spring Framework Versions](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions)
wiki page.
## Reporting a Vulnerability
Please see https://spring.io/security-policy.
+58 -23
View File
@@ -1,12 +1,14 @@
plugins {
id 'io.freefair.aspectj' version '8.13.1' apply false
id 'io.freefair.aspectj' version '8.4' apply false
// kotlinVersion is managed in gradle.properties
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka'
id 'com.github.bjornvester.xjc' version '1.9.1' apply false
id 'com.gradleup.shadow' version "9.2.2" apply false
id 'org.jetbrains.dokka' version '1.8.20'
id 'com.github.ben-manes.versions' version '0.51.0'
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
id 'de.undercouch.download' version '5.4.0'
id 'io.github.goooler.shadow' version '8.1.8' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'io.spring.nullability' version '0.0.15' apply false
id 'me.champeau.mrjar' version '0.1.1'
}
ext {
@@ -18,8 +20,23 @@ description = "Spring Framework"
configure(allprojects) { project ->
apply plugin: "org.springframework.build.localdev"
apply plugin: "org.springframework.build.repositories"
group = "org.springframework"
repositories {
mavenCentral()
maven {
url "https://repo.spring.io/milestone"
content {
// Netty 5 optional support
includeGroup 'io.projectreactor.netty'
}
}
if (version.contains('-')) {
maven { url "https://repo.spring.io/milestone" }
}
if (version.endsWith('-SNAPSHOT')) {
maven { url "https://repo.spring.io/snapshot" }
}
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor 0, "seconds"
@@ -46,34 +63,52 @@ configure([rootProject] + javaProjects) { project ->
apply plugin: "java"
apply plugin: "java-test-fixtures"
apply plugin: 'org.springframework.build.conventions'
apply from: "${rootDir}/gradle/toolchains.gradle"
apply from: "${rootDir}/gradle/ide.gradle"
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.platform:junit-platform-suite")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.junit.platform:junit-platform-suite-api")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("io.mockk:mockk") {
exclude group: 'junit', module: 'junit'
}
testImplementation("io.mockk:mockk")
testImplementation("org.assertj:assertj-core")
// Pull in the latest JUnit 5 Launcher API to ensure proper support in IDEs.
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.junit.platform:junit-platform-suite-engine")
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
// JSR-305 only used for non-required meta-annotations
compileOnly("com.google.code.findbugs:jsr305")
testCompileOnly("com.google.code.findbugs:jsr305")
}
ext.javadocLinks = [
"https://docs.oracle.com/en/java/javase/17/docs/api/",
//"https://jakarta.ee/specifications/platform/11/apidocs/",
"https://docs.hibernate.org/orm/7.2/javadocs/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
"https://projectreactor.io/docs/core/release/api/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
"https://docs.junit.org/6.1.2/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
"https://jspecify.dev/docs/api/"
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.10.4/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
// JakartaEE equivalents in the jakarta.annotation package.
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
] as String[]
}
+1 -31
View File
@@ -9,18 +9,7 @@ The `org.springframework.build.conventions` plugin applies all conventions to th
* Configuring the Java compiler, see `JavaConventions`
* Configuring the Kotlin compiler, see `KotlinConventions`
* Configuring testing in the build with `TestConventions`
* Configuring the ArchUnit rules for the project, see `org.springframework.build.architecture.ArchitectureRules`
This plugin also provides a DSL extension to optionally enable Java preview features for
compiling and testing sources in a module. This can be applied with the following in a
module build file:
```groovy
springFramework {
enableJavaPreviewFeatures = true
}
```
* Configuring testing in the build with `TestConventions`
## Build Plugins
@@ -33,25 +22,6 @@ but doesn't affect the classpath of dependent projects.
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
configurations are preferred.
### MultiRelease Jar
The `org.springframework.build.multiReleaseJar` plugin configures the project with MultiRelease JAR support.
It creates a new SourceSet and dedicated tasks for each Java variant considered.
This can be configured with the DSL, by setting a list of Java variants to configure:
```groovy
plugins {
id 'org.springframework.build.multiReleaseJar'
}
multiRelease {
releaseVersions 21, 24
}
```
Note, Java classes will be compiled with the toolchain pre-configured by the project, assuming that its
Java language version is equal or higher than all variants we consider. Each compilation task will only
set the "-release" compilation option accordingly to produce the expected bytecode version.
### RuntimeHints Java Agent
+2 -26
View File
@@ -20,24 +20,14 @@ ext {
dependencies {
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.2.0"
implementation "com.tngtech.archunit:archunit:1.4.1"
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
testImplementation("org.assertj:assertj-core:${assertjVersion}")
testImplementation(platform("org.junit:junit-bom:${junitVersion}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
gradlePlugin {
plugins {
architecturePlugin {
id = "org.springframework.architecture"
implementationClass = "org.springframework.build.architecture.ArchitecturePlugin"
}
conventionsPlugin {
id = "org.springframework.build.conventions"
implementationClass = "org.springframework.build.ConventionsPlugin"
@@ -46,14 +36,6 @@ gradlePlugin {
id = "org.springframework.build.localdev"
implementationClass = "org.springframework.build.dev.LocalDevelopmentPlugin"
}
multiReleasePlugin {
id = "org.springframework.build.multiReleaseJar"
implementationClass = "org.springframework.build.multirelease.MultiReleaseJarPlugin"
}
repositoriesPlugin {
id = "org.springframework.build.repositories"
implementationClass = "org.springframework.build.RepositoriesPlugin"
}
optionalDependenciesPlugin {
id = "org.springframework.build.optional-dependencies"
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
@@ -64,9 +46,3 @@ gradlePlugin {
}
}
}
test {
useJUnitPlatform()
}
jar.dependsOn check
+8 -7
View File
@@ -1,26 +1,27 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<!-- Root Checks -->
<module name="io.spring.javaformat.checkstyle.check.SpringHeaderCheck">
<property name="fileExtensions" value="java"/>
<property name="headerType" value="apache2"/>
<property name="headerCopyrightPattern" value="20\d\d-present"/>
<property name="headerCopyrightPattern" value="20\d\d-20\d\d"/>
<property name="packageInfoHeaderType" value="none"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"/>
<!-- TreeWalker Checks -->
<module name="TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="UnusedImports"/>
<module name="RedundantImport"/>
<module name="com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck">
<property name="processJavadoc" value="true"/>
</module>
<!-- Modifiers -->
<module name="com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck"/>
</module>
</module>
</module>
+1 -3
View File
@@ -1,4 +1,2 @@
org.gradle.caching=true
assertjVersion=3.27.3
javaFormatVersion=0.0.43
junitVersion=5.12.2
javaFormatVersion=0.0.42
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,9 +17,6 @@
package org.springframework.build;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@@ -38,7 +35,6 @@ import org.gradle.api.plugins.quality.CheckstylePlugin;
* {@link Plugin} that applies conventions for checkstyle.
*
* @author Brian Clozel
* @author Sam Brannen
*/
public class CheckstyleConventions {
@@ -52,10 +48,9 @@ public class CheckstyleConventions {
configureNoHttpPlugin(project);
}
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize()
.set("checkstyleNohttp".equals(checkstyle.getName()) ? "1536m" : "1g"));
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("14.1.0");
checkstyle.setToolVersion("10.21.2");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -68,10 +63,8 @@ public class CheckstyleConventions {
project.getPlugins().apply(NoHttpPlugin.class);
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**", "**/.classpath",
"**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**", "buildSrc/build/**",
".claude/**");
excludeGitIgnoredPaths(project, noHttp);
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
List<String> buildFolders = List.of("bin", "build", "out");
project.allprojects(subproject -> {
Path rootPath = project.getRootDir().toPath();
@@ -83,48 +76,4 @@ public class CheckstyleConventions {
});
}
/**
* Additionally exclude everything matched by the root {@code .gitignore} file,
* so that new ignored paths (build output, IDE metadata, local git worktrees,
* etc.) are automatically kept out of nohttp scanning without having to
* remember to mirror every {@code .gitignore} change here as well.
* <p>Negated patterns (lines starting with {@code !}) are not supported and are
* simply skipped, since there is no useful Ant-glob equivalent for them here.
*/
private static void excludeGitIgnoredPaths(Project project, NoHttpExtension noHttp) {
File gitignore = project.getRootProject().file(".gitignore");
if (!gitignore.exists()) {
return;
}
try {
for (String line : Files.readAllLines(gitignore.toPath())) {
String pattern = line.strip();
if (pattern.isEmpty() || pattern.startsWith("#") || pattern.startsWith("!")) {
continue;
}
boolean directoryOnly = pattern.endsWith("/");
if (directoryOnly) {
pattern = pattern.substring(0, pattern.length() - 1);
}
// A '/' anywhere but a (now removed) trailing position anchors the
// pattern to the repository root; otherwise it matches at any depth.
boolean anchored = pattern.contains("/");
if (pattern.startsWith("/")) {
pattern = pattern.substring(1);
}
String rootPattern = anchored ? pattern : "**/" + pattern;
if (directoryOnly) {
noHttp.getSource().exclude(rootPattern + "/**");
}
else {
// The pattern may match either a file or a directory, so exclude both.
noHttp.getSource().exclude(rootPattern, rootPattern + "/**");
}
}
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to read .gitignore for nohttp exclusions", ex);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,15 +21,12 @@ import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
import org.springframework.build.architecture.ArchitecturePlugin;
/**
* Plugin to apply conventions to projects that are part of Spring Framework's build.
* Conventions are applied in response to various plugins being applied.
*
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
* {@link TestConventions} and {@link JavaConventions} are applied.
* The {@link ArchitecturePlugin} plugin is also applied.
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
* are applied.
*
@@ -39,8 +36,6 @@ public class ConventionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getExtensions().create("springFramework", SpringFrameworkExtension.class);
new ArchitecturePlugin().apply(project);
new CheckstyleConventions().apply(project);
new JavaConventions().apply(project);
new KotlinConventions().apply(project);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.build;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.gradle.api.Plugin;
@@ -26,6 +27,7 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JvmVendorSpec;
/**
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
@@ -40,21 +42,8 @@ public class JavaConventions {
private static final List<String> TEST_COMPILER_ARGS;
/**
* The Java version we should use as the JVM baseline for building the project.
* <p>NOTE: If you update this value, you should also update the value used in
* the {@code javadoc} task in {@code framework-api.gradle}.
*/
private static final JavaLanguageVersion DEFAULT_LANGUAGE_VERSION = JavaLanguageVersion.of(25);
/**
* The Java version we should use as the baseline for the compiled bytecode
* (the "-release" compiler argument).
*/
private static final JavaLanguageVersion DEFAULT_RELEASE_VERSION = JavaLanguageVersion.of(17);
static {
List<String> commonCompilerArgs = List.of(
List<String> commonCompilerArgs = Arrays.asList(
"-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann",
"-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides",
"-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options",
@@ -62,74 +51,43 @@ public class JavaConventions {
);
COMPILER_ARGS = new ArrayList<>();
COMPILER_ARGS.addAll(commonCompilerArgs);
COMPILER_ARGS.addAll(List.of(
COMPILER_ARGS.addAll(Arrays.asList(
"-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
"-Xlint:unchecked", "-Werror"
));
TEST_COMPILER_ARGS = new ArrayList<>();
TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
TEST_COMPILER_ARGS.addAll(List.of("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes",
"-Xlint:-deprecation", "-Xlint:-unchecked"));
}
public void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> {
applyToolchainConventions(project);
applyJavaCompileConventions(project);
});
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
}
/**
* Configure the Toolchain support for the project.
* @param project the current project
*/
private static void applyToolchainConventions(Project project) {
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
toolchain.getLanguageVersion().set(DEFAULT_LANGUAGE_VERSION);
});
}
/**
* Apply the common Java compiler options for main sources, test fixture sources, and
* Applies the common Java compiler options for main sources, test fixture sources, and
* test sources.
* @param project the current project
*/
private void applyJavaCompileConventions(Project project) {
project.afterEvaluate(p -> {
p.getTasks().withType(JavaCompile.class)
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
.forEach(compileTask -> {
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
compileTask.getOptions().setEncoding("UTF-8");
setJavaRelease(compileTask);
});
p.getTasks().withType(JavaCompile.class)
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|| compileTask.getName().equals("compileTestFixturesJava"))
.forEach(compileTask -> {
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
compileTask.getOptions().setEncoding("UTF-8");
setJavaRelease(compileTask);
});
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(17));
});
}
/**
* We should pick the {@link #DEFAULT_RELEASE_VERSION} for all compiled classes,
* unless the current task is compiling multi-release JAR code with a higher version.
*/
private void setJavaRelease(JavaCompile task) {
int defaultVersion = DEFAULT_RELEASE_VERSION.asInt();
int releaseVersion = defaultVersion;
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
if (task.getName().contains("Java" + version)) {
releaseVersion = version;
break;
}
}
task.getOptions().getRelease().set(releaseVersion);
project.getTasks().withType(JavaCompile.class)
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
.forEach(compileTask -> {
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
compileTask.getOptions().setEncoding("UTF-8");
});
project.getTasks().withType(JavaCompile.class)
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|| compileTask.getName().equals("compileTestFixturesJava"))
.forEach(compileTask -> {
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
compileTask.getOptions().setEncoding("UTF-8");
});
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,79 +16,33 @@
package org.springframework.build;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.jetbrains.dokka.gradle.DokkaExtension;
import org.jetbrains.dokka.gradle.DokkaPlugin;
import org.jetbrains.kotlin.gradle.dsl.JvmTarget;
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion;
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
/**
* @author Brian Clozel
* @author Sebastien Deleuze
*/
public class KotlinConventions {
void apply(Project project) {
project.getPlugins().withId("org.jetbrains.kotlin.jvm", plugin -> {
project.getTasks().withType(KotlinCompile.class, this::configure);
if (project.getLayout().getProjectDirectory().dir("src/main/kotlin").getAsFile().exists()) {
project.getPlugins().apply(DokkaPlugin.class);
project.getExtensions().configure(DokkaExtension.class, dokka -> configure(project, dokka));
project.project(":framework-api").getDependencies()
.add("dokka", project.getDependencyFactory().createProjectDependency());
}
});
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
}
private void configure(KotlinCompile compile) {
compile.compilerOptions(options -> {
options.getApiVersion().set(KotlinVersion.KOTLIN_2_2);
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_2);
options.getJvmTarget().set(JvmTarget.JVM_17);
options.getJavaParameters().set(true);
options.getAllWarningsAsErrors().set(true);
options.getFreeCompilerArgs().addAll(
"-Xsuppress-version-warnings",
"-Xjsr305=strict", // For dependencies using JSR 305
"-opt-in=kotlin.RequiresOptIn",
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
"-Xannotation-default-target=param-property" // Preferred behavior, default with Kotlin language version set to 2.4+, see https://youtrack.jetbrains.com/issue/KT-73255
);
});
}
private void configure(Project project, DokkaExtension dokka) {
dokka.getDokkaSourceSets().forEach(sourceSet -> {
sourceSet.getSourceRoots().setFrom(project.file("src/main/kotlin"));
sourceSet.getClasspath()
.from(project.getExtensions()
.getByType(SourceSetContainer.class)
.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
.getOutput());
var externalDocumentationLinks = sourceSet.getExternalDocumentationLinks();
var springVersion = project.getVersion();
externalDocumentationLinks.register("spring-framework", spec -> {
spec.url("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/");
spec.packageListUrl("https://docs.spring.io/spring-framework/docs/" + springVersion + "/javadoc-api/element-list");
});
externalDocumentationLinks.register("reactor-core", spec ->
spec.url("https://projectreactor.io/docs/core/release/api/"));
externalDocumentationLinks.register("reactive-streams", spec ->
spec.url("https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/"));
externalDocumentationLinks.register("kotlinx-coroutines", spec ->
spec.url("https://kotlinlang.org/api/kotlinx.coroutines/"));
externalDocumentationLinks.register("hamcrest", spec ->
spec.url("https://javadoc.io/doc/org.hamcrest/hamcrest/2.1/"));
externalDocumentationLinks.register("jakarta-servlet", spec -> {
spec.url("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/");
spec.packageListUrl("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list");
});
externalDocumentationLinks.register("rsocket-core", spec ->
spec.url("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"));
});
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
kotlinOptions.setApiVersion("1.7");
kotlinOptions.setLanguageVersion("1.7");
kotlinOptions.setJvmTarget("17");
kotlinOptions.setJavaParameters(true);
kotlinOptions.setAllWarningsAsErrors(true);
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
}
}
@@ -1,98 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* Plugin that configures the OSS, commercial and release train repositories in the build.
*
* @author Brian Clozel
*/
public class RepositoriesPlugin implements Plugin <Project> {
@Override
public void apply(Project project) {
configureOssRepositories(project);
configureCommercialRepositories(project);
configureReleaseTrainRepository(project);
}
private void configureOssRepositories(Project project) {
project.getRepositories().mavenCentral();
if (project.getVersion().toString().contains("-")) {
project.getRepositories().maven(repository -> {
repository.setName("spring-oss-milestone");
repository.setUrl("https://repo.spring.io/milestone/");
});
}
if (project.getVersion().toString().endsWith("-SNAPSHOT")) {
project.getRepositories().maven(repository -> {
repository.setName("spring-oss-snapshot");
repository.setUrl("https://repo.spring.io/snapshot/");
});
}
}
private void configureCommercialRepositories(Project project) {
String releaseRepositoryUrl = getEnv("COMMERCIAL_RELEASE_REPO_URL");
if (releaseRepositoryUrl != null) {
project.getRepositories().maven((repository) -> {
repository.setName("spring-commercial-release");
repository.setUrl(releaseRepositoryUrl);
repository.credentials((creds) -> {
creds.setUsername(System.getenv("COMMERCIAL_REPO_USERNAME"));
creds.setPassword(System.getenv("COMMERCIAL_REPO_PASSWORD"));
});
});
}
String snapshotRepositoryUrl = getEnv("COMMERCIAL_SNAPSHOT_REPO_URL");
if (snapshotRepositoryUrl != null && project.getVersion().toString().endsWith("-SNAPSHOT")) {
project.getRepositories().maven((repository) -> {
repository.setName("spring-commercial-snapshot");
repository.setUrl(snapshotRepositoryUrl);
repository.credentials((creds) -> {
creds.setUsername(System.getenv("COMMERCIAL_REPO_USERNAME"));
creds.setPassword(System.getenv("COMMERCIAL_REPO_PASSWORD"));
});
});
}
}
private void configureReleaseTrainRepository(Project project) {
String releaseTrainRepositoryUrl = getEnv("RELEASE_TRAIN_MAVEN_REPOSITORY_URL");
if (releaseTrainRepositoryUrl != null) {
project.getRepositories().maven(repository -> {
repository.setName("spring-release-train");
repository.setUrl(releaseTrainRepositoryUrl);
repository.credentials((creds) -> {
creds.setUsername(System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME"));
creds.setPassword(System.getenv("RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD"));
});
});
}
}
/**
* Returns the environment variable's value, or {@code null} if it is unset or blank.
*/
private static String getEnv(String name) {
String value = System.getenv(name);
return (value != null && !value.isBlank()) ? value : null;
}
}
@@ -1,53 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build;
import java.util.Collections;
import java.util.List;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.process.CommandLineArgumentProvider;
public class SpringFrameworkExtension {
private final Property<Boolean> enableJavaPreviewFeatures;
public SpringFrameworkExtension(Project project) {
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile ->
javaCompile.getOptions().getCompilerArgumentProviders().add(asArgumentProvider()));
project.getTasks().withType(Test.class).configureEach(test ->
test.getJvmArgumentProviders().add(asArgumentProvider()));
}
public Property<Boolean> getEnableJavaPreviewFeatures() {
return this.enableJavaPreviewFeatures;
}
private CommandLineArgumentProvider asArgumentProvider() {
return () -> {
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
return List.of("--enable-preview");
}
return Collections.emptyList();
};
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,31 +16,24 @@
package org.springframework.build;
import java.util.Map;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.testing.Test;
import org.gradle.api.tasks.testing.TestFrameworkOptions;
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
import org.gradle.testretry.TestRetryPlugin;
import org.gradle.testretry.TestRetryTaskExtension;
import java.util.Map;
/**
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
* plugin is applied:
* <ul>
* <li>The {@link TestRetryPlugin Test Retry} plugin is applied so that flaky tests
* are retried 3 times when running on the CI server.
* <li>Common test properties are configured
* <li>The ByteBuddy Java agent is configured on test tasks.
* </ul>
*
* @author Brian Clozel
* @author Andy Wilkinson
* @author Sam Brannen
*/
class TestConventions {
@@ -49,7 +42,6 @@ class TestConventions {
}
private void configureTestConventions(Project project) {
configureByteBuddyAgent(project);
project.getTasks().withType(Test.class,
test -> {
configureTests(project, test);
@@ -58,20 +50,17 @@ class TestConventions {
}
private void configureTests(Project project, Test test) {
TestFrameworkOptions existingOptions = test.getOptions();
test.useJUnitPlatform(options -> {
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
options.copyFrom(junitPlatformOptions);
}
});
test.useJUnitPlatform();
test.include("**/*Tests.class", "**/*Test.class");
test.setSystemProperties(Map.of(
"java.awt.headless", "true",
"io.netty.leakDetection.level", "paranoid",
"junit.platform.discovery.issue.severity.critical", "INFO"
"io.netty5.leakDetectionLevel", "paranoid",
"io.netty5.leakDetection.targetRecords", "32",
"io.netty5.buffer.lifecycleTracingEnabled", "true"
));
if (project.hasProperty("testGroups")) {
test.systemProperty("testGroups", project.findProperty("testGroups"));
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
}
test.jvmArgs(
"--add-opens=java.base/java.lang=ALL-UNNAMED",
@@ -80,20 +69,6 @@ class TestConventions {
);
}
private void configureByteBuddyAgent(Project project) {
if (project.hasProperty("byteBuddyVersion")) {
String byteBuddyVersion = (String) project.findProperty("byteBuddyVersion");
Configuration byteBuddyAgentConfig = project.getConfigurations().create("byteBuddyAgentConfig");
byteBuddyAgentConfig.setTransitive(false);
Dependency byteBuddyAgent = project.getDependencies().create("net.bytebuddy:byte-buddy-agent:" + byteBuddyVersion);
byteBuddyAgentConfig.getDependencies().add(byteBuddyAgent);
project.afterEvaluate(p -> {
p.getTasks().withType(Test.class, test -> test
.jvmArgs("-javaagent:" + byteBuddyAgentConfig.getAsPath()));
});
}
}
private void configureTestRetryPlugin(Project project, Test test) {
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
@@ -1,123 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.architecture;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.EvaluationResult;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.List;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.IgnoreEmptyDirectories;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
/**
* {@link Task} that checks for architecture problems.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
public abstract class ArchitectureCheck extends DefaultTask {
private FileCollection classes;
public ArchitectureCheck() {
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
getProhibitObjectsRequireNonNull().convention(true);
getRules().addAll(ArchitectureRules.CLASSES_SHOULD_NOT_IMPORT_FORBIDDEN_TYPES,
ArchitectureRules.JAVA_CLASSES_SHOULD_NOT_IMPORT_KOTLIN_ANNOTATIONS,
ArchitectureRules.ALL_PACKAGES_SHOULD_BE_FREE_OF_TANGLES,
ArchitectureRules.NO_CLASSES_SHOULD_CALL_STRING_TO_LOWER_CASE_WITHOUT_LOCALE,
ArchitectureRules.NO_CLASSES_SHOULD_CALL_STRING_TO_UPPER_CASE_WITHOUT_LOCALE);
}
@TaskAction
void checkArchitecture() throws IOException {
JavaClasses javaClasses = new ClassFileImporter()
.importPaths(this.classes.getFiles().stream().map(File::toPath).toList());
List<EvaluationResult> violations = getRules().get()
.stream()
.map(ArchitectureRules::archRule)
.map((rule) -> rule.evaluate(javaClasses))
.filter(EvaluationResult::hasViolation)
.toList();
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
outputFile.getParentFile().mkdirs();
if (!violations.isEmpty()) {
StringBuilder report = new StringBuilder();
for (EvaluationResult violation : violations) {
report.append(violation.getFailureReport());
report.append(String.format("%n"));
}
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
}
else {
outputFile.createNewFile();
}
}
public void setClasses(FileCollection classes) {
this.classes = classes;
}
@Internal
public FileCollection getClasses() {
return this.classes;
}
@InputFiles
@SkipWhenEmpty
@IgnoreEmptyDirectories
@PathSensitive(PathSensitivity.RELATIVE)
final FileTree getInputClasses() {
return this.classes.getAsFileTree();
}
@Optional
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public abstract DirectoryProperty getResourcesDirectory();
@OutputDirectory
public abstract DirectoryProperty getOutputDirectory();
@Input
public abstract ListProperty<ArchitectureRules> getRules();
@Internal
public abstract Property<Boolean> getProhibitObjectsRequireNonNull();
}
@@ -1,74 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.architecture;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
/**
* {@link Plugin} for verifying a project's architecture.
*
* @author Andy Wilkinson
*/
public class ArchitecturePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerTasks(project));
}
private void registerTasks(Project project) {
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
List<TaskProvider<ArchitectureCheck>> architectureChecks = new ArrayList<>();
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
if (sourceSet.getName().contains("test")) {
// skip test source sets.
continue;
}
TaskProvider<ArchitectureCheck> checkArchitecture = project.getTasks()
.register(taskName(sourceSet), ArchitectureCheck.class,
(task) -> {
task.setClasses(sourceSet.getOutput().getClassesDirs());
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
task.dependsOn(sourceSet.getProcessResourcesTaskName());
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
+ " source set.");
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
});
architectureChecks.add(checkArchitecture);
}
if (!architectureChecks.isEmpty()) {
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
checkTask.configure((check) -> check.dependsOn(architectureChecks));
}
}
private static String taskName(SourceSet sourceSet) {
return "checkArchitecture"
+ sourceSet.getName().substring(0, 1).toUpperCase()
+ sourceSet.getName().substring(1);
}
}
@@ -1,117 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.architecture;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.library.dependencies.SliceAssignment;
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import java.util.List;
public enum ArchitectureRules {
ALL_PACKAGES_SHOULD_BE_FREE_OF_TANGLES(allPackagesShouldBeFreeOfTangles()),
NO_CLASSES_SHOULD_CALL_STRING_TO_LOWER_CASE_WITHOUT_LOCALE(noClassesShouldCallStringToLowerCaseWithoutLocale()),
NO_CLASSES_SHOULD_CALL_STRING_TO_UPPER_CASE_WITHOUT_LOCALE(noClassesShouldCallStringToUpperCaseWithoutLocale()),
CLASSES_SHOULD_NOT_IMPORT_FORBIDDEN_TYPES(classesShouldNotImportForbiddenTypes()),
JAVA_CLASSES_SHOULD_NOT_IMPORT_KOTLIN_ANNOTATIONS(javaClassesShouldNotImportKotlinAnnotations())
;
private final ArchRule archRule;
public ArchRule archRule() {
return this.archRule;
}
private ArchitectureRules(ArchRule archRule) {
this.archRule = archRule;
}
static ArchRule allPackagesShouldBeFreeOfTangles() {
return SlicesRuleDefinition.slices()
.assignedFrom(new SpringSlices()).should().beFreeOfCycles();
}
static ArchRule noClassesShouldCallStringToLowerCaseWithoutLocale() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod(String.class, "toLowerCase")
.because("String.toLowerCase(Locale.ROOT) should be used instead");
}
static ArchRule noClassesShouldCallStringToUpperCaseWithoutLocale() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod(String.class, "toUpperCase")
.because("String.toUpperCase(Locale.ROOT) should be used instead");
}
static ArchRule classesShouldNotImportForbiddenTypes() {
return ArchRuleDefinition.noClasses()
.should().dependOnClassesThat()
.haveFullyQualifiedName("reactor.core.support.Assert")
.orShould().dependOnClassesThat()
.haveFullyQualifiedName("org.slf4j.LoggerFactory")
.orShould().dependOnClassesThat()
.haveFullyQualifiedName("org.springframework.lang.NonNull")
.orShould().dependOnClassesThat()
.haveFullyQualifiedName("org.springframework.lang.Nullable");
}
static ArchRule javaClassesShouldNotImportKotlinAnnotations() {
return ArchRuleDefinition.noClasses()
.that(new DescribedPredicate<JavaClass>("is not a Kotlin class") {
@Override
public boolean test(JavaClass javaClass) {
return javaClass.getSourceCodeLocation()
.getSourceFileName().endsWith(".java");
}
}
)
.should().dependOnClassesThat()
.resideInAnyPackage("org.jetbrains.annotations..")
.allowEmptyShould(true);
}
static class SpringSlices implements SliceAssignment {
private final List<String> ignoredPackages = List.of("org.springframework.asm",
"org.springframework.cglib",
"org.springframework.javapoet",
"org.springframework.objenesis");
@Override
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
String packageName = javaClass.getPackageName();
for (String ignoredPackage : ignoredPackages) {
if (packageName.startsWith(ignoredPackage)) {
return SliceIdentifier.ignore();
}
}
return SliceIdentifier.of("spring framework");
}
@Override
public String getDescription() {
return "Spring Framework Slices";
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import org.gradle.api.attributes.Usage;
import org.gradle.api.attributes.java.TargetJvmVersion;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.jvm.JvmTestSuite;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.testing.Test;
import org.gradle.testing.base.TestingExtension;
@@ -53,7 +52,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
TaskProvider<Test> agentTest = project.getTasks().register(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
test.useJUnitPlatform(options -> {
options.includeTags("RuntimeHintsTests");
});
@@ -64,9 +63,8 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
});
project.getTasks().named("check", task -> task.dependsOn(agentTest));
project.getDependencies().add(CONFIGURATION_NAME,
project.getDependencyFactory().createProjectDependency(":spring-core-test"));
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
});
}
@@ -1,139 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import javax.inject.Inject;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.java.archives.Attributes;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
/**
* @author Cedric Champeau
* @author Brian Clozel
*/
public abstract class MultiReleaseExtension {
private final TaskContainer tasks;
private final SourceSetContainer sourceSets;
private final DependencyHandler dependencies;
private final ObjectFactory objects;
private final ConfigurationContainer configurations;
@Inject
public MultiReleaseExtension(SourceSetContainer sourceSets,
ConfigurationContainer configurations,
TaskContainer tasks,
DependencyHandler dependencies,
ObjectFactory objectFactory) {
this.sourceSets = sourceSets;
this.configurations = configurations;
this.tasks = tasks;
this.dependencies = dependencies;
this.objects = objectFactory;
}
public void releaseVersions(int... javaVersions) {
releaseVersions("src/main/", "src/test/", javaVersions);
}
private void releaseVersions(String mainSourceDirectory, String testSourceDirectory, int... javaVersions) {
for (int javaVersion : javaVersions) {
addLanguageVersion(javaVersion, mainSourceDirectory, testSourceDirectory);
}
}
private void addLanguageVersion(int javaVersion, String mainSourceDirectory, String testSourceDirectory) {
String javaN = "java" + javaVersion;
SourceSet langSourceSet = sourceSets.create(javaN, srcSet -> srcSet.getJava().srcDir(mainSourceDirectory + javaN));
SourceSet testSourceSet = sourceSets.create(javaN + "Test", srcSet -> srcSet.getJava().srcDir(testSourceDirectory + javaN));
SourceSet sharedSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet sharedTestSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
FileCollection mainClasses = objects.fileCollection().from(sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs());
dependencies.add(javaN + "Implementation", mainClasses);
tasks.named(langSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
task.getOptions().getRelease().set(javaVersion)
);
tasks.named(testSourceSet.getCompileJavaTaskName(), JavaCompile.class, task ->
task.getOptions().getRelease().set(javaVersion)
);
TaskProvider<Test> testTask = createTestTask(javaVersion, testSourceSet, sharedTestSourceSet, langSourceSet, sharedSourceSet);
tasks.named("check", task -> task.dependsOn(testTask));
configureMultiReleaseJar(javaVersion, langSourceSet);
}
private TaskProvider<Test> createTestTask(int javaVersion, SourceSet testSourceSet, SourceSet sharedTestSourceSet, SourceSet langSourceSet, SourceSet sharedSourceSet) {
Configuration testImplementation = configurations.getByName(testSourceSet.getImplementationConfigurationName());
testImplementation.extendsFrom(configurations.getByName(sharedTestSourceSet.getImplementationConfigurationName()));
Configuration testCompileOnly = configurations.getByName(testSourceSet.getCompileOnlyConfigurationName());
testCompileOnly.extendsFrom(configurations.getByName(sharedTestSourceSet.getCompileOnlyConfigurationName()));
testCompileOnly.getDependencies().add(dependencies.create(langSourceSet.getOutput().getClassesDirs()));
testCompileOnly.getDependencies().add(dependencies.create(sharedSourceSet.getOutput().getClassesDirs()));
Configuration testRuntimeClasspath = configurations.getByName(testSourceSet.getRuntimeClasspathConfigurationName());
// so here's the deal. MRjars are JARs! Which means that to execute tests, we need
// the JAR on classpath, not just classes + resources as Gradle usually does
testRuntimeClasspath.getAttributes()
.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
TaskProvider<Test> testTask = tasks.register("java" + javaVersion + "Test", Test.class, test -> {
test.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
ConfigurableFileCollection testClassesDirs = objects.fileCollection();
testClassesDirs.from(testSourceSet.getOutput());
testClassesDirs.from(sharedTestSourceSet.getOutput());
test.setTestClassesDirs(testClassesDirs);
ConfigurableFileCollection classpath = objects.fileCollection();
// must put the MRJar first on classpath
classpath.from(tasks.named("jar"));
// then we put the specific test sourceset tests, so that we can override
// the shared versions
classpath.from(testSourceSet.getOutput());
// then we add the shared tests
classpath.from(sharedTestSourceSet.getRuntimeClasspath());
test.setClasspath(classpath);
});
return testTask;
}
private void configureMultiReleaseJar(int version, SourceSet languageSourceSet) {
tasks.named("jar", Jar.class, jar -> {
jar.into("META-INF/versions/" + version, s -> s.from(languageSourceSet.getOutput()));
Attributes attributes = jar.getManifest().getAttributes();
attributes.put("Multi-Release", "true");
});
}
}
@@ -1,76 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import javax.inject.Inject;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaToolchainService;
/**
* A plugin which adds support for building multi-release jars
* with Gradle.
* @author Cedric Champeau
* @author Brian Clozel
* @see <a href="https://github.com/melix/mrjar-gradle-plugin">original project</a>
*/
public class MultiReleaseJarPlugin implements Plugin<Project> {
public static String VALIDATE_JAR_TASK_NAME = "validateMultiReleaseJar";
@Inject
protected JavaToolchainService getToolchains() {
throw new UnsupportedOperationException();
}
public void apply(Project project) {
project.getPlugins().apply(JavaPlugin.class);
ExtensionContainer extensions = project.getExtensions();
JavaPluginExtension javaPluginExtension = extensions.getByType(JavaPluginExtension.class);
ConfigurationContainer configurations = project.getConfigurations();
TaskContainer tasks = project.getTasks();
DependencyHandler dependencies = project.getDependencies();
ObjectFactory objects = project.getObjects();
extensions.create("multiRelease", MultiReleaseExtension.class,
javaPluginExtension.getSourceSets(),
configurations,
tasks,
dependencies,
objects);
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
});
tasks.named("check", task -> task.dependsOn(validateJarTask));
}
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.jvm.toolchain.JavaToolchainService;
import java.util.List;
import javax.inject.Inject;
@CacheableTask
public abstract class MultiReleaseJarValidateTask extends JavaExec {
public MultiReleaseJarValidateTask() {
getMainModule().set("jdk.jartool");
getArgumentProviders().add(() -> List.of("--validate", "--file", getJar().get().getAsFile().getAbsolutePath()));
}
@Inject
protected abstract JavaToolchainService getJavaToolchainService();
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
public abstract RegularFileProperty getJar();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
}
@OutputDirectory
public DirectoryProperty getOutputDirectory() {
DirectoryProperty getOutputDirectory() {
return this.outputDirectory;
}
@@ -1,182 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.multirelease;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link MultiReleaseJarPlugin}
*/
public class MultiReleaseJarPluginTests {
private File projectDir;
private File buildFile;
private File propertiesFile;
@BeforeEach
void setup(@TempDir File projectDir) {
this.projectDir = projectDir;
this.buildFile = new File(this.projectDir, "build.gradle");
this.propertiesFile = new File(this.projectDir, "gradle.properties");
}
@Test
void configureSourceSets() throws IOException {
writeBuildFile("""
plugins {
id 'java'
id 'org.springframework.build.multiReleaseJar'
}
multiRelease { releaseVersions 21, 24 }
task printSourceSets {
doLast {
sourceSets.all { println it.name }
}
}
""");
BuildResult buildResult = runGradle("printSourceSets");
assertThat(buildResult.getOutput()).contains("main", "test", "java21", "java21Test", "java24", "java24Test");
}
@Test
void configureToolchainReleaseVersion() throws IOException {
writeBuildFile("""
plugins {
id 'java'
id 'org.springframework.build.multiReleaseJar'
}
multiRelease { releaseVersions 21 }
task printReleaseVersion {
doLast {
tasks.all { println it.name }
tasks.named("compileJava21Java") {
println "compileJava21Java releaseVersion: ${it.options.release.get()}"
}
tasks.named("compileJava21TestJava") {
println "compileJava21TestJava releaseVersion: ${it.options.release.get()}"
}
}
}
""");
BuildResult buildResult = runGradle("printReleaseVersion");
assertThat(buildResult.getOutput()).contains("compileJava21Java releaseVersion: 21")
.contains("compileJava21TestJava releaseVersion: 21");
}
@Test
void packageInJar() throws IOException {
writeBuildFile("""
plugins {
id 'java'
id 'org.springframework.build.multiReleaseJar'
}
version = '1.2.3'
multiRelease { releaseVersions 17 }
""");
writeClass("src/main/java17", "Main.java", """
public class Main {}
""");
BuildResult buildResult = runGradle("assemble");
File file = new File(this.projectDir, "/build/libs/" + this.projectDir.getName() + "-1.2.3.jar");
assertThat(file).exists();
try (JarFile jar = new JarFile(file)) {
Attributes mainAttributes = jar.getManifest().getMainAttributes();
assertThat(mainAttributes.getValue("Multi-Release")).isEqualTo("true");
assertThat(jar.entries().asIterator()).toIterable()
.anyMatch(entry -> entry.getName().equals("META-INF/versions/17/Main.class"));
}
}
@Test
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
void validateJar() throws IOException {
writeBuildFile("""
plugins {
id 'java'
id 'org.springframework.build.multiReleaseJar'
}
version = '1.2.3'
tasks.withType(JavaCompile).configureEach {
options.release = 11
}
multiRelease { releaseVersions 17 }
""");
writeGradleProperties("""
org.gradle.jvmargs=-Duser.language=en
""");
writeClass("src/main/java17", "Main.java", """
public class Main {
public void method() {}
}
""");
writeClass("src/main/java", "Main.java", """
public class Main {}
""");
assertThatThrownBy(() ->runGradle("validateMultiReleaseJar"))
.isInstanceOf(UnexpectedBuildFailure.class)
.hasMessageContaining("entry: META-INF/versions/17/Main.class, contains a class with different api from earlier version");
}
private void writeBuildFile(String buildContent) throws IOException {
try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
out.print(buildContent);
}
}
private void writeGradleProperties(String properties) throws IOException {
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
out.print(properties);
}
}
private void writeClass(String path, String fileName, String fileContent) throws IOException {
Path folder = this.projectDir.toPath().resolve(path);
Files.createDirectories(folder);
Path filePath = folder.resolve(fileName);
Files.createFile(filePath);
Files.writeString(filePath, fileContent);
}
private BuildResult runGradle(String... args) {
return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build();
}
}
+22 -30
View File
@@ -1,7 +1,6 @@
plugins {
id 'java-platform'
id 'io.freefair.aggregate-javadoc' version '8.13.1'
id 'org.jetbrains.dokka'
id 'io.freefair.aggregate-javadoc' version '8.3'
}
description = "Spring Framework API Docs"
@@ -10,24 +9,18 @@ apply from: "${rootDir}/gradle/publications.gradle"
repositories {
maven {
url = "https://repo.spring.io/release"
url "https://repo.spring.io/release"
}
}
dependencies {
rootProject.ext.moduleProjects.each { moduleProject ->
javadoc project(moduleProject.path)
moduleProjects.each { moduleProject ->
javadoc moduleProject
}
}
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
javadoc {
javadocTool.set(javaToolchains.javadocToolFor({
languageVersion = JavaLanguageVersion.of(25)
}))
title = "${rootProject.description} ${version} API"
failOnError = true
options {
encoding = "UTF-8"
memberLevel = JavadocMemberLevel.PROTECTED
@@ -38,37 +31,36 @@ javadoc {
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
splitIndex = true
links(rootProject.ext.javadocLinks)
// Check for 'syntax' and 'reference' during linting.
addBooleanOption('Xdoclint:syntax,reference', true)
// Change modularity mismatch from warn to info.
// See https://github.com/spring-projects/spring-framework/issues/27497
addStringOption("-link-modularity-mismatch", "info")
// Fail build on Javadoc warnings.
addBooleanOption('Werror', true)
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
addBooleanOption('Werror', true) // fail build on Javadoc warnings
}
maxMemory = "1024m"
doFirst {
classpath += files(
// ensure the javadoc process can resolve types compiled from .aj sources
springAspectsOutput
// ensure the javadoc process can resolve types compiled from .aj sources
project(":spring-aspects").sourceSets.main.output
)
classpath += files(rootProject.ext.moduleProjects.collect { it.sourceSets.main.compileClasspath })
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
}
}
dokka {
moduleName = "spring-framework"
dokkaPublications.html {
outputDirectory = project.java.docsDir.dir("kdoc-api")
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
/**
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
*/
rootProject.tasks.dokkaHtmlMultiModule.configure {
dependsOn {
tasks.named("javadoc")
}
moduleName.set("spring-framework")
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
}
/**
* Zip all Java docs (javadoc & kdoc) into a single archive
*/
tasks.register('docsZip', Zip) {
dependsOn = ['javadoc', 'dokkaGenerate']
dependsOn = ['javadoc', rootProject.tasks.dokkaHtmlMultiModule]
group = "distribution"
description = "Builds -${archiveClassifier} archive containing api and reference " +
"for deployment at https://docs.spring.io/spring-framework/docs/."
@@ -81,7 +73,7 @@ tasks.register('docsZip', Zip) {
from(javadoc) {
into "javadoc-api"
}
from(project.java.docsDir.dir("kdoc-api")) {
from(rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
into "kdoc-api"
}
}
@@ -95,8 +87,8 @@ tasks.register('schemaZip', Zip) {
archiveClassifier.set("schema")
description = "Builds -${archiveClassifier} archive containing all " +
"XSDs for deployment at https://springframework.org/schema."
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
rootProject.ext.moduleProjects.each { module ->
duplicatesStrategy DuplicatesStrategy.EXCLUDE
moduleProjects.each { module ->
def Properties schemas = new Properties();
module.sourceSets.main.resources.find {
+1 -1
View File
@@ -8,7 +8,7 @@ group = "org.springframework"
dependencies {
constraints {
parent.moduleProjects.sort { "$it.name" }.each {
api project(it.path)
api it
}
}
}
+3 -5
View File
@@ -13,10 +13,8 @@ content:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
# tags: include all releases from 6.2.0 to 9.*.*.
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
branches: ['main', '{6..9}.+({1..9}).x']
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
extensions:
@@ -38,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
+2 -4
View File
@@ -6,7 +6,7 @@ nav:
ext:
collector:
run:
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g" :framework-docs:generateAntoraResources
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :framework-docs:generateAntoraResources
local: true
scan:
dir: ./build/generated-antora-resources
@@ -21,7 +21,6 @@ asciidoc:
table-stripes: 'odd'
include-java: 'example$docs-src/main/java/org/springframework/docs'
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
include-xml: 'example$docs-src/main/resources/org/springframework/docs'
spring-site: 'https://spring.io'
spring-site-blog: '{spring-site}/blog'
spring-site-cve: "{spring-site}/security"
@@ -42,8 +41,7 @@ asciidoc:
spring-framework-reference: '{spring-framework-docs-root}/{spring-version}/reference'
#
# Other Spring portfolio projects
spring-boot-docs: '{docs-site}/spring-boot'
spring-boot-docs-ref: '{spring-boot-docs}/reference'
spring-boot-docs: '{docs-site}/spring-boot/docs/current/reference/html'
spring-boot-issues: '{spring-github-org}/spring-boot/issues'
# TODO add more projects / links or just build up on {docs-site}?
# TODO rename the below using new conventions
+14 -54
View File
@@ -1,6 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
id 'kotlin'
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
@@ -15,15 +12,11 @@ apply from: "${rootDir}/gradle/publications.gradle"
antora {
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
environment = [
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
]
}
node {
version = '24.15.0'
}
tasks.named("generateAntoraYml") {
asciidocAttributes = project.provider( {
return ["spring-version": project.version ]
@@ -44,55 +37,22 @@ javadoc {
repositories {
maven {
url = "https://repo.spring.io/release"
url "https://repo.spring.io/release"
}
}
// To avoid a redeclaration error with Kotlin compiler and set the JVM target
tasks.withType(KotlinCompilationTask.class).configureEach {
javaSources.from = []
compilerOptions.jvmTarget = JvmTarget.JVM_17
compilerOptions.freeCompilerArgs.addAll(
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
)
}
dependencies {
implementation(project(":spring-aspects"))
implementation(project(":spring-context"))
implementation(project(":spring-context-support"))
implementation(project(":spring-core-test"))
implementation(project(":spring-jdbc"))
implementation(project(":spring-jms"))
implementation(project(":spring-test"))
implementation(project(":spring-web"))
implementation(project(":spring-webflux"))
implementation(project(":spring-webmvc"))
implementation(project(":spring-websocket"))
api(project(":spring-context"))
api(project(":spring-jdbc"))
api(project(":spring-jms"))
api(project(":spring-web"))
api(project(":spring-webflux"))
implementation("com.github.ben-manes.caffeine:caffeine")
implementation("com.mchange:c3p0:0.9.5.5")
implementation("com.oracle.database.jdbc:ojdbc11")
implementation("io.micrometer:context-propagation")
implementation("io.projectreactor.netty:reactor-netty-http")
implementation("jakarta.jms:jakarta.jms-api")
implementation("jakarta.servlet:jakarta.servlet-api")
implementation("jakarta.resource:jakarta.resource-api")
implementation("jakarta.validation:jakarta.validation-api")
implementation("jakarta.websocket:jakarta.websocket-client-api")
implementation("javax.cache:cache-api")
implementation("org.apache.activemq:activemq-ra:6.1.2")
implementation("org.apache.commons:commons-dbcp2:2.11.0")
implementation("org.apache.groovy:groovy-templates")
implementation("org.aspectj:aspectjweaver")
api("com.oracle.database.jdbc:ojdbc11")
api("jakarta.jms:jakarta.jms-api")
api("jakarta.servlet:jakarta.servlet-api")
api("org.jetbrains.kotlin:kotlin-stdlib")
implementation(project(":spring-core-test"))
implementation("org.assertj:assertj-core")
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
implementation("org.freemarker:freemarker")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.junit.jupiter:junit-jupiter-api")
implementation("tools.jackson.core:jackson-databind")
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 63 KiB

@@ -17,7 +17,6 @@
class="st5"
id="svg5499"
version="1.1"
font-family="Helvetica, Arial, sans-serif"
inkscape:version="0.91 r13725"
sodipodi:docname="mvc-splitted-contexts.svg"
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
@@ -37,7 +36,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path9164" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -48,7 +47,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path8836" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -59,7 +58,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path8520" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -70,7 +69,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path8216" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -81,7 +80,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7924" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -92,7 +91,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7644" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -103,7 +102,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7375" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -114,7 +113,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7119" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -125,7 +124,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6875" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -136,7 +135,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6643" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -147,7 +146,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6423" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -158,7 +157,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6215" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -169,7 +168,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6019" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -180,7 +179,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5835" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -191,7 +190,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5663" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -202,7 +201,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5503" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -213,7 +212,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5355" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -224,7 +223,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5219" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -235,7 +234,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5095" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -246,7 +245,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path4983" /></marker><marker
inkscape:stockid="Arrow2Mend"
orient="auto"
@@ -257,7 +256,7 @@
inkscape:isstock="true"
inkscape:collect="always"><path
id="path7394"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
inkscape:stockid="Arrow2Lend"
@@ -268,7 +267,7 @@
style="overflow:visible;"
inkscape:isstock="true"><path
id="path8123"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
inkscape:stockid="Arrow1Mend"
@@ -280,7 +279,7 @@
inkscape:isstock="true"><path
id="path8035"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
inkscape:stockid="Arrow1Mend"
orient="auto"
@@ -291,7 +290,7 @@
inkscape:isstock="true"><path
id="path7959"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
inkscape:stockid="Arrow1Mend"
orient="auto"
@@ -302,7 +301,7 @@
inkscape:isstock="true"><path
id="path7376"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
inkscape:stockid="Arrow2Lend"
orient="auto"
@@ -312,7 +311,7 @@
style="overflow:visible;"
inkscape:isstock="true"><path
id="path7388"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
inkscape:stockid="Arrow1Lend"
@@ -324,7 +323,7 @@
inkscape:isstock="true"><path
id="path7370"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
@@ -353,9 +352,9 @@
v:viewMarkup="false" /><style
type="text/css"
id="style5501"><![CDATA[
.st1 {fill:#969696;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
.st4 {font-size:0.333333em;font-weight:normal}
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
]]></style><g
@@ -364,22 +363,20 @@
v:groupContext="foregroundPage"
id="g5503"
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6599"
width="382.68423"
height="146.09897"
x="87.884865"
y="148.26482"
rx="8" /><v:userDefs><v:ud
y="148.26482" /><v:userDefs><v:ud
v:nameU="SchemeName"
v:val="VT4(Default)" /></v:userDefs><rect
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect5725"
width="464.31128"
height="374.11411"
x="41.684383"
y="112.3262"
rx="8" /><title
y="112.3262" /><title
id="title5505">Page-1</title><v:pageProperties
v:drawingScale="0.0393701"
v:pageScale="0.0393701"
@@ -388,16 +385,15 @@
v:shadowOffsetY="-8.50394" /><v:layer
v:name="Connector"
v:index="0" /><rect
style="fill:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect5715"
width="322.8194"
height="43.63184"
x="119.95335"
y="-135.66222"
transform="scale(1,-1)"
rx="8" /><text
transform="scale(1,-1)" /><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="168.843"
y="124.32391"
id="text5717"
@@ -405,12 +401,12 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan5719"
x="172.843"
x="168.843"
y="124.32391"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="89.770851"
y="181.20923"
id="text6589"
@@ -418,12 +414,12 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6591"
x="106.770851"
x="89.770851"
y="181.20923"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="260.00443"
y="198.41273"
id="text6593"
@@ -433,39 +429,36 @@
id="tspan6595"
x="260.00443"
y="198.41273"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
sodipodi:role="line"
x="260.00443"
y="212.83057"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
id="tspan6597">and other web-related beans)</tspan></text>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6620"
width="82.040657"
height="36.72575"
x="114.52653"
y="-259.43161"
transform="scale(1,-1)"
rx="4" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
transform="scale(1,-1)" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6622"
width="87.843979"
height="36.697304"
x="223.39864"
y="-287.19809"
transform="scale(1,-1)"
rx="4" /><rect
transform="scale(1,-1)" /><rect
transform="scale(1,-1)"
y="-264.81918"
x="117.92834"
height="36.72575"
width="82.040657"
id="rect6614"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><text
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="121.24728"
y="260.14957"
id="text6616"
@@ -473,20 +466,20 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6618"
x="124.24728"
x="121.24728"
y="260.14957"
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
style="font-size:11.53426838px">Controllers</tspan></text>
<text
transform="scale(1.0403984,0.96117025)"
sodipodi:linespacing="125%"
id="text6624"
y="282.70709"
x="219.61203"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:11.53426838px;fill:#333333"
style="font-size:11.53426838px"
y="282.70709"
x="221.61203"
x="219.61203"
id="tspan6626"
sodipodi:role="line">ViewResolver</tspan></text>
<rect
@@ -496,10 +489,9 @@
height="36.577778"
width="114.4539"
id="rect6628"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><text
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="327.51276"
y="255.28464"
id="text6630"
@@ -507,27 +499,26 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6632"
x="332.51276"
x="327.51276"
y="255.28464"
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
style="font-size:11.53426838px">HandlerMapping</tspan></text>
<rect
y="338.69724"
x="87.803261"
height="121.5683"
width="382.84744"
id="rect6634"
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="8" /><text
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
transform="scale(1.0403984,0.96117025)"
sodipodi:linespacing="125%"
id="text6636"
y="376.61673"
x="108.61351"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
y="376.61673"
x="113.61351"
x="108.61351"
id="tspan6638"
sodipodi:role="line">Root WebApplicationContext</tspan></text>
<text
@@ -536,10 +527,10 @@
id="text6640"
y="395.35812"
x="260.93863"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan6644"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
y="395.35812"
x="260.93863"
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
@@ -550,47 +541,43 @@
height="36.72575"
width="82.040657"
id="rect6648"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6650"
width="82.040657"
height="36.72575"
x="167.73116"
y="-445.45563"
transform="scale(1,-1)"
rx="4" /><text
transform="scale(1,-1)" /><text
transform="scale(1.0403984,0.96117025)"
sodipodi:linespacing="125%"
id="text6652"
y="448.55054"
x="175.87148"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:11.53426838px;fill:#333333"
style="font-size:11.53426838px"
y="448.55054"
x="178.87148"
x="175.87148"
id="tspan6654"
sodipodi:role="line">Services</tspan></text>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6656"
width="86.393044"
height="36.704323"
x="306.86328"
y="-439.60837"
transform="scale(1,-1)"
rx="4" /><rect
transform="scale(1,-1)" /><rect
transform="scale(1,-1)"
y="-444.99475"
x="310.26624"
height="36.701977"
width="86.876686"
id="rect6658"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><text
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="305.30771"
y="448.55054"
id="text6660"
@@ -598,20 +585,20 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6662"
x="308.30771"
x="305.30771"
y="448.55054"
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
style="font-size:11.53426838px">Repositories</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
id="path7643"
inkscape:connector-curvature="0" /><path
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
id="path7645"
inkscape:connector-curvature="0" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="352.55331"
y="333.03622"
id="text4963"
@@ -620,6 +607,6 @@
sodipodi:role="line"
x="352.55331"
y="333.03622"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
id="tspan4965">Delegates if no bean found</tspan></text>
</g></svg>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+92 -114
View File
@@ -32,7 +32,6 @@
**** xref:core/beans/java/bean-annotation.adoc[]
**** xref:core/beans/java/configuration-annotation.adoc[]
**** xref:core/beans/java/composing-configuration-classes.adoc[]
**** xref:core/beans/java/programmatic-bean-registration.adoc[]
*** xref:core/beans/environment.adoc[]
*** xref:core/beans/context-load-time-weaver.adoc[]
*** xref:core/beans/context-introduction.adoc[]
@@ -40,8 +39,8 @@
** xref:core/resources.adoc[]
** xref:core/validation.adoc[]
*** xref:core/validation/validator.adoc[]
*** xref:core/validation/data-binding.adoc[]
*** xref:core/validation/error-code-resolution.adoc[]
*** xref:core/validation/beans-beans.adoc[]
*** xref:core/validation/conversion.adoc[]
*** xref:core/validation/convert.adoc[]
*** xref:core/validation/format.adoc[]
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
@@ -61,7 +60,6 @@
**** xref:core/expressions/language-ref/constructors.adoc[]
**** xref:core/expressions/language-ref/variables.adoc[]
**** xref:core/expressions/language-ref/functions.adoc[]
**** xref:core/expressions/language-ref/varargs.adoc[]
**** xref:core/expressions/language-ref/bean-references.adoc[]
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
@@ -100,14 +98,95 @@
*** xref:core/aop-api/autoproxy.adoc[]
*** xref:core/aop-api/targetsource.adoc[]
*** xref:core/aop-api/extensibility.adoc[]
** xref:core/resilience.adoc[]
** xref:core/null-safety.adoc[]
** xref:core/databuffer-codec.adoc[]
** xref:core/spring-jcl.adoc[]
** xref:core/aot.adoc[]
** xref:core/appendix.adoc[]
*** xref:core/appendix/xsd-schemas.adoc[]
*** xref:core/appendix/xml-custom.adoc[]
*** xref:core/appendix/application-startup-steps.adoc[]
* xref:testing.adoc[]
** xref:testing/introduction.adoc[]
** xref:testing/unit.adoc[]
** xref:testing/integration.adoc[]
** xref:testing/support-jdbc.adoc[]
** xref:testing/testcontext-framework.adoc[]
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
*** xref:testing/testcontext-framework/tel-config.adoc[]
*** xref:testing/testcontext-framework/application-events.adoc[]
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
*** xref:testing/testcontext-framework/ctx-management.adoc[]
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
*** xref:testing/testcontext-framework/fixture-di.adoc[]
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
*** xref:testing/testcontext-framework/tx.adoc[]
*** xref:testing/testcontext-framework/executing-sql.adoc[]
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
*** xref:testing/testcontext-framework/support-classes.adoc[]
*** xref:testing/testcontext-framework/aot.adoc[]
** xref:testing/webtestclient.adoc[]
** xref:testing/spring-mvc-test-framework.adoc[]
*** xref:testing/spring-mvc-test-framework/server.adoc[]
*** xref:testing/spring-mvc-test-framework/server-static-imports.adoc[]
*** xref:testing/spring-mvc-test-framework/server-setup-options.adoc[]
*** xref:testing/spring-mvc-test-framework/server-setup-steps.adoc[]
*** xref:testing/spring-mvc-test-framework/server-performing-requests.adoc[]
*** xref:testing/spring-mvc-test-framework/server-defining-expectations.adoc[]
*** xref:testing/spring-mvc-test-framework/async-requests.adoc[]
*** xref:testing/spring-mvc-test-framework/vs-streaming-response.adoc[]
*** xref:testing/spring-mvc-test-framework/server-filters.adoc[]
*** xref:testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc[]
*** xref:testing/spring-mvc-test-framework/server-resources.adoc[]
*** xref:testing/spring-mvc-test-framework/server-htmlunit.adoc[]
**** xref:testing/spring-mvc-test-framework/server-htmlunit/why.adoc[]
**** xref:testing/spring-mvc-test-framework/server-htmlunit/mah.adoc[]
**** xref:testing/spring-mvc-test-framework/server-htmlunit/webdriver.adoc[]
**** xref:testing/spring-mvc-test-framework/server-htmlunit/geb.adoc[]
** xref:testing/spring-mvc-test-client.adoc[]
** xref:testing/appendix.adoc[]
*** xref:testing/annotations.adoc[]
**** xref:testing/annotations/integration-standard.adoc[]
**** xref:testing/annotations/integration-spring.adoc[]
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
**** xref:testing/annotations/integration-junit4.adoc[]
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
**** xref:testing/annotations/integration-meta.adoc[]
*** xref:testing/resources.adoc[]
* xref:data-access.adoc[]
** xref:data-access/transaction.adoc[]
*** xref:data-access/transaction/motivation.adoc[]
@@ -162,10 +241,10 @@
**** xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[]
**** xref:web/webmvc/mvc-servlet/viewresolver.adoc[]
**** xref:web/webmvc/mvc-servlet/localeresolver.adoc[]
**** xref:web/webmvc/mvc-servlet/themeresolver.adoc[]
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
*** xref:web/webmvc/filters.adoc[]
*** xref:web/webmvc/message-converters.adoc[]
*** xref:web/webmvc/mvc-controller.adoc[]
**** xref:web/webmvc/mvc-controller/ann.adoc[]
**** xref:web/webmvc/mvc-controller/ann-requestmapping.adoc[]
@@ -197,10 +276,7 @@
*** xref:web/webmvc-functional.adoc[]
*** xref:web/webmvc/mvc-uri-building.adoc[]
*** xref:web/webmvc/mvc-ann-async.adoc[]
*** xref:web/webmvc/mvc-range.adoc[]
*** xref:web/webmvc/mvc-data-binding.adoc[]
*** xref:web/webmvc-cors.adoc[]
*** xref:web/webmvc-versioning.adoc[]
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
*** xref:web/webmvc/mvc-security.adoc[]
*** xref:web/webmvc/mvc-caching.adoc[]
@@ -209,7 +285,6 @@
**** xref:web/webmvc-view/mvc-freemarker.adoc[]
**** xref:web/webmvc-view/mvc-groovymarkup.adoc[]
**** xref:web/webmvc-view/mvc-script.adoc[]
**** xref:web/webmvc-view/mvc-fragments.adoc[]
**** xref:web/webmvc-view/mvc-jsp.adoc[]
**** xref:web/webmvc-view/mvc-feeds.adoc[]
**** xref:web/webmvc-view/mvc-document.adoc[]
@@ -229,7 +304,6 @@
**** xref:web/webmvc/mvc-config/static-resources.adoc[]
**** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[]
**** xref:web/webmvc/mvc-config/path-matching.adoc[]
**** xref:web/webmvc/mvc-config/api-version.adoc[]
**** xref:web/webmvc/mvc-config/advanced-java.adoc[]
**** xref:web/webmvc/mvc-config/advanced-xml.adoc[]
*** xref:web/webmvc/mvc-http2.adoc[]
@@ -262,6 +336,7 @@
**** xref:web/websocket/stomp/configuration-performance.adoc[]
**** xref:web/websocket/stomp/stats.adoc[]
**** xref:web/websocket/stomp/testing.adoc[]
** xref:web/integration.adoc[]
* xref:web-reactive.adoc[]
** xref:web/webflux.adoc[]
*** xref:web/webflux/new-framework.adoc[]
@@ -295,10 +370,7 @@
**** xref:web/webflux/controller/ann-advice.adoc[]
*** xref:web/webflux-functional.adoc[]
*** xref:web/webflux/uri-building.adoc[]
*** xref:web/webflux/range.adoc[]
*** xref:web/webflux/data-binding.adoc[]
*** xref:web/webflux-cors.adoc[]
*** xref:web/webflux-versioning.adoc[]
*** xref:web/webflux/ann-rest-exceptions.adoc[]
*** xref:web/webflux/security.adoc[]
*** xref:web/webflux/caching.adoc[]
@@ -315,105 +387,11 @@
*** xref:web/webflux-webclient/client-context.adoc[]
*** xref:web/webflux-webclient/client-synchronous.adoc[]
*** xref:web/webflux-webclient/client-testing.adoc[]
** xref:web/webflux-http-service-client.adoc[]
** xref:web/webflux-http-interface-client.adoc[]
** xref:web/webflux-websocket.adoc[]
** xref:web/webflux-test.adoc[]
** xref:rsocket.adoc[]
** xref:web/webflux-reactive-libraries.adoc[]
* xref:testing.adoc[]
** xref:testing/introduction.adoc[]
** xref:testing/unit.adoc[]
** xref:testing/integration.adoc[]
** xref:testing/support-jdbc.adoc[]
** xref:testing/testcontext-framework.adoc[]
*** xref:testing/testcontext-framework/key-abstractions.adoc[]
*** xref:testing/testcontext-framework/bootstrapping.adoc[]
*** xref:testing/testcontext-framework/tel-config.adoc[]
*** xref:testing/testcontext-framework/application-events.adoc[]
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
*** xref:testing/testcontext-framework/ctx-management.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/inheritance.adoc[]
**** xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[]
**** xref:testing/testcontext-framework/ctx-management/property-sources.adoc[]
**** xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[]
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
**** xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[]
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
*** xref:testing/testcontext-framework/fixture-di.adoc[]
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
*** xref:testing/testcontext-framework/tx.adoc[]
*** xref:testing/testcontext-framework/executing-sql.adoc[]
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
*** xref:testing/testcontext-framework/support-classes.adoc[]
*** xref:testing/testcontext-framework/aot.adoc[]
** xref:testing/webtestclient.adoc[]
** xref:testing/resttestclient.adoc[]
** xref:testing/mockmvc.adoc[]
*** xref:testing/mockmvc/overview.adoc[]
*** xref:testing/mockmvc/setup-options.adoc[]
*** xref:testing/mockmvc/hamcrest.adoc[]
**** xref:testing/mockmvc/hamcrest/static-imports.adoc[]
**** xref:testing/mockmvc/hamcrest/setup.adoc[]
**** xref:testing/mockmvc/hamcrest/setup-steps.adoc[]
**** xref:testing/mockmvc/hamcrest/requests.adoc[]
**** xref:testing/mockmvc/hamcrest/expectations.adoc[]
**** xref:testing/mockmvc/hamcrest/async-requests.adoc[]
**** xref:testing/mockmvc/hamcrest/vs-streaming-response.adoc[]
**** xref:testing/mockmvc/hamcrest/filters.adoc[]
*** xref:testing/mockmvc/assertj.adoc[]
**** xref:testing/mockmvc/assertj/setup.adoc[]
**** xref:testing/mockmvc/assertj/requests.adoc[]
**** xref:testing/mockmvc/assertj/assertions.adoc[]
**** xref:testing/mockmvc/assertj/integration.adoc[]
*** xref:testing/mockmvc/htmlunit.adoc[]
**** xref:testing/mockmvc/htmlunit/why.adoc[]
**** xref:testing/mockmvc/htmlunit/mah.adoc[]
**** xref:testing/mockmvc/htmlunit/webdriver.adoc[]
**** xref:testing/mockmvc/htmlunit/geb.adoc[]
*** xref:testing/mockmvc/vs-end-to-end-integration-tests.adoc[]
*** xref:testing/mockmvc/resources.adoc[]
** xref:testing/spring-mvc-test-client.adoc[]
** xref:testing/appendix.adoc[]
*** xref:testing/annotations.adoc[]
**** xref:testing/annotations/integration-standard.adoc[]
**** xref:testing/annotations/integration-spring.adoc[]
***** xref:testing/annotations/integration-spring/annotation-bootstrapwith.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contextconfiguration.adoc[]
***** xref:testing/annotations/integration-spring/annotation-webappconfiguration.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contexthierarchy.adoc[]
***** xref:testing/annotations/integration-spring/annotation-contextcustomizerfactories.adoc[]
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
***** xref:testing/annotations/integration-spring/annotation-commit.adoc[]
***** xref:testing/annotations/integration-spring/annotation-rollback.adoc[]
***** xref:testing/annotations/integration-spring/annotation-beforetransaction.adoc[]
***** xref:testing/annotations/integration-spring/annotation-aftertransaction.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sql.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlconfig.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
**** xref:testing/annotations/integration-junit4.adoc[]
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
**** xref:testing/annotations/integration-meta.adoc[]
*** xref:testing/resources.adoc[]
* xref:integration.adoc[]
** xref:integration/rest-clients.adoc[]
** xref:integration/jms.adoc[]
@@ -442,8 +420,8 @@
*** xref:integration/cache/plug.adoc[]
*** xref:integration/cache/specific-config.adoc[]
** xref:integration/observability.adoc[]
** xref:integration/aot-cache.adoc[]
** xref:integration/checkpoint-restore.adoc[]
** xref:integration/cds.adoc[]
** xref:integration/appendix.adoc[]
* xref:languages.adoc[]
** xref:languages/kotlin.adoc[]
@@ -452,14 +430,14 @@
*** xref:languages/kotlin/null-safety.adoc[]
*** xref:languages/kotlin/classes-interfaces.adoc[]
*** xref:languages/kotlin/annotations.adoc[]
*** xref:languages/kotlin/bean-registration-dsl.adoc[]
*** xref:languages/kotlin/bean-definition-dsl.adoc[]
*** xref:languages/kotlin/web.adoc[]
*** xref:languages/kotlin/coroutines.adoc[]
*** xref:languages/kotlin/spring-projects-in.adoc[]
*** xref:languages/kotlin/getting-started.adoc[]
*** xref:languages/kotlin/resources.adoc[]
** xref:languages/groovy.adoc[]
** xref:languages/dynamic.adoc[]
* xref:appendix.adoc[]
* {spring-framework-docs-root}/{spring-version}/javadoc-api/[Java API,window=_blank, role=link-external]
* {spring-framework-api-kdoc}/[Kotlin API,window=_blank, role=link-external]
* {spring-framework-wiki}[Wiki, window=_blank, role=link-external]
* {spring-framework-wiki}[Wiki]
@@ -74,17 +74,6 @@ expressions used in XML bean definitions, `@Value`, etc.
| The mode to use when compiling expressions for the
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
| `spring.expression.maxBigPowerBits`
| The default maximum number of bits permitted in the result of a `BigDecimal` or
`BigInteger` power operation within a
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expression.
| `spring.expression.maxOperations`
| The default maximum number of operations permitted during
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expression evaluation.
| `spring.getenv.ignore`
| Instructs Spring to ignore operating system environment variables if a Spring
`Environment` property -- for example, a placeholder in a configuration String -- isn't
@@ -92,11 +81,6 @@ resolvable otherwise. See
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
for details.
| `spring.http.response.flush.enabled`
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
| `spring.jdbc.getParameterType.ignore`
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
@@ -108,25 +92,11 @@ the repeated JNDI lookup overhead. See
{spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
for details.
| `spring.locking.strict`
| Instructs Spring to enforce strict locking during bean creation, rather than the mix of
strict and lenient locking that 6.2 applies by default. See
{spring-framework-api}++/beans/factory/support/DefaultListableBeanFactory.html#STRICT_LOCKING_PROPERTY_NAME++[`DefaultListableBeanFactory`]
for details.
| `spring.objenesis.ignore`
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
for details.
| `spring.placeholder.escapeCharacter.default`
| The default escape character for property placeholder support. If not set, `'\'` will
be used. Can be set to a custom escape character or an empty string to disable support
for an escape character. The default escape character be explicitly overridden in
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
for details.
| `spring.test.aot.processing.failOnError`
| A boolean flag that controls whether errors encountered during AOT processing in the
_Spring TestContext Framework_ should result in an exception that fails the overall process.
@@ -140,20 +110,11 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
| The maximum size of the context cache in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
| `spring.test.context.cache.pause`
| The pause mode for the context cache in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
| `spring.test.context.failure.threshold`
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
| `spring.test.extension.context.scope`
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
class hierarchies. See
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
| `spring.test.enclosing.configuration`
| The default _enclosing configuration inheritance mode_ to use if
`@NestedTestConfiguration` is not present on a test class. See
@@ -17,3 +17,14 @@ is also provided.
AOT processing can be used to optimize your application ahead-of-time. It is typically
used for native image deployment using GraalVM.
@@ -6,3 +6,7 @@ The previous chapter described the Spring's support for AOP with @AspectJ and sc
aspect definitions. In this chapter, we discuss the lower-level Spring AOP APIs. For common
applications, we recommend the use of Spring AOP with AspectJ pointcuts as described in the
previous chapter.
@@ -4,6 +4,7 @@
Now we can examine how Spring AOP handles advice.
[[aop-api-advice-lifecycle]]
== Advice Lifecycles
@@ -21,20 +22,22 @@ the advice adds state to the proxied object.
You can use a mix of shared and per-instance advice in the same AOP proxy.
[[aop-api-advice-types]]
== Advice Types in Spring
Spring provides several advice types and is extensible to support
arbitrary advice types. This section describes the basic concepts and standard advice types.
[[aop-api-advice-around]]
=== Interception Around Advice
The most fundamental advice type in Spring is _interception around advice_.
The most fundamental advice type in Spring is interception around advice.
Spring is compliant with the AOP Alliance interface for around advice that uses method
interception. Classes that implement around advice should therefore implement the
following `MethodInterceptor` interface from the `org.aopalliance.intercept` package:
Spring is compliant with the AOP `Alliance` interface for around advice that uses method
interception. Classes that implement `MethodInterceptor` and that implement around advice should also implement the
following interface:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -46,8 +49,8 @@ following `MethodInterceptor` interface from the `org.aopalliance.intercept` pac
The `MethodInvocation` argument to the `invoke()` method exposes the method being
invoked, the target join point, the AOP proxy, and the arguments to the method. The
`invoke()` method should return the invocation's result: typically the return value of
the join point.
`invoke()` method should return the invocation's result: the return value of the join
point.
The following example shows a simple `MethodInterceptor` implementation:
@@ -55,30 +58,30 @@ The following example shows a simple `MethodInterceptor` implementation:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object result = invocation.proceed();
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return result;
return rval;
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class DebugInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation): Any {
println("Before: invocation=[$invocation]")
val result = invocation.proceed()
val rval = invocation.proceed()
println("Invocation returned")
return result
return rval
}
}
----
@@ -98,10 +101,11 @@ you are likely to want to run the aspect in another AOP framework. Note that poi
are not currently interoperable between frameworks, and the AOP Alliance does not
currently define pointcut interfaces.
[[aop-api-advice-before]]
=== Before Advice
A simpler advice type is a _before advice_. This does not need a `MethodInvocation`
A simpler advice type is a before advice. This does not need a `MethodInvocation`
object, since it is called only before entering the method.
The main advantage of a before advice is that there is no need to invoke the `proceed()`
@@ -118,6 +122,10 @@ The following listing shows the `MethodBeforeAdvice` interface:
}
----
(Spring's API design would allow for
field before advice, although the usual objects apply to field interception and it is
unlikely for Spring to ever implement it.)
Note that the return type is `void`. Before advice can insert custom behavior before the join
point runs but cannot change the return value. If a before advice throws an
exception, it stops further execution of the interceptor chain. The exception
@@ -131,7 +139,7 @@ The following example shows a before advice in Spring, which counts all method i
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class CountingBeforeAdvice implements MethodBeforeAdvice {
@@ -149,7 +157,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class CountingBeforeAdvice : MethodBeforeAdvice {
@@ -164,13 +172,14 @@ Kotlin::
TIP: Before advice can be used with any pointcut.
[[aop-api-advice-throws]]
=== Throws Advice
_Throws advice_ is invoked after the return of the join point if the join point threw
Throws advice is invoked after the return of the join point if the join point threw
an exception. Spring offers typed throws advice. Note that this means that the
`org.springframework.aop.ThrowsAdvice` interface does not contain any methods. It is a
marker interface identifying that the given object implements one or more typed throws
tag interface identifying that the given object implements one or more typed throws
advice methods. These should be in the following form:
[source,java,indent=0,subs="verbatim,quotes"]
@@ -180,16 +189,15 @@ advice methods. These should be in the following form:
Only the last argument is required. The method signatures may have either one or four
arguments, depending on whether the advice method is interested in the method and
arguments. The next two listings show classes that are examples of throws advice.
arguments. The next two listing show classes that are examples of throws advice.
The following advice is invoked if a `RemoteException` is thrown (including subclasses of
`RemoteException`):
The following advice is invoked if a `RemoteException` is thrown (including from subclasses):
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class RemoteThrowsAdvice implements ThrowsAdvice {
@@ -201,7 +209,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class RemoteThrowsAdvice : ThrowsAdvice {
@@ -212,15 +220,15 @@ Kotlin::
----
======
Unlike the preceding advice, the next example declares four arguments, so that it has
access to the invoked method, method arguments, and target object. The following advice
is invoked if a `ServletException` is thrown:
Unlike the preceding
advice, the next example declares four arguments, so that it has access to the invoked method, method
arguments, and target object. The following advice is invoked if a `ServletException` is thrown:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
@@ -232,7 +240,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
@@ -251,7 +259,7 @@ methods can be combined in a single class. The following listing shows the final
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public static class CombinedThrowsAdvice implements ThrowsAdvice {
@@ -267,7 +275,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class CombinedThrowsAdvice : ThrowsAdvice {
@@ -292,10 +300,11 @@ exception that is incompatible with the target method's signature!_
TIP: Throws advice can be used with any pointcut.
[[aop-api-advice-after-returning]]
=== After Returning Advice
An _after returning advice_ in Spring must implement the
An after returning advice in Spring must implement the
`org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows:
[source,java,indent=0,subs="verbatim,quotes"]
@@ -317,7 +326,7 @@ not thrown exceptions:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
@@ -336,7 +345,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class CountingAfterReturningAdvice : AfterReturningAdvice {
@@ -355,10 +364,11 @@ thrown up the interceptor chain instead of the return value.
TIP: After returning advice can be used with any pointcut.
[[aop-api-advice-introduction]]
=== Introduction Advice
Spring treats _introduction advice_ as a special kind of interception advice.
Spring treats introduction advice as a special kind of interception advice.
Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that
implement the following interface:
@@ -410,7 +420,7 @@ introduce the following interface to one or more objects:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public interface Lockable {
void lock();
@@ -421,7 +431,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
interface Lockable {
fun lock()
@@ -470,7 +480,7 @@ The following example shows the example `LockMixin` class:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
@@ -494,12 +504,13 @@ Java::
}
return super.invoke(invocation);
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
@@ -523,6 +534,7 @@ Kotlin::
}
return super.invoke(invocation)
}
}
----
======
@@ -544,7 +556,7 @@ The following example shows our `LockMixinAdvisor` class:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
@@ -556,7 +568,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
----
@@ -573,3 +585,8 @@ We can apply this advisor programmatically by using the `Advised.addAdvisor()` m
(the recommended way) in XML configuration, as any other advisor. All proxy creation
choices discussed below, including "`auto proxy creators,`" correctly handle introductions
and stateful mixins.
@@ -10,7 +10,7 @@ following methods:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Advisor[] getAdvisors();
@@ -35,7 +35,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
fun getAdvisors(): Array<Advisor>
@@ -90,7 +90,7 @@ manipulating its advice:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
@@ -110,7 +110,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val advised = myObject as Advised
val advisors = advised.advisors
@@ -142,3 +142,7 @@ case, the `Advised` `isFrozen()` method returns `true`, and any attempts to modi
advice through addition or removal results in an `AopConfigException`. The ability
to freeze the state of an advised object is useful in some cases (for example, to
prevent calling code removing a security interceptor).
@@ -14,3 +14,7 @@ It is possible to mix advisor and advice types in Spring in the same AOP proxy.
example, you could use an interception around advice, throws advice, and before advice in
one proxy configuration. Spring automatically creates the necessary interceptor
chain.
@@ -19,12 +19,14 @@ There are two ways to do this:
auto-proxy creation driven by source-level metadata attributes.
[[aop-autoproxy-choices]]
== Auto-proxy Bean Definitions
This section covers the auto-proxy creators provided by the
`org.springframework.aop.framework.autoproxy` package.
[[aop-api-autoproxy]]
=== `BeanNameAutoProxyCreator`
@@ -59,6 +61,7 @@ automatically created by the `BeanNameAutoProxyCreator`. The same advice is appl
to all matching beans. Note that, if advisors are used (rather than the interceptor in
the preceding example), the pointcuts may apply differently to different beans.
[[aop-api-autoproxy-default]]
=== `DefaultAdvisorAutoProxyCreator`
@@ -122,3 +125,7 @@ differently configured, AdvisorAutoProxyCreators in the same factory) and orderi
Advisors can implement the `org.springframework.core.Ordered` interface to ensure
correct ordering if this is an issue. The `TransactionAttributeSourceAdvisor` used in the
preceding example has a configurable order value. The default setting is unordered.
@@ -65,3 +65,7 @@ that, if you have a (parent) bean definition that you intend to use only as a te
and this definition specifies a class, you must make sure to set the `abstract`
attribute to `true`. Otherwise, the application context actually tries to
pre-instantiate it.
@@ -14,6 +14,7 @@ the pointcuts, any advice that applies, and their ordering. However, there are s
options that are preferable if you do not need such control.
[[aop-pfb-1]]
== Basics
@@ -31,6 +32,7 @@ application objects (besides the target, which should be available in any AOP
framework), benefiting from all the pluggability provided by Dependency Injection.
[[aop-pfb-2]]
== JavaBean Properties
@@ -38,7 +40,7 @@ In common with most `FactoryBean` implementations provided with Spring, the
`ProxyFactoryBean` class is itself a JavaBean. Its properties are used to:
* Specify the target you want to proxy.
* Specify whether to use CGLIB (described later and see also <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
* Specify whether to use CGLIB (described later and see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
Some key properties are inherited from `org.springframework.aop.framework.ProxyConfig`
(the superclass for all AOP proxy factories in Spring). These key properties include
@@ -46,7 +48,7 @@ the following:
* `proxyTargetClass`: `true` if the target class is to be proxied, rather than the
target class's interfaces. If this property value is set to `true`, then CGLIB proxies
are created (but see also <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
are created (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
* `optimize`: Controls whether or not aggressive optimizations are applied to proxies
created through CGLIB. You should not blithely use this setting unless you fully
understand how the relevant AOP proxy handles optimization. This is currently used
@@ -64,7 +66,7 @@ the following:
Other properties specific to `ProxyFactoryBean` include the following:
* `proxyInterfaces`: An array of `String` interface names. If this is not supplied, a CGLIB
proxy for the target class is used (but see also <<aop-pfb-proxy-types,JDK- and CGLIB-based proxies>>).
proxy for the target class is used (but see also xref:core/aop-api/pfb.adoc#aop-pfb-proxy-types[JDK- and CGLIB-based proxies]).
* `interceptorNames`: A `String` array of `Advisor`, interceptor, or other advice names to
apply. Ordering is significant, on a first come-first served basis. That is to say
that the first interceptor in the list is the first to be able to intercept the
@@ -76,7 +78,7 @@ factories. You cannot mention bean references here, since doing so results in th
+
You can append an interceptor name with an asterisk (`*`). Doing so results in the
application of all advisor beans with names that start with the part before the asterisk
to be applied. You can find an example of using this feature in <<aop-global-advisors,Using "`Global`" Advisors>>.
to be applied. You can find an example of using this feature in xref:core/aop-api/pfb.adoc#aop-global-advisors[Using "`Global`" Advisors].
* singleton: Whether or not the factory should return a single object, no matter how
often the `getObject()` method is called. Several `FactoryBean` implementations offer
@@ -85,6 +87,7 @@ to be applied. You can find an example of using this feature in <<aop-global-adv
`false`.
[[aop-pfb-proxy-types]]
== JDK- and CGLIB-based proxies
@@ -134,6 +137,7 @@ interface that the target class implements to the `proxyInterfaces` property. Ho
it is significantly less work and less prone to typographical errors.
[[aop-api-proxying-intf]]
== Proxying Interfaces
@@ -192,16 +196,16 @@ follows:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Person person = (Person) factory.getBean("person");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val person = factory.getBean("person") as Person
val person = factory.getBean("person") as Person;
----
======
@@ -259,6 +263,7 @@ However, there are times when being able to obtain the un-advised target from th
factory might actually be an advantage (for example, in certain test scenarios).
[[aop-api-proxying-class]]
== Proxying Classes
@@ -297,6 +302,7 @@ There is little performance difference between CGLIB proxies and dynamic proxies
Performance should not be a decisive consideration in this case.
[[aop-global-advisors]]
== Using "`Global`" Advisors
@@ -319,3 +325,7 @@ two global advisors:
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
----
@@ -4,6 +4,7 @@
This section describes how Spring handles the crucial pointcut concept.
[[aop-api-concepts]]
== Concepts
@@ -68,6 +69,7 @@ TIP: If possible, try to make pointcuts static, allowing the AOP framework to ca
results of pointcut evaluation when an AOP proxy is created.
[[aop-api-pointcut-ops]]
== Operations on Pointcuts
@@ -82,6 +84,7 @@ You can compose pointcuts by using the static methods in the
expressions is usually a simpler approach.
[[aop-api-pointcuts-aspectj]]
== AspectJ Expression Pointcuts
@@ -92,12 +95,14 @@ uses an AspectJ-supplied library to parse an AspectJ pointcut expression string.
See the xref:core/aop.adoc[previous chapter] for a discussion of supported AspectJ pointcut primitives.
[[aop-api-pointcuts-impls]]
== Convenience Pointcut Implementations
Spring provides several convenient pointcut implementations. You can use some of them
directly; others are intended to be subclassed in application-specific pointcuts.
[[aop-api-pointcuts-static]]
=== Static Pointcuts
@@ -123,7 +128,18 @@ the resulting pointcut is effectively the union of the specified patterns.)
The following example shows how to use `JdkRegexpMethodPointcut`:
include-code::./JdkRegexpConfiguration[tag=snippet,indent=0]
[source,xml,indent=0,subs="verbatim"]
----
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
----
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
@@ -131,7 +147,21 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
pointcut and advice, as the following example shows:
include-code::./RegexpConfiguration[tag=snippet,indent=0]
[source,xml,indent=0,subs="verbatim"]
----
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
----
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
@@ -141,6 +171,7 @@ You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
An important type of static pointcut is a metadata-driven pointcut. This uses the
values of metadata attributes (typically, source-level metadata).
[[aop-api-pointcuts-dynamic]]
=== Dynamic pointcuts
@@ -166,6 +197,7 @@ other dynamic pointcuts. In Java 1.4, the cost is about five times that of other
pointcuts.
[[aop-api-pointcuts-superclasses]]
== Pointcut Superclasses
@@ -180,7 +212,7 @@ following example shows how to subclass `StaticMethodMatcherPointcut`:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
class TestStaticPointcut extends StaticMethodMatcherPointcut {
@@ -192,7 +224,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class TestStaticPointcut : StaticMethodMatcherPointcut() {
@@ -207,6 +239,7 @@ There are also superclasses for dynamic pointcuts.
You can use custom pointcuts with any advice type.
[[aop-api-pointcuts-custom]]
== Custom Pointcuts
@@ -217,3 +250,7 @@ expression language, if you can.
NOTE: Later versions of Spring may offer support for "`semantic pointcuts`" as offered by JAC --
for example, "`all methods that change instance variables in the target object.`"
@@ -12,7 +12,7 @@ interceptor and one advisor:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
@@ -22,7 +22,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val factory = ProxyFactory(myBusinessInterfaceImpl)
factory.addAdvice(myMethodInterceptor)
@@ -22,6 +22,7 @@ rather than a singleton bean definition. This allows Spring to create a new targ
instance when required.
[[aop-ts-swap]]
== Hot-swappable Target Sources
@@ -37,7 +38,7 @@ You can change the target by using the `swap()` method on HotSwappableTargetSour
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
@@ -45,7 +46,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)
@@ -76,6 +77,7 @@ use a `TargetSource`), any `TargetSource` can be used in conjunction with
arbitrary advice.
[[aop-ts-pool]]
== Pooling Target Sources
@@ -87,12 +89,14 @@ A crucial difference between Spring pooling and SLSB pooling is that Spring pool
be applied to any POJO. As with Spring in general, this service can be applied in a
non-invasive way.
Spring provides support for Commons Pool 2, which provides a
Spring provides support for Commons Pool 2.2, which provides a
fairly efficient pooling implementation. You need the `commons-pool` Jar on your
application's classpath to use this feature. You can also subclass
`org.springframework.aop.target.AbstractPoolingTargetSource` to support any other
pooling API.
NOTE: Commons Pool 1.5+ is also supported but is deprecated as of Spring Framework 4.2.
The following listing shows an example configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -148,7 +152,7 @@ The cast is defined as follows:
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
@@ -156,7 +160,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
@@ -171,6 +175,7 @@ Simpler pooling is available by using auto-proxying. You can set the `TargetSour
used by any auto-proxy creator.
[[aop-ts-prototype]]
== Prototype Target Sources
@@ -195,6 +200,7 @@ The only property is the name of the target bean. Inheritance is used in the
source, the target bean must be a prototype bean definition.
[[aop-ts-threadlocal]]
== `ThreadLocal` Target Sources
@@ -220,3 +226,7 @@ always remember to correctly set and unset (where the latter involves a call to
any case, since not unsetting it might result in problematic behavior. Spring's
`ThreadLocal` support does this for you and should always be considered in favor of using
`ThreadLocal` instances without other proper handling code.
@@ -32,3 +32,7 @@ AOP is used in the Spring Framework to:
NOTE: If you are interested only in generic declarative services or other pre-packaged
declarative middleware services such as pooling, you do not need to work directly with
Spring AOP, and can skip most of this chapter.
@@ -15,7 +15,7 @@ The basic usage for this class is very simple, as the following example shows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
@@ -34,7 +34,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
// create a factory that can generate a proxy for the given target object
val factory = AspectJProxyFactory(targetObject)
@@ -53,3 +53,7 @@ Kotlin::
======
See the {spring-framework-api}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information.
@@ -11,3 +11,6 @@ there is no dependency on the AspectJ compiler or weaver.
NOTE: Using the AspectJ compiler and weaver enables use of the full AspectJ language and
is discussed in xref:core/aop/using-aspectj.adoc[Using AspectJ with Spring Applications].
@@ -17,7 +17,7 @@ The following example uses an inline pointcut expression.
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@@ -34,7 +34,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@@ -57,7 +57,7 @@ as follows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@@ -74,7 +74,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@@ -101,7 +101,7 @@ You can declare it by using the `@AfterReturning` annotation.
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@@ -118,7 +118,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@@ -146,7 +146,7 @@ access, as the following example shows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@@ -165,7 +165,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@@ -204,7 +204,7 @@ following example shows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@@ -221,7 +221,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@@ -247,7 +247,7 @@ The following example shows how to do so:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@@ -266,7 +266,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@@ -311,7 +311,7 @@ purposes. The following example shows how to use after finally advice:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@@ -328,7 +328,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@@ -394,7 +394,7 @@ execution-only semantics. You only need to be aware of this difference if you co
`@AspectJ` aspects written for Spring and use `proceed` with arguments with the AspectJ
compiler and weaver. There is a way to write such aspects that is 100% compatible across
both Spring AOP and AspectJ, and this is discussed in the
<<aop-ataspectj-advice-proceeding-with-the-call,following section on advice parameters>>.
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-proceeding-with-the-call[following section on advice parameters].
====
The value returned by the around advice is the return value seen by the caller of the
@@ -417,7 +417,7 @@ The following example shows how to use around advice:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
@@ -438,7 +438,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
@@ -500,7 +500,7 @@ You could write the following:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
public void validateAccount(Account account) {
@@ -510,7 +510,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
fun validateAccount(account: Account) {
@@ -533,7 +533,7 @@ from the advice. This would look as follows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@@ -546,7 +546,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
@@ -572,7 +572,7 @@ The following shows the definition of the `@Auditable` annotation:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@@ -583,7 +583,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
@@ -597,7 +597,7 @@ The following shows the advice that matches the execution of `@Auditable` method
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
public void audit(Auditable auditable) {
@@ -609,7 +609,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") // <1>
fun audit(auditable: Auditable) {
@@ -630,7 +630,7 @@ you have a generic type like the following:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public interface Sample<T> {
void sampleGenericMethod(T param);
@@ -640,7 +640,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
interface Sample<T> {
fun sampleGenericMethod(param: T)
@@ -656,7 +656,7 @@ tying the advice parameter to the parameter type for which you want to intercept
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
@@ -666,7 +666,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
@@ -682,7 +682,7 @@ pointcut as follows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
@@ -692,7 +692,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
@@ -722,12 +722,12 @@ of determining parameter names, an exception will be thrown.
`AspectJAnnotationParameterNameDiscoverer` :: Uses parameter names that have been explicitly
specified by the user via the `argNames` attribute in the corresponding advice or
pointcut annotation. See <<aop-ataspectj-advice-params-names-explicit,Explicit Argument Names>> for details.
pointcut annotation. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names-explicit[Explicit Argument Names] for details.
`KotlinReflectionParameterNameDiscoverer` :: Uses Kotlin reflection APIs to determine
parameter names. This discoverer is only used if such APIs are present on the classpath.
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
API to determine parameter names. Requires that code be compiled with the `-parameters`
flag for `javac`. Recommended approach.
flag for `javac`. Recommended approach on Java 8+.
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
expression, `returning`, and `throwing` clauses. See the
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
@@ -756,7 +756,7 @@ The following example shows how to use the `argNames` attribute:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
@@ -771,7 +771,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
@@ -794,7 +794,7 @@ point object, the `argNames` attribute does not need to include it:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
@@ -809,7 +809,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", // <1>
@@ -833,7 +833,7 @@ the `argNames` attribute:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
public void audit(JoinPoint jp) {
@@ -844,7 +844,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Before("com.xyz.Pointcuts.publicMethod()") // <1>
fun audit(jp: JoinPoint) {
@@ -867,7 +867,7 @@ The following example shows how to do so:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
@@ -882,7 +882,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
@@ -923,7 +923,7 @@ Each of the distinct advice types of a particular aspect is conceptually meant t
to the join point directly. As a consequence, an `@AfterThrowing` advice method is not
supposed to receive an exception from an accompanying `@After`/`@AfterReturning` method.
Advice methods defined in the same `@Aspect` class that
As of Spring Framework 5.2.7, advice methods defined in the same `@Aspect` class that
need to run at the same join point are assigned precedence based on their advice type in
the following order, from highest to lowest precedence: `@Around`, `@Before`, `@After`,
`@AfterReturning`, `@AfterThrowing`. Note, however, that an `@After` advice method will
@@ -937,3 +937,5 @@ reflection for javac-compiled classes). Consider collapsing such advice methods
advice method per join point in each `@Aspect` class or refactor the pieces of advice into
separate `@Aspect` classes that you can order at the aspect level via `Ordered` or `@Order`.
====
@@ -8,8 +8,54 @@ determines that a bean is advised by one or more aspects, it automatically gener
a proxy for that bean to intercept method invocations and ensures that advice is run
as needed.
The @AspectJ support can be enabled with programmatic or XML configuration. In either
case, you also need to ensure that AspectJ's `org.aspectj:aspectjweaver` library is on the
classpath of your application (version 1.9 or later).
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
classpath of your application (version 1.9 or later). This library is available in the
`lib` directory of an AspectJ distribution or from the Maven Central repository.
[[aop-enable-aspectj-java]]
== Enabling @AspectJ Support with Java Configuration
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
annotation, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Configuration
@EnableAspectJAutoProxy
class AppConfig
----
======
[[aop-enable-aspectj-xml]]
== Enabling @AspectJ Support with XML Configuration
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
element, as the following example shows:
[source,xml,indent=0,subs="verbatim"]
----
<aop:aspectj-autoproxy/>
----
This assumes that you use schema support as described in
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
import the tags in the `aop` namespace.
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
@@ -9,12 +9,43 @@ minimal steps required for a not-very-useful aspect.
The first of the two examples shows a regular bean definition in the application context
that points to a bean class that is annotated with `@Aspect`:
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
[source,xml,indent=0,subs="verbatim"]
----
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>
----
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
annotated with `@Aspect`:
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
----
package com.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
----
package com.xyz
import org.aspectj.lang.annotation.Aspect
@Aspect
class NotVeryUsefulAspect
----
======
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
other class. They can also contain pointcut, advice, and introduction (inter-type)
@@ -32,3 +63,6 @@ stereotype annotation that qualifies, as per the rules of Spring's component sca
NOTE: In Spring AOP, aspects themselves cannot be the targets of advice from other
aspects. The `@Aspect` annotation on a class marks it as an aspect and, hence, excludes
it from auto-proxying.
@@ -16,10 +16,93 @@ aspect.
Because we want to retry the operation, we need to use around advice so that we can
call `proceed` multiple times. The following listing shows the basic aspect implementation:
include-code::./ConcurrentOperationExecutor[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Aspect
public class ConcurrentOperationExecutor implements Ordered {
`@Around("com.xyz.CommonPointcuts.businessService()")` references the `businessService` named pointcut defined in
xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
----
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Aspect
class ConcurrentOperationExecutor : Ordered {
private val DEFAULT_MAX_RETRIES = 2
private var maxRetries = DEFAULT_MAX_RETRIES
private var order = 1
fun setMaxRetries(maxRetries: Int) {
this.maxRetries = maxRetries
}
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
numAttempts++
try {
return pjp.proceed()
} catch (ex: PessimisticLockingFailureException) {
lockFailureException = ex
}
} while (numAttempts <= this.maxRetries)
throw lockFailureException
}
}
----
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
======
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
the aspect higher than the transaction advice (we want a fresh transaction each time we
@@ -31,15 +114,70 @@ we have exhausted all of our retry attempts.
The corresponding Spring configuration follows:
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
[source,xml,indent=0,subs="verbatim"]
----
<aop:aspectj-autoproxy/>
<bean id="concurrentOperationExecutor"
class="com.xyz.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
----
To refine the aspect so that it retries only idempotent operations, we might define the following
`Idempotent` annotation:
include-code::./service/Idempotent[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
// marker annotation
annotation class Idempotent
----
======
We can then use the annotation to annotate the implementation of service operations. The change
to the aspect to retry only idempotent operations involves refining the pointcut
expression so that only `@Idempotent` operations match, as follows:
include-code::./service/SampleService[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Around("execution(* com.xyz..service.*.*(..)) && " +
"@annotation(com.xyz.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Around("execution(* com.xyz..service.*.*(..)) && " +
"@annotation(com.xyz.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
// ...
}
----
======
@@ -17,7 +17,7 @@ annotation. Consider the following example:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
public class MyAspect {
@@ -33,7 +33,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
class MyAspect {
@@ -60,3 +60,6 @@ Programming Guide for more information on `per` clauses.
The `pertarget` instantiation model works in exactly the same way as `perthis`, but it
creates one aspect instance for each unique target object at matched join points.
@@ -9,13 +9,13 @@ You can make an introduction by using the `@DeclareParents` annotation. This ann
is used to declare that matching types have a new parent (hence the name). For example,
given an interface named `UsageTracked` and an implementation of that interface named
`DefaultUsageTracked`, the following aspect declares that all implementors of service
interfaces also implement the `UsageTracked` interface (for example, for statistics via JMX):
interfaces also implement the `UsageTracked` interface (e.g. for statistics via JMX):
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Aspect
public class UsageTracking {
@@ -33,7 +33,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Aspect
class UsageTracking {
@@ -63,15 +63,17 @@ you would write the following:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
val usageTracked = context.getBean<UsageTracked>("myService")
val usageTracked = context.getBean("myService", UsageTracked.class)
----
======
@@ -19,7 +19,7 @@ matches the execution of any method named `transfer`:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
@@ -27,7 +27,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
potential non-public interactions through proxies, it needs to be defined accordingly.
If your interception needs to include method calls or even constructors within the target
If your interception needs include method calls or even constructors within the target
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
with different characteristics, so be sure to make yourself familiar with weaving
@@ -150,7 +150,7 @@ pointcut expressions by name. The following example shows three pointcut express
======
Java::
+
[source,java,indent=0,subs="verbatim",chomp="-packages"]
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
----
package com.xyz;
@@ -174,7 +174,7 @@ trading module.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
----
package com.xyz
@@ -217,7 +217,7 @@ expressions for this purpose. Such a class typically resembles the following
======
Java::
+
[source,java,indent=0,subs="verbatim",chomp="-packages",fold="none"]
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
----
package com.xyz;
@@ -279,7 +279,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",chomp="-packages",fold="none"]
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
----
package com.xyz
@@ -581,3 +581,6 @@ performance (time and memory used), due to extra processing and analysis. Scopin
designators are very fast to match, and using them means AspectJ can very quickly
dismiss groups of join points that should not be further processed. A good
pointcut should always include one if possible.
@@ -8,6 +8,7 @@ decisions are influenced by a number of factors including application requiremen
development tools, and team familiarity with AOP.
[[aop-spring-or-aspectj]]
== Spring AOP or Full AspectJ?
@@ -30,6 +31,7 @@ the @AspectJ style, sticking with regular Java compilation in your IDE, and addi
an aspect weaving phase to your build script.
[[aop-ataspectj-or-xml]]
== @AspectJ or XML for Spring AOP?
@@ -57,7 +59,7 @@ For example, in the @AspectJ style you can write something like the following:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@@ -71,7 +73,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Pointcut("execution(* get*())")
fun propertyAccess() {}
@@ -105,3 +107,7 @@ Spring AOP and by AspectJ. So, if you later decide you need the capabilities of
to implement additional requirements, you can easily migrate to a classic AspectJ setup.
In general, the Spring team prefers the @AspectJ style for custom aspects beyond simple
configuration of enterprise services.
@@ -65,7 +65,7 @@ with less potential for errors. For example, you do not need to invoke the `proc
method on the `JoinPoint` used for around advice, and, hence, you cannot fail to invoke it.
All advice parameters are statically typed so that you work with advice parameters of
the appropriate type (for example, the type of the return value from a method execution) rather
the appropriate type (e.g. the type of the return value from a method execution) rather
than `Object` arrays.
The concept of join points matched by pointcuts is the key to AOP, which distinguishes
@@ -73,3 +73,7 @@ it from older technologies offering only interception. Pointcuts enable advice t
targeted independently of the object-oriented hierarchy. For example, you can apply an
around advice providing declarative transaction management to a set of methods that span
multiple objects (such as all business operations in the service layer).
@@ -14,5 +14,9 @@ need to advise a method that is not declared on an interface or where you need t
pass a proxied object to a method as a concrete type.
It is important to grasp the fact that Spring AOP is proxy-based. See
xref:core/aop/proxying.adoc#aop-understanding-aop-proxies[Understanding AOP Proxies]
for a thorough examination of exactly what this implementation detail actually means.
xref:core/aop/proxying.adoc#aop-understanding-aop-proxies[Understanding AOP Proxies] for a thorough examination of exactly what this
implementation detail actually means.
@@ -52,6 +52,10 @@ configuration-style approach. The fact that this chapter chooses to introduce th
@AspectJ-style approach first should not be taken as an indication that the Spring team
favors the @AspectJ annotation-style approach over the Spring XML configuration-style.
See xref:core/aop/choosing.adoc[Choosing which AOP Declaration Style to Use] for a more
complete discussion of the advantages and disadvantages of each style.
See xref:core/aop/choosing.adoc[Choosing which AOP Declaration Style to Use] for a more complete discussion of the advantages and disadvantages of
each style.
====
@@ -6,3 +6,7 @@ It is perfectly possible to mix @AspectJ style aspects by using the auto-proxyin
schema-defined `<aop:aspect>` aspects, `<aop:advisor>` declared advisors, and even proxies
and interceptors in other styles in the same configuration. All of these are implemented
by using the same underlying support mechanism and can co-exist without any difficulty.
@@ -6,32 +6,24 @@ target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a co
open-source class definition library (repackaged into `spring-core`).
If the target object to be proxied implements at least one interface, a JDK dynamic
proxy is used, and all of the interfaces implemented by the target type are proxied.
If the target object does not implement any interfaces, a CGLIB proxy is created which
is a runtime-generated subclass of the target type.
proxy is used. All of the interfaces implemented by the target type are proxied.
If the target object does not implement any interfaces, a CGLIB proxy is created.
If you want to force the use of CGLIB proxying (for example, to proxy every method
defined for the target object, not only those implemented by its interfaces),
you can do so. However, you should consider the following issues:
* `final` classes cannot be proxied, because they cannot be extended.
* `final` methods cannot be advised, because they cannot be overridden.
* `private` methods cannot be advised, because they cannot be overridden.
* Methods that are not visible for example, package-private methods in a parent class
from a different package cannot be advised because they are effectively private.
* The constructor of your proxied object will not be called twice, since the CGLIB proxy
instance is created through Objenesis. However, if your JVM does not allow for
constructor bypassing, you might see double invocations and corresponding debug log
entries from Spring's AOP support.
* Your CGLIB proxy usage may face limitations with the Java Module System. As a typical
case, you cannot create a CGLIB proxy for a class from the `java.lang` package when
deploying on the module path. Such cases require a JVM bootstrap flag
* With CGLIB, `final` methods cannot be advised, as they cannot be overridden in
runtime-generated subclasses.
* As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore,
since the CGLIB proxy instance is created through Objenesis. Only if your JVM does
not allow for constructor bypassing, you might see double invocations and
corresponding debug log entries from Spring's AOP support.
* Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system.
As a typical case, you cannot create a CGLIB proxy for a class from the `java.lang`
package when deploying on the module path. Such cases require a JVM bootstrap flag
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
[[aop-forcing-proxy-types]]
== Forcing Specific AOP Proxy Types
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
of the `<aop:config>` element to true, as follows:
@@ -64,23 +56,6 @@ To be clear, using `proxy-target-class="true"` on `<tx:annotation-driven/>`,
proxies _for all three of them_.
====
`@EnableAspectJAutoProxy`, `@EnableTransactionManagement` and related configuration
annotations offer a corresponding `proxyTargetClass` attribute. These are collapsed
into a single unified auto-proxy creator too, effectively applying the _strongest_
proxy settings at runtime. As of 7.0, this applies to individual proxy processors
as well, for example `@EnableAsync`, consistently participating in unified global
default settings for all auto-proxying attempts in a given application.
The global default proxy type may differ between setups. While the core framework
suggests interface-based proxies by default, Spring Boot may - depending on
configuration properties - enable class-based proxies by default.
As of 7.0, forcing a specific proxy type for individual beans is possible through
the `@Proxyable` annotation on a given `@Bean` method or `@Component` class, with
`@Proxyable(INTERFACES)` or `@Proxyable(TARGET_CLASS)` overriding any globally
configured default. For very specific purposes, you may even specify the proxy
interface(s) to use through `@Proxyable(interfaces=...)`, limiting the exposure
to selected interfaces rather than all interfaces that the target bean implements.
[[aop-understanding-aop-proxies]]
@@ -90,14 +65,15 @@ Spring AOP is proxy-based. It is vitally important that you grasp the semantics
what that last statement actually means before you write your own aspects or use any of
the Spring AOP-based aspects supplied with the Spring Framework.
Consider first the scenario where you have a plain-vanilla, un-proxied object reference,
as the following code snippet shows:
Consider first the scenario where you have a plain-vanilla, un-proxied,
nothing-special-about-it, straight object reference, as the following
code snippet shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class SimplePojo implements Pojo {
@@ -114,7 +90,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
class SimplePojo : Pojo {
@@ -139,7 +115,7 @@ image::aop-proxy-plain-pojo-call.png[]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class Main {
@@ -153,7 +129,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun main() {
val pojo = SimplePojo()
@@ -172,7 +148,7 @@ image::aop-proxy-call.png[]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class Main {
@@ -190,7 +166,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun main() {
val factory = ProxyFactory(SimplePojo())
@@ -211,35 +187,26 @@ the interceptors (advice) that are relevant to that particular method call. Howe
once the call has finally reached the target object (the `SimplePojo` reference in
this case), any method calls that it may make on itself, such as `this.bar()` or
`this.foo()`, are going to be invoked against the `this` reference, and not the proxy.
This has important implications. It means that self invocation is not going to result
in the advice associated with a method invocation getting a chance to run. In other words,
self invocation via an explicit or implicit `this` reference will bypass the advice.
This has important implications. It means that self-invocation is not going to result
in the advice associated with a method invocation getting a chance to run.
To address that, you have the following options.
Avoid self invocation ::
The best approach (the term "best" is used loosely here) is to refactor your code such
that the self invocation does not happen. This does entail some work on your part, but
it is the best, least-invasive approach.
Inject a self reference ::
An alternative approach is to make use of
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-self-injection[self injection],
and invoke methods on the proxy via the self reference instead of via `this`.
Use `AopContext.currentProxy()` ::
This last approach is highly discouraged, and we hesitate to point it out, in favor of
the previous options. However, as a last resort you can choose to tie the logic within
your class to Spring AOP, as the following example shows.
Okay, so what is to be done about this? The best approach (the term "best" is used
loosely here) is to refactor your code such that the self-invocation does not happen.
This does entail some work on your part, but it is the best, least-invasive approach.
The next approach is absolutely horrendous, and we hesitate to point it out, precisely
because it is so horrendous. You can (painful as it is to us) totally tie the logic
within your class to Spring AOP, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class SimplePojo implements Pojo {
public void foo() {
// This works, but it should be avoided if possible.
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
@@ -251,12 +218,12 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
class SimplePojo : Pojo {
fun foo() {
// This works, but it should be avoided if possible.
// this works, but... gah!
(AopContext.currentProxy() as Pojo).bar()
}
@@ -267,16 +234,16 @@ Kotlin::
----
======
The use of `AopContext.currentProxy()` totally couples your code to Spring AOP, and it
makes the class itself aware of the fact that it is being used in an AOP context, which
reduces some of the benefits of AOP. It also requires that the `ProxyFactory` is
configured to expose the proxy, as the following example shows:
This totally couples your code to Spring AOP, and it makes the class itself aware of
the fact that it is being used in an AOP context, which flies in the face of AOP. It
also requires some additional configuration when the proxy is being created, as the
following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class Main {
@@ -295,7 +262,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun main() {
val factory = ProxyFactory(SimplePojo())
@@ -310,5 +277,9 @@ Kotlin::
----
======
NOTE: AspectJ compile-time weaving and load-time weaving do not have this self-invocation
issue because they apply advice within the bytecode instead of via a proxy.
Finally, it must be noted that AspectJ does not have this self-invocation issue because
it is not a proxy-based AOP framework.
@@ -10,7 +10,7 @@ of advice parameters.
To use the aop namespace tags described in this section, you need to import the
`spring-aop` schema, as described in xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]
. See xref:core/appendix/xsd-schemas.adoc#xsd-schemas-aop[the AOP schema]
. See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema]
for how to import the tags in the `aop` namespace.
Within your Spring configurations, all aspect and advisor elements must be placed within
@@ -26,6 +26,7 @@ use either only the `<aop:config>` style or only the `AutoProxyCreator` style an
never mix them.
[[aop-schema-declaring-an-aspect]]
== Declaring an Aspect
@@ -53,6 +54,7 @@ The bean that backs the aspect (`aBean` in this case) can of course be configure
dependency injected just like any other Spring bean.
[[aop-schema-pointcuts]]
== Declaring a Pointcut
@@ -134,7 +136,7 @@ parameters of the matching names, as follows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public void monitor(Object service) {
// ...
@@ -143,7 +145,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun monitor(service: Any) {
// ...
@@ -175,7 +177,9 @@ follows:
Note that pointcuts defined in this way are referred to by their XML `id` and cannot be
used as named pointcuts to form composite pointcuts. The named pointcut support in the
schema-based definition style is thus more limited than that offered by the @AspectJ style.
schema-based definition style is thus more limited than that offered by the @AspectJ
style.
[[aop-schema-advice]]
@@ -184,6 +188,7 @@ schema-based definition style is thus more limited than that offered by the @Asp
The schema-based AOP support uses the same five kinds of advice as the @AspectJ style, and they have
exactly the same semantics.
[[aop-schema-advice-before]]
=== Before Advice
@@ -204,7 +209,7 @@ Before advice runs before a matched method execution. It is declared inside an
----
In the example above, `dataAccessOperation` is the `id` of a _named pointcut_ defined at
the top (`<aop:config>`) level (see <<aop-schema-pointcuts,Declaring a Pointcut>>).
the top (`<aop:config>`) level (see xref:core/aop/schema.adoc#aop-schema-pointcuts[Declaring a Pointcut]).
NOTE: As we noted in the discussion of the @AspectJ style, using _named pointcuts_ can
significantly improve the readability of your code. See xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions] for
@@ -232,6 +237,7 @@ that contains the advice. Before a data access operation is performed (a method
join point matched by the pointcut expression), the `doAccessCheck` method on the aspect
bean is invoked.
[[aop-schema-advice-after-returning]]
=== After Returning Advice
@@ -276,19 +282,20 @@ example, you can declare the method signature as follows:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public void doAccessCheck(Object retVal) {...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun doAccessCheck(retVal: Any) {...
----
======
[[aop-schema-advice-after-throwing]]
=== After Throwing Advice
@@ -333,19 +340,20 @@ The type of this parameter constrains matching in the same way as described for
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public void doRecoveryActions(DataAccessException dataAccessEx) {...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
----
======
[[aop-schema-advice-after-finally]]
=== After (Finally) Advice
@@ -364,6 +372,7 @@ You can declare it by using the `after` element, as the following example shows:
</aop:aspect>
----
[[aop-schema-advice-around]]
=== Around Advice
@@ -412,7 +421,7 @@ The implementation of the `doBasicProfiling` advice can be exactly the same as i
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
@@ -424,7 +433,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
// start stopwatch
@@ -435,18 +444,17 @@ Kotlin::
----
======
[[aop-schema-params]]
=== Advice Parameters
The schema-based declaration style supports fully typed advice in the same way as
described for the @AspectJ support -- by matching pointcut parameters by name against
advice method parameters. See
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params[Advice Parameters] for details.
If you wish to explicitly specify argument names for the advice methods (not relying on the
advice method parameters. See xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params[Advice Parameters] for details. If you wish
to explicitly specify argument names for the advice methods (not relying on the
detection strategies previously described), you can do so by using the `arg-names`
attribute of the advice element, which is treated in the same manner as the `argNames`
attribute in an advice annotation (as described in
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names[Determining Argument Names]).
attribute in an advice annotation (as described in xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-params-names[Determining Argument Names]).
The following example shows how to specify an argument name in XML:
[source,xml,indent=0,subs="verbatim"]
@@ -456,8 +464,7 @@ The following example shows how to specify an argument name in XML:
method="audit"
arg-names="auditable" />
----
<1> References the `publicMethod` named pointcut defined in
xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
<1> References the `publicMethod` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-pointcuts-combining[Combining Pointcut Expressions].
The `arg-names` attribute accepts a comma-delimited list of parameter names.
@@ -468,7 +475,7 @@ some around advice used in conjunction with a number of strongly typed parameter
======
Java::
+
[source,java,indent=0,subs="verbatim",chomp="-packages"]
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
----
package com.xyz.service;
@@ -487,7 +494,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
----
package com.xyz.service
@@ -514,7 +521,7 @@ proceed with the method call. The presence of this parameter is an indication th
======
Java::
+
[source,java,indent=0,subs="verbatim",chomp="-packages"]
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages"]
----
package com.xyz;
@@ -538,7 +545,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",chomp="-packages"]
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
----
package com.xyz
@@ -603,7 +610,7 @@ Consider the following driver script:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class Boot {
@@ -617,7 +624,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
@@ -638,15 +645,15 @@ ms % Task name
00000 ? execution(getFoo)
----
[[aop-ordering]]
=== Advice Ordering
When multiple pieces of advice need to run at the same join point (executing method)
the ordering rules are as described in
xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-ordering[Advice Ordering]. The
precedence between aspects is determined via the `order` attribute in the `<aop:aspect>`
element or by either adding the `@Order` annotation to the bean that backs the aspect
or by having the bean implement the `Ordered` interface.
the ordering rules are as described in xref:core/aop/ataspectj/advice.adoc#aop-ataspectj-advice-ordering[Advice Ordering]. The precedence
between aspects is determined via the `order` attribute in the `<aop:aspect>` element or
by either adding the `@Order` annotation to the bean that backs the aspect or by having
the bean implement the `Ordered` interface.
[NOTE]
====
@@ -669,6 +676,7 @@ at the aspect level.
====
[[aop-schema-introductions]]
== Introductions
@@ -706,7 +714,7 @@ The class that backs the `usageTracking` bean would then contain the following m
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
@@ -715,7 +723,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
@@ -734,20 +742,21 @@ following:
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
val usageTracked = context.getBean("myService", UsageTracked.class)
----
======
[[aop-schema-instantiation-models]]
== Aspect Instantiation Models
@@ -755,6 +764,7 @@ The only supported instantiation model for schema-defined aspects is the singlet
model. Other instantiation models may be supported in future releases.
[[aop-schema-advisors]]
== Advisors
@@ -762,8 +772,7 @@ The concept of "advisors" comes from the AOP support defined in Spring
and does not have a direct equivalent in AspectJ. An advisor is like a small
self-contained aspect that has a single piece of advice. The advice itself is
represented by a bean and must implement one of the advice interfaces described in
xref:core/aop-api/advice.adoc#aop-api-advice-types[Advice Types in Spring].
Advisors can take advantage of AspectJ pointcut expressions.
xref:core/aop-api/advice.adoc#aop-api-advice-types[Advice Types in Spring]. Advisors can take advantage of AspectJ pointcut expressions.
Spring supports the advisor concept with the `<aop:advisor>` element. You most
commonly see it used in conjunction with transactional advice, which also has its own
@@ -796,6 +805,7 @@ To define the precedence of an advisor so that the advice can participate in ord
use the `order` attribute to define the `Ordered` value of the advisor.
[[aop-schema-example]]
== An AOP Schema Example
@@ -819,7 +829,7 @@ call `proceed` multiple times. The following listing shows the basic aspect impl
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
public class ConcurrentOperationExecutor implements Ordered {
@@ -859,7 +869,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
class ConcurrentOperationExecutor : Ordered {
@@ -943,7 +953,7 @@ to annotate the implementation of service operations, as the following example s
======
Java::
+
[source,java,indent=0,subs="verbatim"]
[source,java,indent=0,subs="verbatim",role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
// marker annotation
@@ -953,7 +963,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
// marker annotation
@@ -971,3 +981,7 @@ pointcut expression so that only `@Idempotent` operations match, as follows:
expression="execution(* com.xyz.service.*.*(..)) and
@annotation(com.xyz.service.Idempotent)"/>
----

Some files were not shown because too many files have changed in this diff Show More