mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34764252dc | |||
| bb17ad8314 | |||
| 63486bf19c | |||
| d0bff584d7 | |||
| 8be5010f5d | |||
| 86761cd233 | |||
| 07cdef410e | |||
| e74406afd0 | |||
| fe4fd00429 | |||
| 1703b71563 | |||
| 1911ca728d | |||
| f4a73b79b8 | |||
| 289c35c829 | |||
| 749145be2a | |||
| 28eece3da1 | |||
| 2b8225f11c | |||
| 1e6767ac03 | |||
| 7a250aa8ea | |||
| 21bc8726d6 | |||
| df7ac9b308 | |||
| 43217bdf06 | |||
| a31945f8b6 | |||
| a0edf129eb | |||
| bcffa15c7d | |||
| f9d2641fd5 | |||
| 3b506e11a1 | |||
| ddee309e11 | |||
| 7e4884fcfa | |||
| 38a3d18256 | |||
| 6c74fee5e8 | |||
| 36e84a5209 | |||
| 1e97b2137b | |||
| 5e4e530c59 | |||
| 76b2d13b2c | |||
| 9b85a246d8 | |||
| 5a99407847 | |||
| 05bd246cab | |||
| 0127de5a7a | |||
| fcc99a67b6 | |||
| 8afff3359b | |||
| c57c2272a1 | |||
| 8ec23a0fcc | |||
| 8badae9d72 | |||
| 444e7eeb74 | |||
| cb6a5baac5 | |||
| 3b2a818f1e | |||
| 84d1122190 | |||
| 722dcae1b1 | |||
| 850a0de1b0 | |||
| 321e8a58ae | |||
| 46ba13b645 | |||
| 9d9e621efe | |||
| 5aa38833dd | |||
| 9aac24c18a | |||
| ea665d776d | |||
| 7a2fc2ff09 | |||
| 52849819de | |||
| bcdc991838 | |||
| 589f17f48b | |||
| 9366308285 | |||
| 12b996b07d | |||
| 656d08f075 | |||
| 6e9a19212f | |||
| 83ff8e4e98 | |||
| cbdaafd409 | |||
| e5c505b71a | |||
| af8dc44469 | |||
| 955602bdbd | |||
| b62662296e | |||
| a3b737ed00 | |||
| 61d1fde797 | |||
| 0bb309f433 | |||
| e8630f3409 | |||
| c250817284 | |||
| 77bdbf7e37 | |||
| 2fe7ab1f92 | |||
| 6becfe2508 | |||
| dfcd8374c7 | |||
| ae5dd54115 | |||
| e088892fc1 | |||
| 152914a752 | |||
| 0f3f979d16 | |||
| f8875ea970 | |||
| c9375167fd | |||
| 48a6bd6ce7 | |||
| e15f123486 | |||
| 2bfff7fc37 | |||
| 1880e109c3 | |||
| 611d3e5551 | |||
| fa2a58b9db | |||
| 83ca2c0cff | |||
| dc16f3cffb | |||
| f2b3263fff | |||
| 1d890a8952 | |||
| 3ccaefe38f | |||
| 300f4585be | |||
| a0f5c16627 | |||
| b5a86dec92 | |||
| c55c64427c | |||
| a4fcd301f2 | |||
| ec4558dc14 | |||
| d6c623be40 | |||
| 86da45ba15 | |||
| 6a60298024 | |||
| f8b448e949 | |||
| 52a0e6f900 | |||
| 3d2fb3af3d | |||
| 4f38079042 | |||
| 3008d97f93 | |||
| f0c758498b | |||
| bc2deef951 | |||
| 7322a0d3c5 | |||
| 7227c30917 | |||
| 7e7f55cb23 | |||
| 84f20cd0a7 | |||
| 3d8f1fb00f | |||
| e06115c06f | |||
| 69850cad27 | |||
| bbbc95f773 | |||
| 83f7996c79 | |||
| 0544dfe090 | |||
| eacfd77d6a | |||
| 89338c91a9 | |||
| 51641ece72 | |||
| 06d267f04e | |||
| dfa6b4bd42 | |||
| 099a97740e | |||
| daea3f0eae | |||
| 8974da2a5a | |||
| ab236c7741 | |||
| 61adf2dd25 | |||
| 61894af0bd | |||
| c74666a883 | |||
| 976b4f3533 | |||
| 100da83913 | |||
| e881c70a93 | |||
| fea237c065 | |||
| 8b11ee9ee2 | |||
| 1cf5264163 | |||
| c68c6faa03 | |||
| 6dd5c85ed0 | |||
| d133ab60ee | |||
| 5d6e143ff4 | |||
| 4e2fb308f6 | |||
| 5f765fc8ce | |||
| 6b456b6157 | |||
| 000b563e83 | |||
| c571ee1f95 | |||
| 899f6308d9 | |||
| a580d6d6fc | |||
| 66eddf99af | |||
| e94ec80df4 | |||
| 2861e570fd | |||
| ee7a1e8b7e |
@@ -0,0 +1,20 @@
|
||||
name: Await HTTP Resource
|
||||
description: Waits for an HTTP resource to be available (a HEAD request succeeds)
|
||||
inputs:
|
||||
url:
|
||||
description: 'The 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"
|
||||
@@ -0,0 +1,56 @@
|
||||
name: 'Build'
|
||||
description: 'Builds the project, optionally publishing it to a local deployment repository'
|
||||
inputs:
|
||||
java-version:
|
||||
required: false
|
||||
default: '17'
|
||||
description: 'The Java version to compile and test with'
|
||||
java-early-access:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether the Java version is in early access'
|
||||
java-toolchain:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
publish:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
develocity-access-key:
|
||||
required: false
|
||||
description: 'The access key for authentication with ge.spring.io'
|
||||
outputs:
|
||||
build-scan-url:
|
||||
description: 'The URL, if any, of the build scan produced by the build'
|
||||
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
|
||||
version:
|
||||
description: 'The version that was built'
|
||||
value: ${{ steps.read-version.outputs.version }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Gradle Build
|
||||
uses: ./.github/actions/prepare-gradle-build
|
||||
with:
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
java-version: ${{ inputs.java-version }}
|
||||
java-early-access: ${{ inputs.java-early-access }}
|
||||
java-toolchain: ${{ inputs.java-toolchain }}
|
||||
- name: Build
|
||||
id: build
|
||||
if: ${{ inputs.publish == 'false' }}
|
||||
shell: bash
|
||||
run: ./gradlew check antora
|
||||
- name: Publish
|
||||
id: publish
|
||||
if: ${{ inputs.publish == 'true' }}
|
||||
shell: bash
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Read Version From gradle.properties
|
||||
id: read-version
|
||||
shell: bash
|
||||
run: |
|
||||
version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties)
|
||||
echo "Version is $version"
|
||||
echo "version=$version" >> $GITHUB_OUTPUT
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Create GitHub Release
|
||||
description: Create the release on GitHub with a changelog
|
||||
inputs:
|
||||
milestone:
|
||||
description: Name of the GitHub milestone for which a release will be created
|
||||
required: true
|
||||
token:
|
||||
description: Token to use for authentication with GitHub
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
|
||||
with:
|
||||
milestone: ${{ inputs.milestone }}
|
||||
token: ${{ inputs.token }}
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
shell: bash
|
||||
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
|
||||
+9
-1
@@ -17,4 +17,12 @@ changelog:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "poutsma"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "simonbasle"
|
||||
- "snicoll"
|
||||
@@ -0,0 +1,49 @@
|
||||
name: 'Prepare Gradle Build'
|
||||
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
|
||||
inputs:
|
||||
java-version:
|
||||
required: false
|
||||
default: '17'
|
||||
description: 'The Java version to use for the build'
|
||||
java-early-access:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether the Java version is in early access'
|
||||
java-toolchain:
|
||||
required: false
|
||||
default: 'false'
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
develocity-access-key:
|
||||
required: false
|
||||
description: 'The access key for authentication with ge.spring.io'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || 'liberica' }}
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
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 }} >> $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,4 +1,4 @@
|
||||
name: Send notification
|
||||
name: Send Notification
|
||||
description: Sends a Google Chat message as a notification of the job's outcome
|
||||
inputs:
|
||||
webhook-url:
|
||||
|
||||
@@ -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
|
||||
spring-framework-version:
|
||||
description: 'The version of Spring Framework that is being synced to Central'
|
||||
required: true
|
||||
ossrh-s01-token-username:
|
||||
description: 'Username for authentication with s01.oss.sonatype.org'
|
||||
required: true
|
||||
ossrh-s01-token-password:
|
||||
description: 'Password for authentication with s01.oss.sonatype.org'
|
||||
required: true
|
||||
ossrh-s01-staging-profile:
|
||||
description: 'Staging profile to use when syncing to Central'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.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:
|
||||
username: ${{ inputs.ossrh-s01-token-username }}
|
||||
password: ${{ inputs.ossrh-s01-token-password }}
|
||||
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
|
||||
create: true
|
||||
upload: true
|
||||
close: true
|
||||
release: true
|
||||
generate-checksums: true
|
||||
- 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/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Build and deploy snapshot
|
||||
name: Build and Deploy Snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -7,43 +7,26 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build and deploy snapshot
|
||||
name: Build and Deploy Snapshot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Check out code
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
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: Build and publish
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@v0.0.1
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
|
||||
build-name: 'spring-framework-6.1.x'
|
||||
repository: 'libs-snapshot-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
@@ -52,11 +35,24 @@ jobs:
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
- name: Send notification
|
||||
- name: Send Notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-deploy-snapshot
|
||||
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:
|
||||
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
|
||||
|
||||
+17
-37
@@ -7,6 +7,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -18,61 +21,38 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 22
|
||||
toolchain: true
|
||||
- version: 23
|
||||
early-access: true
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: |
|
||||
${{ matrix.java.version }}
|
||||
${{ matrix.java.toolchain && '17' || '' }}
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: |
|
||||
git config --global core.autocrlf true
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check out code
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
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: ${{ matrix.java.toolchain }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ matrix.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', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
|
||||
- name: Build
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew check antora
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
java-version: ${{ matrix.java.version }}
|
||||
java-early-access: ${{ matrix.java.early-access || 'false' }}
|
||||
java-toolchain: ${{ matrix.java.toolchain }}
|
||||
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
- name: Send Notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v6.1.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build and Stage Release
|
||||
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.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
repository: 'libs-staging-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
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
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
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 }}
|
||||
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@105617d23456a69a92485207c4f28ae12297581d # v4.2.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 }}
|
||||
+2
-4
@@ -1,13 +1,11 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
name: "Validate Gradle Wrapper"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
- uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Verify
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
staging:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
secrets:
|
||||
repository-username:
|
||||
required: false
|
||||
repository-password:
|
||||
required: false
|
||||
google-chat-webhook-url:
|
||||
required: true
|
||||
token:
|
||||
required: true
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
ref: 'v0.0.2'
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: spring-framework
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
- name: Run Release Verification Tests
|
||||
env:
|
||||
RVT_VERSION: ${{ inputs.version }}
|
||||
RVT_RELEASE_TYPE: oss
|
||||
RVT_STAGING: ${{ inputs.staging }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
- name: Send Notification
|
||||
uses: ./spring-framework/.github/actions/send-notification
|
||||
if: failure()
|
||||
with:
|
||||
webhook-url: ${{ secrets.google-chat-webhook-url }}
|
||||
status: ${{ job.status }}
|
||||
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.11-librca
|
||||
java=17.0.12-librca
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@ plugins {
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
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 'me.champeau.mrjar' version '0.1.1'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -62,9 +62,11 @@ class TestConventions {
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
}
|
||||
test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
test.jvmArgs(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"-Djava.locale.providers=COMPAT");
|
||||
"-Xshare:off"
|
||||
);
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
|
||||
@@ -26,7 +26,9 @@ import org.gradle.api.attributes.LibraryElements;
|
||||
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.testing.Test;
|
||||
import org.gradle.testing.base.TestingExtension;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -47,6 +49,8 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
public void apply(Project project) {
|
||||
|
||||
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
|
||||
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
|
||||
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
|
||||
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
@@ -55,6 +59,8 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.systemProperty("java.awt.headless", "true");
|
||||
test.systemProperty("org.graalvm.nativeimage.imagecode", "runtime");
|
||||
test.setTestClassesDirs(jvmTestSuite.getSources().getOutput().getClassesDirs());
|
||||
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
|
||||
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
|
||||
});
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
|
||||
First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
|
||||
|
||||
You can then login with the instance using:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring login -n spring-framework -c https://ci.spring.io
|
||||
----
|
||||
|
||||
Once logged in, you should get something like:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly ts
|
||||
name url team expiry
|
||||
spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
|
||||
----
|
||||
|
||||
=== Pipeline configuration and structure
|
||||
|
||||
The build pipelines are described in `pipeline.yml` file.
|
||||
|
||||
This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
|
||||
|
||||
It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
|
||||
|
||||
The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
|
||||
|
||||
You'll find in this folder the following resources:
|
||||
|
||||
* `pipeline.yml` the build pipeline
|
||||
* `parameters.yml` the build parameters used for the pipeline
|
||||
* `images/` holds the container images definitions used in this pipeline
|
||||
* `scripts/` holds the build scripts that ship within the CI container images
|
||||
* `tasks` contains the task definitions used in the main `pipeline.yml`
|
||||
|
||||
=== Updating the build pipeline
|
||||
|
||||
Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
|
||||
|
||||
The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
@@ -1,10 +0,0 @@
|
||||
logging:
|
||||
level:
|
||||
io.spring.concourse: DEBUG
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
sonatype:
|
||||
exclude:
|
||||
- 'build-info\.json'
|
||||
- '.*\.zip'
|
||||
@@ -1,21 +0,0 @@
|
||||
== CI Images
|
||||
|
||||
These images are used by CI to run the actual builds.
|
||||
|
||||
To build the image locally run the following from this directory:
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f <image-folder>/Dockerfile .
|
||||
----
|
||||
|
||||
For example
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
|
||||
----
|
||||
|
||||
To test run:
|
||||
|
||||
----
|
||||
$ docker run -it --entrypoint /bin/bash <SHA>
|
||||
----
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM ubuntu:jammy-20240125
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK21 /opt/openjdk/java21
|
||||
ENV JDK23 /opt/openjdk/java23
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
|
||||
;;
|
||||
java21)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java23)
|
||||
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
###########################################################
|
||||
# UTILS
|
||||
###########################################################
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
|
||||
|
||||
###########################################################
|
||||
# JAVA
|
||||
###########################################################
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java21 java23
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
pushd $jdk > /dev/null
|
||||
curl -L ${JDK_URL} | tar zx --strip-components=1
|
||||
test -f bin/java
|
||||
test -f bin/javac
|
||||
popd > /dev/null
|
||||
done
|
||||
popd
|
||||
|
||||
###########################################################
|
||||
# GRADLE ENTERPRISE
|
||||
###########################################################
|
||||
cd /
|
||||
mkdir ~/.gradle
|
||||
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
|
||||
@@ -1,10 +0,0 @@
|
||||
github-repo: "https://github.com/spring-projects/spring-framework.git"
|
||||
github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "6.1.x"
|
||||
milestone: "6.1.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
-293
@@ -1,293 +0,0 @@
|
||||
anchors:
|
||||
git-repo-resource-source: &git-repo-resource-source
|
||||
uri: ((github-repo))
|
||||
username: ((github-username))
|
||||
password: ((github-ci-release-token))
|
||||
branch: ((branch))
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((s01-user-token))
|
||||
SONATYPE_PASSWORD: ((s01-user-token-password))
|
||||
SONATYPE_URL: ((sonatype-url))
|
||||
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
ARTIFACTORY_SERVER: ((artifactory-server))
|
||||
ARTIFACTORY_USERNAME: ((artifactory-username))
|
||||
ARTIFACTORY_PASSWORD: ((artifactory-password))
|
||||
build-project-task-params: &build-project-task-params
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
changelog-task-params: &changelog-task-params
|
||||
name: generated-changelog/tag
|
||||
tag: generated-changelog/tag
|
||||
body: generated-changelog/changelog.md
|
||||
github-task-params: &github-task-params
|
||||
GITHUB_USERNAME: ((github-username))
|
||||
GITHUB_TOKEN: ((github-ci-release-token))
|
||||
|
||||
resource_types:
|
||||
- name: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.8.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.18
|
||||
- name: github-release
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.8.0
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
<<: *git-repo-resource-source
|
||||
- name: ci-images-git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
uri: ((github-repo))
|
||||
branch: ((branch))
|
||||
paths: ["ci/images/*"]
|
||||
- name: ci-image
|
||||
type: registry-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
source:
|
||||
uri: ((artifactory-server))
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: github-pre-release
|
||||
type: github-release
|
||||
icon: briefcase-download-outline
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: true
|
||||
release: false
|
||||
- name: github-release
|
||||
type: github-release
|
||||
icon: briefcase-download
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: false
|
||||
jobs:
|
||||
- name: build-ci-images
|
||||
plan:
|
||||
- get: git-repo
|
||||
- get: ci-images-git-repo
|
||||
trigger: true
|
||||
- task: build-ci-image
|
||||
privileged: true
|
||||
file: git-repo/ci/tasks/build-ci-image.yml
|
||||
output_mapping:
|
||||
image: ci-image
|
||||
vars:
|
||||
ci-image-name: ci-image
|
||||
<<: *docker-resource-source
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-staging-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-milestone]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *github-task-params
|
||||
<<: *docker-resource-source
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-rc]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *github-task-params
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-release]
|
||||
params:
|
||||
download_artifacts: true
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
<<: *sonatype-task-params
|
||||
- name: create-github-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: artifactory-repo
|
||||
trigger: true
|
||||
passed: [promote-release]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *github-task-params
|
||||
- put: github-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-ci-images"]
|
||||
@@ -1,2 +0,0 @@
|
||||
source /opt/concourse-java.sh
|
||||
setup_symlinks
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
|
||||
java -jar /github-changelog-generator.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
|
||||
${version} generated-changelog/changelog.md
|
||||
|
||||
echo ${version} > generated-changelog/version
|
||||
echo v${version} > generated-changelog/tag
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
echo "Promotion complete"
|
||||
echo $version > version/version
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
git fetch --tags --all > /dev/null
|
||||
popd > /dev/null
|
||||
|
||||
git clone git-repo stage-git-repo > /dev/null
|
||||
|
||||
pushd stage-git-repo > /dev/null
|
||||
|
||||
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
|
||||
if [[ $RELEASE_TYPE = "M" ]]; then
|
||||
stageVersion=$( get_next_milestone_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RC" ]]; then
|
||||
stageVersion=$( get_next_rc_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
|
||||
stageVersion=$( get_next_release $snapshotVersion)
|
||||
nextVersion=$( bump_version_number $snapshotVersion)
|
||||
else
|
||||
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
|
||||
fi
|
||||
|
||||
echo "Staging $stageVersion (next version will be $nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
|
||||
|
||||
git config user.name "Spring Builds" > /dev/null
|
||||
git config user.email "spring-builds@users.noreply.github.com" > /dev/null
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Release v$stageVersion" > /dev/null
|
||||
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
|
||||
|
||||
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
build publishAllPublicationsToDeploymentRepository
|
||||
|
||||
git reset --hard HEAD^ > /dev/null
|
||||
if [[ $nextVersion != $snapshotVersion ]]; then
|
||||
echo "Setting next development version (v$nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Next development version (v$nextVersion)" > /dev/null
|
||||
fi;
|
||||
|
||||
echo "Staging Complete"
|
||||
|
||||
popd > /dev/null
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
tag: 0.10.0
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: ci-images-git-repo
|
||||
outputs:
|
||||
- name: image
|
||||
caches:
|
||||
- path: ci-image-cache
|
||||
params:
|
||||
CONTEXT: ci-images-git-repo/ci/images
|
||||
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
|
||||
DOCKER_HUB_AUTH: ((docker-hub-auth))
|
||||
run:
|
||||
path: /bin/sh
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
mkdir -p /root/.docker
|
||||
cat > /root/.docker/config.json <<EOF
|
||||
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
|
||||
EOF
|
||||
build
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.8'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: generated-changelog
|
||||
params:
|
||||
GITHUB_ORGANIZATION:
|
||||
GITHUB_REPO:
|
||||
GITHUB_USERNAME:
|
||||
GITHUB_TOKEN:
|
||||
RELEASE_TYPE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/generate-changelog.sh
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.4.0'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: version
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
ARTIFACTORY_SERVER:
|
||||
ARTIFACTORY_USERNAME:
|
||||
ARTIFACTORY_PASSWORD:
|
||||
SONATYPE_USER:
|
||||
SONATYPE_PASSWORD:
|
||||
SONATYPE_URL:
|
||||
SONATYPE_STAGING_PROFILE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/promote-version.sh
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: stage-git-repo
|
||||
- name: distribution-repository
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
caches:
|
||||
- path: gradle
|
||||
run:
|
||||
path: git-repo/ci/scripts/stage-version.sh
|
||||
@@ -28,7 +28,7 @@ javadoc {
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
|
||||
destinationDir = file("$project.docsDir/javadoc-api")
|
||||
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
|
||||
splitIndex = true
|
||||
links(rootProject.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
@@ -52,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(file("$docsDir/kdoc-api"))
|
||||
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ asciidoc:
|
||||
fold: 'all'
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
spring-site-cve: "{spring-site}/security"
|
||||
@@ -70,6 +71,8 @@ asciidoc:
|
||||
kotlin-coroutines-api: '{kotlin-site}/api/kotlinx.coroutines'
|
||||
kotlin-github-org: 'https://github.com/Kotlin'
|
||||
kotlin-issues: 'https://youtrack.jetbrains.com/issue'
|
||||
micrometer-docs: 'https://docs.micrometer.io/micrometer/reference'
|
||||
micrometer-context-propagation-docs: 'https://docs.micrometer.io/context-propagation/reference'
|
||||
reactive-streams-site: 'https://www.reactive-streams.org'
|
||||
reactive-streams-spec: 'https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md#specification'
|
||||
reactor-github-org: 'https://github.com/reactor'
|
||||
@@ -87,4 +90,5 @@ asciidoc:
|
||||
stackoverflow-questions: '{stackoverflow-site}/questions'
|
||||
stackoverflow-spring-tag: "{stackoverflow-questions}/tagged/spring"
|
||||
stackoverflow-spring-kotlin-tags: "{stackoverflow-spring-tag}+kotlin"
|
||||
testcontainers-site: 'https://www.testcontainers.org'
|
||||
testcontainers-site: 'https://www.testcontainers.org'
|
||||
vavr-docs: 'https://vavr-io.github.io/vavr-docs'
|
||||
@@ -43,10 +43,15 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-web"))
|
||||
api(project(":spring-webflux"))
|
||||
|
||||
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")
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
= Using `depends-on`
|
||||
|
||||
If a bean is a dependency of another bean, that usually means that one bean is set as a
|
||||
property of another. Typically you accomplish this with the <<beans-ref-element, `<ref/>`
|
||||
element>> in XML-based configuration metadata. However, sometimes dependencies between
|
||||
beans are less direct. An example is when a static initializer in a class needs to be
|
||||
triggered, such as for database driver registration. The `depends-on` attribute can
|
||||
explicitly force one or more beans to be initialized before the bean using this element
|
||||
is initialized. The following example uses the `depends-on` attribute to express a
|
||||
dependency on a single bean:
|
||||
property of another. Typically you accomplish this with the
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
|
||||
in XML-based configuration metadata. However, sometimes dependencies between beans are
|
||||
less direct. An example is when a static initializer in a class needs to be triggered,
|
||||
such as for database driver registration. The `depends-on` attribute can explicitly force
|
||||
one or more beans to be initialized before the bean using this element is initialized.
|
||||
The following example uses the `depends-on` attribute to express a dependency on a single
|
||||
bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ the original class. Consider the following example:
|
||||
<!-- inject dependencies here as required -->
|
||||
</bean>
|
||||
|
||||
<!-- commandProcessor uses statefulCommandHelper -->
|
||||
<!-- commandManager uses myCommand prototype bean -->
|
||||
<bean id="commandManager" class="fiona.apple.CommandManager">
|
||||
<lookup-method name="createCommand" bean="myCommand"/>
|
||||
</bean>
|
||||
|
||||
@@ -431,7 +431,7 @@ Kotlin::
|
||||
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` with the following:
|
||||
|
||||
- Error codes `"Size.student.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
|
||||
- Error codes `"Size.person.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
|
||||
- Message arguments `"name"`, `10`, and `1` (the field name and the constraint attributes)
|
||||
- Default message "size must be between 1 and 10"
|
||||
|
||||
@@ -439,14 +439,14 @@ To customize the default message, you can add properties to
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundles using any of the above errors codes and message arguments. Note also that the
|
||||
message argument `"name"` is itself a `MessagreSourceResolvable` with error codes
|
||||
`"student.name"` and `"name"` and can customized too. For example:
|
||||
`"person.name"` and `"name"` and can customized too. For example:
|
||||
|
||||
Properties::
|
||||
+
|
||||
[source,properties,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
|
||||
student.name=username
|
||||
Size.person.name=Please, provide a {0} that is between {2} and {1} characters long
|
||||
person.name=username
|
||||
----
|
||||
|
||||
A `ConstraintViolation` on the `degrees` method parameter is adapted to a
|
||||
|
||||
@@ -211,19 +211,28 @@ the JDBC driver. If the count is not available, the JDBC driver returns a value
|
||||
====
|
||||
In such a scenario, with automatic setting of values on an underlying `PreparedStatement`,
|
||||
the corresponding JDBC type for each value needs to be derived from the given Java type.
|
||||
While this usually works well, there is a potential for issues (for example, with Map-contained
|
||||
`null` values). Spring, by default, calls `ParameterMetaData.getParameterType` in such a
|
||||
case, which can be expensive with your JDBC driver. You should use a recent driver
|
||||
While this usually works well, there is a potential for issues (for example, with
|
||||
Map-contained `null` values). Spring, by default, calls `ParameterMetaData.getParameterType`
|
||||
in such a case, which can be expensive with your JDBC driver. You should use a recent driver
|
||||
version and consider setting the `spring.jdbc.getParameterType.ignore` property to `true`
|
||||
(as a JVM system property or via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) if you encounter
|
||||
a performance issue (as reported on Oracle 12c, JBoss, and PostgreSQL).
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism)
|
||||
if you encounter a specific performance issue for your application.
|
||||
|
||||
Alternatively, you might consider specifying the corresponding JDBC types explicitly,
|
||||
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit type
|
||||
array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
|
||||
custom `MapSqlParameterSource` instance, or through a `BeanPropertySqlParameterSource`
|
||||
that derives the SQL type from the Java-declared property type even for a null value.
|
||||
As of 6.1.2, Spring bypasses the default `getParameterType` resolution on PostgreSQL and
|
||||
MS SQL Server. This is a common optimization to avoid further roundtrips to the DBMS just
|
||||
for parameter type resolution which is known to make a very significant difference on
|
||||
PostgreSQL and MS SQL Server specifically, in particular for batch operations. If you
|
||||
happen to see a side effect e.g. when setting a byte array to null without specific type
|
||||
indication, you may explicitly set the `spring.jdbc.getParameterType.ignore=false` flag
|
||||
as a system property (see above) to restore full `getParameterType` resolution.
|
||||
|
||||
Alternatively, you could consider specifying the corresponding JDBC types explicitly,
|
||||
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit
|
||||
type array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
|
||||
custom `MapSqlParameterSource` instance, through a `BeanPropertySqlParameterSource`
|
||||
that derives the SQL type from the Java-declared property type even for a null value, or
|
||||
through providing individual `SqlParameterValue` instances instead of plain null values.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -209,141 +209,26 @@ are passed in as a parameter to the stored procedure.
|
||||
|
||||
The `SqlReturnType` interface has a single method (named `getTypeValue`) that must be
|
||||
implemented. This interface is used as part of the declaration of an `SqlOutParameter`.
|
||||
The following example shows returning the value of an Oracle `STRUCT` object of the user
|
||||
The following example shows returning the value of a `java.sql.Struct` object of the user
|
||||
declared type `ITEM_TYPE`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class TestItemStoredProcedure extends StoredProcedure {
|
||||
|
||||
public TestItemStoredProcedure(DataSource dataSource) {
|
||||
// ...
|
||||
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
|
||||
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
|
||||
STRUCT struct = (STRUCT) cs.getObject(colIndx);
|
||||
Object[] attr = struct.getAttributes();
|
||||
TestItem item = new TestItem();
|
||||
item.setId(((Number) attr[0]).longValue());
|
||||
item.setDescription((String) attr[1]);
|
||||
item.setExpirationDate((java.util.Date) attr[2]);
|
||||
return item;
|
||||
}));
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
// ...
|
||||
declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
|
||||
val struct = cs.getObject(colIndx) as STRUCT
|
||||
val attr = struct.getAttributes()
|
||||
TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
|
||||
})
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./TestItemStoredProcedure[]
|
||||
|
||||
You can use `SqlTypeValue` to pass the value of a Java object (such as `TestItem`) to a
|
||||
stored procedure. The `SqlTypeValue` interface has a single method (named
|
||||
`createTypeValue`) that you must implement. The active connection is passed in, and you
|
||||
can use it to create database-specific objects, such as `StructDescriptor` instances
|
||||
or `ArrayDescriptor` instances. The following example creates a `StructDescriptor` instance:
|
||||
can use it to create database-specific objects, such as `java.sql.Struct` instances
|
||||
or `java.sql.Array` instances. The following example creates a `java.sql.Struct` instance:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
final TestItem testItem = new TestItem(123L, "A test item",
|
||||
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
|
||||
Struct item = new STRUCT(itemDescriptor, conn,
|
||||
new Object[] {
|
||||
testItem.getId(),
|
||||
testItem.getDescription(),
|
||||
new java.sql.Date(testItem.getExpirationDate().getTime())
|
||||
});
|
||||
return item;
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val (id, description, expirationDate) = TestItem(123L, "A test item",
|
||||
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
|
||||
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val itemDescriptor = StructDescriptor(typeName, conn)
|
||||
return STRUCT(itemDescriptor, conn,
|
||||
arrayOf(id, description, java.sql.Date(expirationDate.time)))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./SqlTypeValueFactory[tag=struct,indent=0]
|
||||
|
||||
You can now add this `SqlTypeValue` to the `Map` that contains the input parameters for the
|
||||
`execute` call of the stored procedure.
|
||||
|
||||
Another use for the `SqlTypeValue` is passing in an array of values to an Oracle stored
|
||||
procedure. Oracle has its own internal `ARRAY` class that must be used in this case, and
|
||||
you can use the `SqlTypeValue` to create an instance of the Oracle `ARRAY` and populate
|
||||
it with values from the Java `ARRAY`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
final Long[] ids = new Long[] {1L, 2L};
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
|
||||
ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
|
||||
return idArray;
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
val ids = arrayOf(1L, 2L)
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val arrayDescriptor = ArrayDescriptor(typeName, conn)
|
||||
return ARRAY(arrayDescriptor, conn, ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
procedure. Oracle has an `createOracleArray` method on `OracleConnection` that you can
|
||||
access by unwrapping it. You can use the `SqlTypeValue` to create an array and populate
|
||||
it with values from the Java `java.sql.Array`, as the following example shows:
|
||||
|
||||
include-code::./SqlTypeValueFactory[tag=oracle-array,indent=0]
|
||||
|
||||
|
||||
@@ -323,12 +323,12 @@ use these alternative input classes.
|
||||
|
||||
The `SimpleJdbcCall` class uses metadata in the database to look up names of `in`
|
||||
and `out` parameters so that you do not have to explicitly declare them. You can
|
||||
declare parameters if you prefer to do that or if you have parameters (such as `ARRAY`
|
||||
or `STRUCT`) that do not have an automatic mapping to a Java class. The first example
|
||||
shows a simple procedure that returns only scalar values in `VARCHAR` and `DATE` format
|
||||
from a MySQL database. The example procedure reads a specified actor entry and returns
|
||||
`first_name`, `last_name`, and `birth_date` columns in the form of `out` parameters.
|
||||
The following listing shows the first example:
|
||||
declare parameters if you prefer to do that or if you have parameters that do not
|
||||
have an automatic mapping to a Java class. The first example shows a simple procedure
|
||||
that returns only scalar values in `VARCHAR` and `DATE` format from a MySQL database.
|
||||
The example procedure reads a specified actor entry and returns `first_name`,
|
||||
`last_name`, and `birth_date` columns in the form of `out` parameters. The following
|
||||
listing shows the first example:
|
||||
|
||||
[source,sql,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ As of Spring Framework 5.2, the default configuration also provides support for
|
||||
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
|
||||
This allows you to handle functional-style errors using Try and have the transaction
|
||||
automatically rolled back in case of a failure. For more information on Vavr's Try,
|
||||
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
|
||||
refer to the {vavr-docs}/#_try[official Vavr documentation].
|
||||
Here's an example of how to use Vavr's Try with a transactional method:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -524,7 +524,7 @@ To enable caching annotations add the annotation `@EnableCaching` to one of your
|
||||
----
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class AppConfig {
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
CacheManager cacheManager() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[observability]]
|
||||
= Observability Support
|
||||
|
||||
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Micrometer defines an {micrometer-docs}/observation.html[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
@@ -38,7 +38,7 @@ As outlined xref:integration/observability.adoc[at the beginning of this section
|
||||
|===
|
||||
|
||||
NOTE: Observations are using Micrometer's official naming convention, but Metrics names will be automatically converted
|
||||
https://micrometer.io/docs/concepts#_naming_meters[to the format preferred by the monitoring system backend]
|
||||
{micrometer-docs}/concepts/naming.html[to the format preferred by the monitoring system backend]
|
||||
(Prometheus, Atlas, Graphite, InfluxDB...).
|
||||
|
||||
|
||||
@@ -162,6 +162,8 @@ include-code::./JmsTemplatePublish[]
|
||||
|
||||
It uses the `io.micrometer.jakarta9.instrument.jms.DefaultJmsPublishObservationConvention` by default, backed by the `io.micrometer.jakarta9.instrument.jms.JmsPublishObservationContext`.
|
||||
|
||||
Similar observations are recorded with `@JmsListener` annotated methods when response messages are returned from the listener method.
|
||||
|
||||
[[observability.jms.process]]
|
||||
=== JMS message Processing instrumentation
|
||||
|
||||
@@ -366,7 +368,7 @@ This means that during the execution of that task, the ThreadLocals and logging
|
||||
|
||||
If the application globally configures a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
|
||||
All `@EventListener` methods will be processed on a different thread, outside the main event publication thread.
|
||||
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagate such values and better correlate the processing of the events.
|
||||
In these cases, the {micrometer-context-propagation-docs}/[Micrometer Context Propagation library] can help propagate such values and better correlate the processing of the events.
|
||||
The application can configure the chosen `TaskExecutor` to use a `ContextPropagatingTaskDecorator` that decorates tasks and propagates context.
|
||||
For this to work, the `io.micrometer:context-propagation` library must be present on the classpath:
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ String result = restClient.get() <1>
|
||||
.uri("https://example.com/this-url-does-not-exist") <1>
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
|
||||
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <3>
|
||||
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
|
||||
})
|
||||
.body(String.class);
|
||||
----
|
||||
|
||||
@@ -339,6 +339,19 @@ Java::
|
||||
spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectAll(
|
||||
{ spec -> spec.expectStatus().isOk() },
|
||||
{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
|
||||
)
|
||||
----
|
||||
======
|
||||
|
||||
You can then choose to decode the response body through one of the following:
|
||||
@@ -704,4 +717,3 @@ Kotlin::
|
||||
.andExpect(model().attribute("string", "a string value"));
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -688,40 +688,7 @@ You can customize options related to path matching. For details on the individua
|
||||
{spring-framework-api}/web/reactive/config/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc.
|
||||
The following example shows how to use `PathMatchConfigurer`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurer.addPathPrefix(
|
||||
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
fun configurePathMatch(configurer: PathMatchConfigurer) {
|
||||
configurer.addPathPrefix(
|
||||
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WebConfig[]
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
||||
@@ -77,7 +77,7 @@ and others) and is equivalent to `required=false`.
|
||||
| For access to a part in a `multipart/form-data` request. Supports reactive types.
|
||||
See xref:web/webflux/controller/ann-methods/multipart-forms.adoc[Multipart Content] and xref:web/webflux/reactive-spring.adoc#webflux-multipart[Multipart Data].
|
||||
|
||||
| `java.util.Map`, `org.springframework.ui.Model`, and `org.springframework.ui.ModelMap`.
|
||||
| `java.util.Map` or `org.springframework.ui.Model`
|
||||
| For access to the model that is used in HTML controllers and is exposed to templates as
|
||||
part of view rendering.
|
||||
|
||||
@@ -89,9 +89,9 @@ and others) and is equivalent to `required=false`.
|
||||
Note that use of `@ModelAttribute` is optional -- for example, to set its attributes.
|
||||
See "`Any other argument`" later in this table.
|
||||
|
||||
| `Errors`, `BindingResult`
|
||||
| `Errors` or `BindingResult`
|
||||
| For access to errors from validation and data binding for a command object, i.e. a
|
||||
`@ModelAttribute` argument. An `Errors`, or `BindingResult` argument must be declared
|
||||
`@ModelAttribute` argument. An `Errors` or `BindingResult` argument must be declared
|
||||
immediately after the validated method argument.
|
||||
|
||||
| `SessionStatus` + class-level `@SessionAttributes`
|
||||
|
||||
@@ -13,7 +13,7 @@ xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argum
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
|
||||
discussed next). The exception raised in this case is `WebExchangeBindException`.
|
||||
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
@@ -21,7 +21,7 @@ validation must be applied, and that supersedes validation at the method argumen
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
Applications must handle both `WebExchangeBindException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
method signature. The two exceptions, however are designed to be very similar, and can be
|
||||
handled with almost identical code. The main difference is that the former is for a single
|
||||
@@ -39,7 +39,7 @@ method parameters with an `Errors` immediately after. If there are validation er
|
||||
any other method parameter then `HandlerMethodValidationException` is raised.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebFlux config], or locally
|
||||
through an xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder] method in an
|
||||
`@Controller` or `@ControllerAdvice`. You can also use multiple validators.
|
||||
|
||||
@@ -49,8 +49,8 @@ through an AOP proxy. In order to take advantage of the Spring MVC built-in supp
|
||||
method validation added in Spring Framework 6.1, you need to remove the class level
|
||||
`@Validated` annotation from the controller.
|
||||
|
||||
The xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses] section provides further
|
||||
details on how `MethodArgumentNotValidException` and `HandlerMethodValidationException`
|
||||
The xref:web/webflux/ann-rest-exceptions.adoc[Error Responses] section provides further
|
||||
details on how `WebExchangeBindException` and `HandlerMethodValidationException`
|
||||
are handled, and also how their rendering can be customized through a `MessageSource` and
|
||||
locale and language specific resource bundles.
|
||||
|
||||
|
||||
@@ -445,9 +445,8 @@ directly. For example:
|
||||
}
|
||||
----
|
||||
|
||||
For more details, see the
|
||||
https://micrometer.io/docs/contextPropagation[documentation] of the Micrometer Context
|
||||
Propagation library.
|
||||
For more details, see the {micrometer-context-propagation-docs}/[documentation] of the
|
||||
Micrometer Context Propagation library.
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -40,7 +40,9 @@ content of the provided resource to the response `OutputStream`. Note that the
|
||||
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
|
||||
close it after it has been copied to the response. If you are using `InputStreamResource`
|
||||
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
|
||||
(e.g. through a lambda expression that retrieves the actual `InputStream`).
|
||||
(e.g. through a lambda expression that retrieves the actual `InputStream`). Also, custom
|
||||
subclasses of `InputStreamResource` are only supported in combination with a custom
|
||||
`contentLength()` implementation which avoids consuming the stream for that purpose.
|
||||
|
||||
Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type]
|
||||
to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive
|
||||
|
||||
@@ -389,7 +389,7 @@ template.
|
||||
encode URI component value _after_ URI variables are expanded.
|
||||
* `NONE`: No encoding is applied.
|
||||
|
||||
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historic
|
||||
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historical
|
||||
reasons and for backwards compatibility. The `WebClient` relies on the default value
|
||||
in `DefaultUriBuilderFactory`, which was changed from `EncodingMode.URI_COMPONENT` in
|
||||
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.dataaccess.jdbc.jdbccomplextypes;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import oracle.jdbc.driver.OracleConnection;
|
||||
|
||||
import org.springframework.jdbc.core.SqlTypeValue;
|
||||
import org.springframework.jdbc.core.support.AbstractSqlTypeValue;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class SqlTypeValueFactory {
|
||||
|
||||
void createStructSample() throws ParseException {
|
||||
// tag::struct[]
|
||||
TestItem testItem = new TestItem(123L, "A test item",
|
||||
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection connection, int sqlType, String typeName) throws SQLException {
|
||||
Object[] item = new Object[] { testItem.getId(), testItem.getDescription(),
|
||||
new java.sql.Date(testItem.getExpirationDate().getTime()) };
|
||||
return connection.createStruct(typeName, item);
|
||||
}
|
||||
};
|
||||
// end::struct[]
|
||||
}
|
||||
|
||||
void createOracleArray() {
|
||||
// tag::oracle-array[]
|
||||
Long[] ids = new Long[] {1L, 2L};
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
return conn.unwrap(OracleConnection.class).createOracleArray(typeName, ids);
|
||||
}
|
||||
};
|
||||
// end::oracle-array[]
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.dataaccess.jdbc.jdbccomplextypes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class TestItem {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String description;
|
||||
|
||||
private Date expirationDate;
|
||||
|
||||
public TestItem() {
|
||||
}
|
||||
|
||||
public TestItem(Long id, String description, Date expirationDate) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.expirationDate = expirationDate;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Date getExpirationDate() {
|
||||
return this.expirationDate;
|
||||
}
|
||||
|
||||
public void setExpirationDate(Date expirationDate) {
|
||||
this.expirationDate = expirationDate;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.dataaccess.jdbc.jdbccomplextypes;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.Struct;
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.jdbc.core.SqlOutParameter;
|
||||
import org.springframework.jdbc.object.StoredProcedure;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class TestItemStoredProcedure extends StoredProcedure {
|
||||
|
||||
public TestItemStoredProcedure(DataSource dataSource) {
|
||||
super(dataSource, "get_item");
|
||||
declareParameter(new SqlOutParameter("item", Types.STRUCT, "ITEM_TYPE",
|
||||
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
|
||||
Struct struct = (Struct) cs.getObject(colIndx);
|
||||
Object[] attr = struct.getAttributes();
|
||||
TestItem item = new TestItem();
|
||||
item.setId(((Number) attr[0]).longValue());
|
||||
item.setDescription((String) attr[1]);
|
||||
item.setExpirationDate((java.util.Date) attr[2]);
|
||||
return item;
|
||||
}));
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.web.webflux.webfluxconfigpathmatching;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.method.HandlerTypePredicate;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.reactive.config.PathMatchConfigurer;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configurePathMatching(PathMatchConfigurer configurer) {
|
||||
configurer.addPathPrefix(
|
||||
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.dataaccess.jdbc.jdbccomplextypes
|
||||
|
||||
import oracle.jdbc.driver.OracleConnection
|
||||
import org.springframework.jdbc.core.SqlTypeValue
|
||||
import org.springframework.jdbc.core.support.AbstractSqlTypeValue
|
||||
import java.sql.Connection
|
||||
import java.sql.Date
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@Suppress("unused")
|
||||
class SqlTypeValueFactory {
|
||||
|
||||
fun createStructSample(): AbstractSqlTypeValue {
|
||||
// tag::struct[]
|
||||
val testItem = TestItem(123L, "A test item",
|
||||
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
|
||||
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(connection: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val item = arrayOf<Any>(testItem.id, testItem.description,
|
||||
Date(testItem.expirationDate.time))
|
||||
return connection.createStruct(typeName, item)
|
||||
}
|
||||
}
|
||||
// end::struct[]
|
||||
return value
|
||||
}
|
||||
|
||||
fun createOracleArray() : SqlTypeValue {
|
||||
// tag::oracle-array[]
|
||||
val ids = arrayOf(1L, 2L)
|
||||
val value: SqlTypeValue = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
return conn.unwrap(OracleConnection::class.java).createOracleArray(typeName, ids)
|
||||
}
|
||||
}
|
||||
// end::oracle-array[]
|
||||
return value
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.dataaccess.jdbc.jdbccomplextypes
|
||||
|
||||
import org.springframework.jdbc.core.SqlOutParameter
|
||||
import org.springframework.jdbc.`object`.StoredProcedure
|
||||
import java.sql.CallableStatement
|
||||
import java.sql.Struct
|
||||
import java.sql.Types
|
||||
import java.util.Date
|
||||
import javax.sql.DataSource
|
||||
|
||||
@Suppress("unused")
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, "get_item") {
|
||||
init {
|
||||
declareParameter(SqlOutParameter("item",Types.STRUCT,"ITEM_TYPE") {
|
||||
cs: CallableStatement, colIndx: Int, _: Int, _: String? ->
|
||||
val struct = cs.getObject(colIndx) as Struct
|
||||
val attr = struct.attributes
|
||||
val item = TestItem()
|
||||
item.id = (attr[0] as Number).toLong()
|
||||
item.description = attr[1] as String
|
||||
item.expirationDate = attr[2] as Date
|
||||
item
|
||||
})
|
||||
// ...
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.docs.web.webflux.webfluxconfigpathmatching
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.method.HandlerTypePredicate
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import org.springframework.web.reactive.config.PathMatchConfigurer
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configurePathMatching(configurer: PathMatchConfigurer) {
|
||||
configurer.addPathPrefix(
|
||||
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,19 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.7"))
|
||||
api(platform("io.netty:netty-bom:4.1.111.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.9"))
|
||||
api(platform("io.netty:netty-bom:4.1.112.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.7"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.9"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.22"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.26.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.10"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.10"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.12"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.12"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.2"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.10.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.12.0"))
|
||||
|
||||
constraints {
|
||||
@@ -34,6 +34,7 @@ dependencies {
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.3")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
api("com.squareup.okhttp3:okhttp:3.14.9")
|
||||
@@ -55,9 +56,9 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.13.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.13.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.13.Final")
|
||||
api("io.undertow:undertow-core:2.3.15.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.15.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.15.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -101,26 +102,26 @@ dependencies {
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.5")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.24")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.24")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.24")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.28")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.28")
|
||||
api("org.aspectj:aspectjrt:1.9.22.1")
|
||||
api("org.aspectj:aspectjtools:1.9.22.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.22.1")
|
||||
api("org.awaitility:awaitility:4.2.1")
|
||||
api("org.awaitility:awaitility:4.2.0")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.5")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.32")
|
||||
api("org.freemarker:freemarker:2.3.33")
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
@@ -129,16 +130,16 @@ dependencies {
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.2")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.7.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.8.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.14")
|
||||
api("org.mozilla:rhino:1.7.15")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.3")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.1")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.13")
|
||||
api("org.testng:testng:7.9.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
version=6.1.10-SNAPSHOT
|
||||
version=6.1.12
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.9.22
|
||||
kotlinVersion=1.9.24
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
@@ -69,6 +69,13 @@ eclipse.classpath.file.whenMerged {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove Java 21 classpath entries, since we currently use Java 17
|
||||
// within Eclipse. Consequently, Java 21 features managed via the
|
||||
// me.champeau.mrjar plugin cannot be built or tested within Eclipse.
|
||||
eclipse.classpath.file.whenMerged { classpath ->
|
||||
classpath.entries.removeAll { it.path =~ /src\/(main|test)\/java21/ }
|
||||
}
|
||||
|
||||
// Remove classpath entries for non-existent libraries added by the me.champeau.mrjar
|
||||
// plugin, such as "spring-core/build/classes/kotlin/java21".
|
||||
eclipse.classpath.file.whenMerged {
|
||||
@@ -77,6 +84,19 @@ eclipse.classpath.file.whenMerged {
|
||||
}
|
||||
}
|
||||
|
||||
// Due to an apparent bug in Gradle, even though we exclude the "main" classpath
|
||||
// entries for sources generated by XJC in spring-oxm.gradle, the Gradle eclipse
|
||||
// plugin still includes them in the generated .classpath file. So, we have to
|
||||
// manually remove those lingering "main" entries.
|
||||
if (project.name == "spring-oxm") {
|
||||
eclipse.classpath.file.whenMerged { classpath ->
|
||||
classpath.entries.removeAll {
|
||||
it.path =~ /build\/generated\/sources\/xjc\/.+/ &&
|
||||
it.entryAttributes.get("gradle_scope") == "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Include project specific settings
|
||||
task eclipseSettings(type: Copy) {
|
||||
from rootProject.files(
|
||||
|
||||
@@ -3,7 +3,7 @@ apply plugin: 'org.springframework.build.conventions'
|
||||
apply plugin: 'org.springframework.build.optional-dependencies'
|
||||
// Uncomment the following for Shadow support in the jmhJar block.
|
||||
// Currently commented out due to ZipException: archive is not a ZIP archive
|
||||
// apply plugin: 'com.github.johnrengelman.shadow'
|
||||
// apply plugin: 'io.github.goooler.shadow'
|
||||
apply plugin: 'me.champeau.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,8 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
Vendored
+2
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
||||
+95
-1
@@ -20,18 +20,33 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.aopalliance.intercept.MethodInterceptor
|
||||
import org.aopalliance.intercept.MethodInvocation
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.InterceptorConfig
|
||||
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.cache.CacheManager
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.cache.annotation.EnableCaching
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.transaction.testfixture.ReactiveCallCountingTransactionManager
|
||||
import reactor.core.publisher.Mono
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
|
||||
import kotlin.annotation.AnnotationTarget.CLASS
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
import kotlin.annotation.AnnotationTarget.TYPE
|
||||
|
||||
|
||||
/**
|
||||
@@ -43,7 +58,9 @@ import java.lang.reflect.Method
|
||||
class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
@Autowired val echo: Echo,
|
||||
@Autowired val firstAdvisor: TestPointcutAdvisor,
|
||||
@Autowired val secondAdvisor: TestPointcutAdvisor) {
|
||||
@Autowired val secondAdvisor: TestPointcutAdvisor,
|
||||
@Autowired val countingAspect: CountingAspect,
|
||||
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
|
||||
|
||||
@Test
|
||||
fun `Multiple interceptors with regular function`() {
|
||||
@@ -67,8 +84,39 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
|
||||
}
|
||||
|
||||
@Test // gh-33095
|
||||
fun `Aspect and reactive transactional with suspending function`() {
|
||||
assertThat(countingAspect.counter).isZero()
|
||||
assertThat(reactiveTransactionManager.commits).isZero()
|
||||
val value = "Hello!"
|
||||
runBlocking {
|
||||
assertThat(echo.suspendingTransactionalEcho(value)).isEqualTo(value)
|
||||
}
|
||||
assertThat(countingAspect.counter).`as`("aspect applied").isOne()
|
||||
assertThat(reactiveTransactionManager.begun).isOne()
|
||||
assertThat(reactiveTransactionManager.commits).`as`("transactional applied").isOne()
|
||||
}
|
||||
|
||||
@Test // gh-33210
|
||||
fun `Aspect and cacheable with suspending function`() {
|
||||
assertThat(countingAspect.counter).isZero()
|
||||
val value = "Hello!"
|
||||
runBlocking {
|
||||
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
|
||||
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
|
||||
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
|
||||
assertThat(countingAspect.counter).`as`("aspect applied once").isOne()
|
||||
|
||||
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
|
||||
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
|
||||
}
|
||||
assertThat(countingAspect.counter).`as`("aspect applied once per key").isEqualTo(2)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
@EnableTransactionManagement
|
||||
@EnableCaching
|
||||
open class InterceptorConfig {
|
||||
|
||||
@Bean
|
||||
@@ -77,6 +125,18 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
@Bean
|
||||
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
|
||||
|
||||
@Bean
|
||||
open fun countingAspect() = CountingAspect()
|
||||
|
||||
@Bean
|
||||
open fun transactionManager(): ReactiveCallCountingTransactionManager {
|
||||
return ReactiveCallCountingTransactionManager()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun cacheManager(): CacheManager {
|
||||
return ConcurrentMapCacheManager()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun echo(): Echo {
|
||||
@@ -107,6 +167,24 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
}
|
||||
}
|
||||
|
||||
@Target(CLASS, FUNCTION, ANNOTATION_CLASS, TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Counting()
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
class CountingAspect {
|
||||
|
||||
var counter: Long = 0
|
||||
|
||||
@Around("@annotation(org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.Counting)")
|
||||
fun logging(joinPoint: ProceedingJoinPoint): Any {
|
||||
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
|
||||
counter++
|
||||
}.checkpoint("CountingAspect")
|
||||
}
|
||||
}
|
||||
|
||||
open class Echo {
|
||||
|
||||
open fun echo(value: String): String {
|
||||
@@ -118,6 +196,22 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
return value
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Counting
|
||||
open suspend fun suspendingTransactionalEcho(value: String): String {
|
||||
delay(1)
|
||||
return value
|
||||
}
|
||||
|
||||
open var cacheCounter: Int = 0
|
||||
|
||||
@Counting
|
||||
@Cacheable("something")
|
||||
open suspend fun suspendingCacheableEcho(value: String): String {
|
||||
delay(1)
|
||||
return "$value ${cacheCounter++}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -553,7 +553,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
return (superclass != null && compiledByAjc(superclass));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+15
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -79,8 +79,8 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition getTargetBeanDefinition(ConfigurableBeanFactory beanFactory,
|
||||
@Nullable String targetBeanName) {
|
||||
private BeanDefinition getTargetBeanDefinition(
|
||||
ConfigurableBeanFactory beanFactory, @Nullable String targetBeanName) {
|
||||
|
||||
if (targetBeanName != null && beanFactory.containsBean(targetBeanName)) {
|
||||
return beanFactory.getMergedBeanDefinition(targetBeanName);
|
||||
@@ -123,40 +123,32 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode,
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
|
||||
|
||||
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(
|
||||
beanDefinition);
|
||||
processedBeanDefinition
|
||||
.setTargetType(this.targetBeanDefinition.getResolvableType());
|
||||
processedBeanDefinition.getPropertyValues()
|
||||
.removePropertyValue("targetBeanName");
|
||||
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(beanDefinition);
|
||||
processedBeanDefinition.setTargetType(this.targetBeanDefinition.getResolvableType());
|
||||
processedBeanDefinition.getPropertyValues().removePropertyValue("targetBeanName");
|
||||
return super.generateSetBeanDefinitionPropertiesCode(generationContext,
|
||||
beanRegistrationCode, processedBeanDefinition, attributeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
|
||||
public CodeBlock generateInstanceSupplierCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
|
||||
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods()
|
||||
.add("getScopedProxyInstance", method -> {
|
||||
method.addJavadoc(
|
||||
"Create the scoped proxy bean instance for '$L'.",
|
||||
method.addJavadoc("Create the scoped proxy bean instance for '$L'.",
|
||||
this.registeredBean.getBeanName());
|
||||
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
|
||||
method.returns(ScopedProxyFactoryBean.class);
|
||||
method.addParameter(RegisteredBean.class,
|
||||
REGISTERED_BEAN_PARAMETER_NAME);
|
||||
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
|
||||
method.addStatement("$T factory = new $T()",
|
||||
ScopedProxyFactoryBean.class,
|
||||
ScopedProxyFactoryBean.class);
|
||||
method.addStatement("factory.setTargetBeanName($S)",
|
||||
this.targetBeanName);
|
||||
method.addStatement(
|
||||
"factory.setBeanFactory($L.getBeanFactory())",
|
||||
ScopedProxyFactoryBean.class, ScopedProxyFactoryBean.class);
|
||||
method.addStatement("factory.setTargetBeanName($S)", this.targetBeanName);
|
||||
method.addStatement("factory.setBeanFactory($L.getBeanFactory())",
|
||||
REGISTERED_BEAN_PARAMETER_NAME);
|
||||
method.addStatement("return factory");
|
||||
});
|
||||
|
||||
@@ -349,9 +349,10 @@ public abstract class AopUtils {
|
||||
|
||||
// Use reflection to invoke the method.
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method) ?
|
||||
KotlinDelegate.invokeSuspendingFunction(method, target, args) : method.invoke(target, args));
|
||||
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
ReflectionUtils.makeAccessible(originalMethod);
|
||||
return (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(originalMethod) ?
|
||||
KotlinDelegate.invokeSuspendingFunction(originalMethod, target, args) : originalMethod.invoke(target, args));
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
// Invoked method threw a checked exception.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -34,19 +34,48 @@ class AopUtilsKotlinTests {
|
||||
@Test
|
||||
fun `Invoking suspending function should return Mono`() {
|
||||
val value = "foo"
|
||||
val method = ReflectionUtils.findMethod(AopUtilsKotlinTests::class.java, "suspendingFunction",
|
||||
String::class.java, Continuation::class.java)!!
|
||||
val method = ReflectionUtils.findMethod(WithoutInterface::class.java, "handle",
|
||||
String::class. java, Continuation::class.java)!!
|
||||
val continuation = Continuation<Any>(CoroutineName("test")) { }
|
||||
val result = AopUtils.invokeJoinpointUsingReflection(this, method, arrayOf(value, continuation))
|
||||
val result = AopUtils.invokeJoinpointUsingReflection(WithoutInterface(), method, arrayOf(value, continuation))
|
||||
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
|
||||
assertThat(it.block()).isEqualTo(value)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Invoking suspending function on bridged method should return Mono`() {
|
||||
val value = "foo"
|
||||
val bridgedMethod = ReflectionUtils.findMethod(WithInterface::class.java, "handle", Object::class.java, Continuation::class.java)!!
|
||||
val continuation = Continuation<Any>(CoroutineName("test")) { }
|
||||
val result = AopUtils.invokeJoinpointUsingReflection(WithInterface(), bridgedMethod, arrayOf(value, continuation))
|
||||
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
|
||||
assertThat(it.block()).isEqualTo(value)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
suspend fun suspendingFunction(value: String): String {
|
||||
delay(1)
|
||||
return value
|
||||
}
|
||||
|
||||
class WithoutInterface {
|
||||
suspend fun handle(value: String): String {
|
||||
delay(1)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
interface ProxyInterface<T> {
|
||||
suspend fun handle(value: T): T
|
||||
}
|
||||
|
||||
class WithInterface : ProxyInterface<String> {
|
||||
override suspend fun handle(value: String): String {
|
||||
delay(1)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ dependencies {
|
||||
testImplementation(project(":spring-core-test"))
|
||||
testImplementation(testFixtures(project(":spring-core")))
|
||||
testImplementation("jakarta.annotation:jakarta.annotation-api")
|
||||
testImplementation("javax.inject:javax.inject")
|
||||
}
|
||||
|
||||
+25
-15
@@ -63,6 +63,7 @@ import org.springframework.beans.factory.aot.AutowiredMethodArgumentsResolver;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.aot.CodeWarnings;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
@@ -984,8 +985,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
|
||||
method.addParameter(this.target, INSTANCE_PARAMETER);
|
||||
method.returns(this.target);
|
||||
method.addCode(generateMethodCode(generatedClass.getName(),
|
||||
generationContext.getRuntimeHints()));
|
||||
CodeWarnings codeWarnings = new CodeWarnings();
|
||||
codeWarnings.detectDeprecation(this.target);
|
||||
method.addCode(generateMethodCode(codeWarnings,
|
||||
generatedClass.getName(), generationContext.getRuntimeHints()));
|
||||
codeWarnings.suppress(method);
|
||||
});
|
||||
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
|
||||
|
||||
@@ -994,35 +998,37 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodCode(ClassName targetClassName, RuntimeHints hints) {
|
||||
private CodeBlock generateMethodCode(CodeWarnings codeWarnings,
|
||||
ClassName targetClassName, RuntimeHints hints) {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
for (AutowiredElement autowiredElement : this.autowiredElements) {
|
||||
code.addStatement(generateMethodStatementForElement(
|
||||
targetClassName, autowiredElement, hints));
|
||||
codeWarnings, targetClassName, autowiredElement, hints));
|
||||
}
|
||||
code.addStatement("return $L", INSTANCE_PARAMETER);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodStatementForElement(ClassName targetClassName,
|
||||
AutowiredElement autowiredElement, RuntimeHints hints) {
|
||||
private CodeBlock generateMethodStatementForElement(CodeWarnings codeWarnings,
|
||||
ClassName targetClassName, AutowiredElement autowiredElement, RuntimeHints hints) {
|
||||
|
||||
Member member = autowiredElement.getMember();
|
||||
boolean required = autowiredElement.required;
|
||||
if (member instanceof Field field) {
|
||||
return generateMethodStatementForField(
|
||||
targetClassName, field, required, hints);
|
||||
codeWarnings, targetClassName, field, required, hints);
|
||||
}
|
||||
if (member instanceof Method method) {
|
||||
return generateMethodStatementForMethod(
|
||||
targetClassName, method, required, hints);
|
||||
codeWarnings, targetClassName, method, required, hints);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Unsupported member type " + member.getClass().getName());
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodStatementForField(ClassName targetClassName,
|
||||
Field field, boolean required, RuntimeHints hints) {
|
||||
private CodeBlock generateMethodStatementForField(CodeWarnings codeWarnings,
|
||||
ClassName targetClassName, Field field, boolean required, RuntimeHints hints) {
|
||||
|
||||
hints.reflection().registerField(field);
|
||||
CodeBlock resolver = CodeBlock.of("$T.$L($S)",
|
||||
@@ -1033,18 +1039,22 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
|
||||
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
|
||||
}
|
||||
return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
|
||||
field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
|
||||
else {
|
||||
codeWarnings.detectDeprecation(field);
|
||||
return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
|
||||
field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodStatementForMethod(ClassName targetClassName,
|
||||
Method method, boolean required, RuntimeHints hints) {
|
||||
private CodeBlock generateMethodStatementForMethod(CodeWarnings codeWarnings,
|
||||
ClassName targetClassName, Method method, boolean required, RuntimeHints hints) {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add("$T.$L", AutowiredMethodArgumentsResolver.class,
|
||||
(!required ? "forMethod" : "forRequiredMethod"));
|
||||
code.add("($S", method.getName());
|
||||
if (method.getParameterCount() > 0) {
|
||||
codeWarnings.detectDeprecation(method.getParameterTypes());
|
||||
code.add(", $L", generateParameterTypesCode(method.getParameterTypes()));
|
||||
}
|
||||
code.add(")");
|
||||
@@ -1054,6 +1064,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
|
||||
}
|
||||
else {
|
||||
codeWarnings.detectDeprecation(method);
|
||||
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
|
||||
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(this.target,
|
||||
method).generateCode(method.getParameterTypes());
|
||||
@@ -1098,7 +1109,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -24,16 +24,24 @@ import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} for Jakarta annotations.
|
||||
* {@link RuntimeHintsRegistrar} for Jakarta annotations and their pre-Jakarta equivalents.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
Stream.of("jakarta.inject.Inject", "jakarta.inject.Provider", "jakarta.inject.Qualifier").forEach(typeName ->
|
||||
hints.reflection().registerType(TypeReference.of(typeName)));
|
||||
// javax.inject.Provider is omitted from the list, since we do not currently load
|
||||
// it via reflection.
|
||||
Stream.of(
|
||||
"jakarta.inject.Inject",
|
||||
"jakarta.inject.Provider",
|
||||
"jakarta.inject.Qualifier",
|
||||
"javax.inject.Inject",
|
||||
"javax.inject.Qualifier"
|
||||
).forEach(typeName -> hints.reflection().registerType(TypeReference.of(typeName)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-8
@@ -47,11 +47,13 @@ import org.springframework.util.ObjectUtils;
|
||||
* against {@link Qualifier qualifier annotations} on the field or parameter to be autowired.
|
||||
* Also supports suggested expression values through a {@link Value value} annotation.
|
||||
*
|
||||
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available.
|
||||
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as its
|
||||
* pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @see AutowireCandidateQualifier
|
||||
* @see Qualifier
|
||||
@@ -65,9 +67,10 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
|
||||
|
||||
/**
|
||||
* Create a new QualifierAnnotationAutowireCandidateResolver
|
||||
* for Spring's standard {@link Qualifier} annotation.
|
||||
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available.
|
||||
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for Spring's
|
||||
* standard {@link Qualifier} annotation.
|
||||
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as
|
||||
* its pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public QualifierAnnotationAutowireCandidateResolver() {
|
||||
@@ -76,14 +79,21 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Qualifier",
|
||||
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// JSR-330 API (as included in Jakarta EE) not available - simply skip.
|
||||
}
|
||||
try {
|
||||
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Qualifier",
|
||||
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// JSR-330 API not available - simply skip.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new QualifierAnnotationAutowireCandidateResolver
|
||||
* for the given qualifier annotation type.
|
||||
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for the given
|
||||
* qualifier annotation type.
|
||||
* @param qualifierType the qualifier annotation to look for
|
||||
*/
|
||||
public QualifierAnnotationAutowireCandidateResolver(Class<? extends Annotation> qualifierType) {
|
||||
@@ -92,8 +102,8 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new QualifierAnnotationAutowireCandidateResolver
|
||||
* for the given qualifier annotation types.
|
||||
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for the given
|
||||
* qualifier annotation types.
|
||||
* @param qualifierTypes the qualifier annotations to look for
|
||||
*/
|
||||
public QualifierAnnotationAutowireCandidateResolver(Set<Class<? extends Annotation>> qualifierTypes) {
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -213,12 +213,13 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
if (!(executable instanceof Method method)) {
|
||||
return beanSupplier.get();
|
||||
}
|
||||
Method priorInvokedFactoryMethod = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
|
||||
try {
|
||||
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(method);
|
||||
return beanSupplier.get();
|
||||
}
|
||||
finally {
|
||||
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(null);
|
||||
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(priorInvokedFactoryMethod);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -58,39 +58,39 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
|
||||
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
|
||||
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
return this.delegate.generateNewBeanDefinitionCode(generationContext,
|
||||
beanType, beanRegistrationCode);
|
||||
return this.delegate.generateNewBeanDefinitionCode(generationContext, beanType, beanRegistrationCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
|
||||
Predicate<String> attributeFilter) {
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
|
||||
|
||||
return this.delegate.generateSetBeanDefinitionPropertiesCode(
|
||||
generationContext, beanRegistrationCode, beanDefinition, attributeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
|
||||
List<MethodReference> postProcessors) {
|
||||
public CodeBlock generateSetBeanInstanceSupplierCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
CodeBlock instanceSupplierCode, List<MethodReference> postProcessors) {
|
||||
|
||||
return this.delegate.generateSetBeanInstanceSupplierCode(generationContext,
|
||||
beanRegistrationCode, instanceSupplierCode, postProcessors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
|
||||
public CodeBlock generateInstanceSupplierCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
|
||||
return this.delegate.generateInstanceSupplierCode(generationContext,
|
||||
beanRegistrationCode, allowDirectSupplierShortcut);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateReturnCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode) {
|
||||
public CodeBlock generateReturnCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
return this.delegate.generateReturnCode(generationContext, beanRegistrationCode);
|
||||
}
|
||||
|
||||
+41
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -21,12 +21,16 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.AnnotationSpec;
|
||||
import org.springframework.javapoet.AnnotationSpec.Builder;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.FieldSpec;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -38,7 +42,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @since 6.1
|
||||
* @see SuppressWarnings
|
||||
*/
|
||||
class CodeWarnings {
|
||||
public class CodeWarnings {
|
||||
|
||||
private final Set<String> warnings = new LinkedHashSet<>();
|
||||
|
||||
@@ -59,7 +63,7 @@ class CodeWarnings {
|
||||
*/
|
||||
public CodeWarnings detectDeprecation(AnnotatedElement... elements) {
|
||||
for (AnnotatedElement element : elements) {
|
||||
register(element.getAnnotation(Deprecated.class));
|
||||
registerDeprecationIfNecessary(element);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -99,10 +103,31 @@ class CodeWarnings {
|
||||
* @param method the method to update
|
||||
*/
|
||||
public void suppress(MethodSpec.Builder method) {
|
||||
if (this.warnings.isEmpty()) {
|
||||
return;
|
||||
suppress(annotationBuilder -> method.addAnnotation(annotationBuilder.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Include {@link SuppressWarnings} on the specified type if necessary.
|
||||
* @param type the type to update
|
||||
*/
|
||||
public void suppress(TypeSpec.Builder type) {
|
||||
suppress(annotationBuilder -> type.addAnnotation(annotationBuilder.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the builder for {@link SuppressWarnings} if necessary. If this
|
||||
* instance has no warnings registered, the consumer is not invoked.
|
||||
* @param annotationSpec a consumer of the {@link AnnotationSpec.Builder}
|
||||
* @see MethodSpec.Builder#addAnnotation(AnnotationSpec)
|
||||
* @see TypeSpec.Builder#addAnnotation(AnnotationSpec)
|
||||
* @see FieldSpec.Builder#addAnnotation(AnnotationSpec)
|
||||
*/
|
||||
protected void suppress(Consumer<AnnotationSpec.Builder> annotationSpec) {
|
||||
if (!this.warnings.isEmpty()) {
|
||||
Builder annotation = AnnotationSpec.builder(SuppressWarnings.class)
|
||||
.addMember("value", generateValueCode());
|
||||
annotationSpec.accept(annotation);
|
||||
}
|
||||
method.addAnnotation(buildAnnotationSpec());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,6 +138,16 @@ class CodeWarnings {
|
||||
return Collections.unmodifiableSet(this.warnings);
|
||||
}
|
||||
|
||||
private void registerDeprecationIfNecessary(@Nullable AnnotatedElement element) {
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
register(element.getAnnotation(Deprecated.class));
|
||||
if (element instanceof Class<?> type) {
|
||||
registerDeprecationIfNecessary(type.getEnclosingClass());
|
||||
}
|
||||
}
|
||||
|
||||
private void register(@Nullable Deprecated annotation) {
|
||||
if (annotation != null) {
|
||||
if (annotation.forRemoval()) {
|
||||
@@ -124,11 +159,6 @@ class CodeWarnings {
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationSpec buildAnnotationSpec() {
|
||||
return AnnotationSpec.builder(SuppressWarnings.class)
|
||||
.addMember("value", generateValueCode()).build();
|
||||
}
|
||||
|
||||
private CodeBlock generateValueCode() {
|
||||
if (this.warnings.size() == 1) {
|
||||
return CodeBlock.of("$S", this.warnings.iterator().next());
|
||||
|
||||
+33
-37
@@ -46,8 +46,7 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* Internal {@link BeanRegistrationCodeFragments} implementation used by
|
||||
* default.
|
||||
* Internal {@link BeanRegistrationCodeFragments} implementation used by default.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
@@ -81,7 +80,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
if (hasInstanceSupplier()) {
|
||||
String resourceDescription = registeredBean.getMergedBeanDefinition().getResourceDescription();
|
||||
throw new IllegalStateException("Error processing bean with name '" + registeredBean.getBeanName() + "'" +
|
||||
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": instance supplier is not supported");
|
||||
(resourceDescription != null ? " defined in " + resourceDescription : "") +
|
||||
": instance supplier is not supported");
|
||||
}
|
||||
Class<?> target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get());
|
||||
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
|
||||
@@ -94,9 +94,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
|
||||
private Class<?> extractDeclaringClass(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
|
||||
Class<?> declaringClass = ClassUtils.getUserClass(instantiationDescriptor.targetClass());
|
||||
if (instantiationDescriptor.executable() instanceof Constructor<?>
|
||||
&& AccessControl.forMember(instantiationDescriptor.executable()).isPublic()
|
||||
&& FactoryBean.class.isAssignableFrom(declaringClass)) {
|
||||
if (instantiationDescriptor.executable() instanceof Constructor<?> ctor &&
|
||||
AccessControl.forMember(ctor).isPublic() && FactoryBean.class.isAssignableFrom(declaringClass)) {
|
||||
return extractTargetClassFromFactoryBean(declaringClass, registeredBean.getBeanType());
|
||||
}
|
||||
return declaringClass;
|
||||
@@ -105,8 +104,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
/**
|
||||
* Extract the target class of a public {@link FactoryBean} based on its
|
||||
* constructor. If the implementation does not resolve the target class
|
||||
* because it itself uses a generic, attempt to extract it from the
|
||||
* bean type.
|
||||
* because it itself uses a generic, attempt to extract it from the bean type.
|
||||
* @param factoryBeanType the factory bean type
|
||||
* @param beanType the bean type
|
||||
* @return the target class to use
|
||||
@@ -127,17 +125,15 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
RootBeanDefinition mergedBeanDefinition = this.registeredBean.getMergedBeanDefinition();
|
||||
Class<?> beanClass = (mergedBeanDefinition.hasBeanClass()
|
||||
? ClassUtils.getUserClass(mergedBeanDefinition.getBeanClass()) : null);
|
||||
RootBeanDefinition mbd = this.registeredBean.getMergedBeanDefinition();
|
||||
Class<?> beanClass = (mbd.hasBeanClass() ? ClassUtils.getUserClass(mbd.getBeanClass()) : null);
|
||||
CodeBlock beanClassCode = generateBeanClassCode(
|
||||
beanRegistrationCode.getClassName().packageName(),
|
||||
(beanClass != null ? beanClass : beanType.toClass()));
|
||||
code.addStatement("$T $L = new $T($L)", RootBeanDefinition.class,
|
||||
BEAN_DEFINITION_VARIABLE, RootBeanDefinition.class, beanClassCode);
|
||||
if (targetTypeNecessary(beanType, beanClass)) {
|
||||
code.addStatement("$L.setTargetType($L)", BEAN_DEFINITION_VARIABLE,
|
||||
generateBeanTypeCode(beanType));
|
||||
code.addStatement("$L.setTargetType($L)", BEAN_DEFINITION_VARIABLE, generateBeanTypeCode(beanType));
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
@@ -162,8 +158,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
if (beanType.hasGenerics()) {
|
||||
return true;
|
||||
}
|
||||
if (beanClass != null
|
||||
&& this.registeredBean.getMergedBeanDefinition().getFactoryMethodName() != null) {
|
||||
if (beanClass != null && this.registeredBean.getMergedBeanDefinition().getFactoryMethodName() != null) {
|
||||
return true;
|
||||
}
|
||||
return (beanClass != null && !beanType.toClass().equals(beanClass));
|
||||
@@ -171,21 +166,21 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
|
||||
Predicate<String> attributeFilter) {
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
|
||||
|
||||
Loader loader = AotServices.factories(this.registeredBean.getBeanFactory().getBeanClassLoader());
|
||||
List<Delegate> additionalDelegates = loader.load(Delegate.class).asList();
|
||||
return new BeanDefinitionPropertiesCodeGenerator(generationContext.getRuntimeHints(),
|
||||
attributeFilter, beanRegistrationCode.getMethods(),
|
||||
additionalDelegates, (name, value) -> generateValueCode(generationContext, name, value)
|
||||
).generateCode(beanDefinition);
|
||||
|
||||
return new BeanDefinitionPropertiesCodeGenerator(
|
||||
generationContext.getRuntimeHints(), attributeFilter,
|
||||
beanRegistrationCode.getMethods(), additionalDelegates,
|
||||
(name, value) -> generateValueCode(generationContext, name, value))
|
||||
.generateCode(beanDefinition);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected CodeBlock generateValueCode(GenerationContext generationContext,
|
||||
String name, Object value) {
|
||||
|
||||
protected CodeBlock generateValueCode(GenerationContext generationContext, String name, Object value) {
|
||||
RegisteredBean innerRegisteredBean = getInnerRegisteredBean(value);
|
||||
if (innerRegisteredBean != null) {
|
||||
BeanDefinitionMethodGenerator methodGenerator = this.beanDefinitionMethodGeneratorFactory
|
||||
@@ -211,9 +206,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanInstanceSupplierCode(
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
|
||||
List<MethodReference> postProcessors) {
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
CodeBlock instanceSupplierCode, List<MethodReference> postProcessors) {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
if (postProcessors.isEmpty()) {
|
||||
@@ -233,20 +227,22 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
|
||||
public CodeBlock generateInstanceSupplierCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
|
||||
if (hasInstanceSupplier()) {
|
||||
throw new IllegalStateException("Default code generation is not supported for bean definitions declaring "
|
||||
+ "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
|
||||
throw new IllegalStateException("Default code generation is not supported for bean definitions " +
|
||||
"declaring an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
|
||||
}
|
||||
return new InstanceSupplierCodeGenerator(generationContext, beanRegistrationCode.getClassName(),
|
||||
beanRegistrationCode.getMethods(), allowDirectSupplierShortcut).generateCode(
|
||||
this.registeredBean, this.instantiationDescriptor.get());
|
||||
return new InstanceSupplierCodeGenerator(generationContext,
|
||||
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
|
||||
.generateCode(this.registeredBean, this.instantiationDescriptor.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateReturnCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode) {
|
||||
public CodeBlock generateReturnCode(
|
||||
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.addStatement("return $L", BEAN_DEFINITION_VARIABLE);
|
||||
|
||||
+25
-7
@@ -813,10 +813,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
|
||||
// Common return type found: all factory methods return same type. For a non-parameterized
|
||||
// unique candidate, cache the full type declaration context of the target factory method.
|
||||
cachedReturnType = (uniqueCandidate != null ?
|
||||
ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
|
||||
mbd.factoryMethodReturnType = cachedReturnType;
|
||||
return cachedReturnType.resolve();
|
||||
try {
|
||||
cachedReturnType = (uniqueCandidate != null ?
|
||||
ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
|
||||
mbd.factoryMethodReturnType = cachedReturnType;
|
||||
return cachedReturnType.resolve();
|
||||
}
|
||||
catch (LinkageError err) {
|
||||
// E.g. a NoClassDefFoundError for a generic method return type
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to resolve type for factory method of bean '" + beanName + "': " +
|
||||
(uniqueCandidate != null ? uniqueCandidate : commonType), err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -833,10 +843,18 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
*/
|
||||
@Override
|
||||
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {
|
||||
ResolvableType result;
|
||||
|
||||
// Check if the bean definition itself has defined the type with an attribute
|
||||
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
|
||||
if (result != ResolvableType.NONE) {
|
||||
return result;
|
||||
try {
|
||||
result = getTypeForFactoryBeanFromAttributes(mbd);
|
||||
if (result != ResolvableType.NONE) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
|
||||
String.valueOf(ex.getMessage()));
|
||||
}
|
||||
|
||||
// For instance supplied beans, try the target type and bean class immediately
|
||||
|
||||
+9
-3
@@ -1716,9 +1716,15 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* @see #getBean(String)
|
||||
*/
|
||||
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {
|
||||
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
|
||||
if (result != ResolvableType.NONE) {
|
||||
return result;
|
||||
try {
|
||||
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
|
||||
if (result != ResolvableType.NONE) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
|
||||
String.valueOf(ex.getMessage()));
|
||||
}
|
||||
|
||||
if (allowInit && mbd.isSingleton()) {
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -123,16 +123,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
|
||||
|
||||
@Nullable
|
||||
private static Class<?> javaxInjectProviderClass;
|
||||
private static Class<?> jakartaInjectProviderClass;
|
||||
|
||||
static {
|
||||
try {
|
||||
javaxInjectProviderClass =
|
||||
jakartaInjectProviderClass =
|
||||
ClassUtils.forName("jakarta.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// JSR-330 API not available - Provider interface simply not supported then.
|
||||
javaxInjectProviderClass = null;
|
||||
jakartaInjectProviderClass = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,7 +1340,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
ObjectProvider.class == descriptor.getDependencyType()) {
|
||||
return new DependencyObjectProvider(descriptor, requestingBeanName);
|
||||
}
|
||||
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
|
||||
else if (jakartaInjectProviderClass == descriptor.getDependencyType()) {
|
||||
return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
|
||||
}
|
||||
else if (descriptor.supportsLazyResolution()) {
|
||||
|
||||
+12
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -55,12 +55,18 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the factory method currently being invoked or {@code null} to reset.
|
||||
* Set the factory method currently being invoked or {@code null} to remove
|
||||
* the current value, if any.
|
||||
* @param method the factory method currently being invoked or {@code null}
|
||||
* @since 6.0
|
||||
*/
|
||||
public static void setCurrentlyInvokedFactoryMethod(@Nullable Method method) {
|
||||
currentlyInvokedFactoryMethod.set(method);
|
||||
if (method != null) {
|
||||
currentlyInvokedFactoryMethod.set(method);
|
||||
}
|
||||
else {
|
||||
currentlyInvokedFactoryMethod.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,9 +140,9 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(factoryMethod);
|
||||
|
||||
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
|
||||
Method priorInvokedFactoryMethod = getCurrentlyInvokedFactoryMethod();
|
||||
try {
|
||||
currentlyInvokedFactoryMethod.set(factoryMethod);
|
||||
setCurrentlyInvokedFactoryMethod(factoryMethod);
|
||||
Object result = factoryMethod.invoke(factoryBean, args);
|
||||
if (result == null) {
|
||||
result = new NullBean();
|
||||
@@ -144,12 +150,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
return result;
|
||||
}
|
||||
finally {
|
||||
if (priorInvokedFactoryMethod != null) {
|
||||
currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
|
||||
}
|
||||
else {
|
||||
currentlyInvokedFactoryMethod.remove();
|
||||
}
|
||||
setCurrentlyInvokedFactoryMethod(priorInvokedFactoryMethod);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -30,13 +30,13 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @since 26.05.2003
|
||||
* @see java.util.Locale
|
||||
* @see org.springframework.util.StringUtils#parseLocaleString
|
||||
* @see org.springframework.util.StringUtils#parseLocale
|
||||
*/
|
||||
public class LocaleEditor extends PropertyEditorSupport {
|
||||
|
||||
@Override
|
||||
public void setAsText(String text) {
|
||||
setValue(StringUtils.parseLocaleString(text));
|
||||
setValue(StringUtils.parseLocale(text));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+11
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -92,9 +92,9 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
// a file prefix (let's try as Spring resource location)
|
||||
nioPathCandidate = !text.startsWith(ResourceUtils.FILE_URL_PREFIX);
|
||||
}
|
||||
catch (FileSystemNotFoundException ex) {
|
||||
// URI scheme not registered for NIO (let's try URL
|
||||
// protocol handlers via Spring's resource mechanism).
|
||||
catch (FileSystemNotFoundException | IllegalArgumentException ex) {
|
||||
// URI scheme not registered for NIO or not meeting Paths requirements:
|
||||
// let's try URL protocol handlers via Spring's resource mechanism.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +111,13 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
setValue(resource.getFile().toPath());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not retrieve file for " + resource + ": " + ex.getMessage());
|
||||
String msg = "Could not resolve \"" + text + "\" to 'java.nio.file.Path' for " + resource + ": " +
|
||||
ex.getMessage();
|
||||
if (nioPathCandidate) {
|
||||
msg += " - In case of ambiguity, consider adding the 'file:' prefix for an explicit reference " +
|
||||
"to a file system resource of the same name: \"file:" + text + "\"";
|
||||
}
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -21,6 +21,7 @@ import java.util.function.BiFunction;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
@@ -28,9 +29,17 @@ import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.CodeWarnings;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedFieldInjectionPointSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedFieldInjectionTypeSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedMethodInjectionPointSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedMethodInjectionTypeSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedPrivateFieldInjectionTypeSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedPrivateMethodInjectionTypeSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateFieldInjectionSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateMethodInjectionSample;
|
||||
import org.springframework.beans.testfixture.beans.factory.annotation.PrivateFieldInjectionSample;
|
||||
@@ -49,6 +58,7 @@ import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.ParameterizedTypeName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutowiredAnnotationBeanPostProcessor} for AOT contributions.
|
||||
@@ -199,6 +209,69 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
|
||||
assertThat(contribution).isNull();
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SuppressWarnings("deprecation")
|
||||
class DeprecationTests {
|
||||
|
||||
private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem()
|
||||
.withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror");
|
||||
|
||||
@Test
|
||||
void contributeWhenTargetClassIsDeprecated() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(DeprecatedSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenFieldInjectionsUsesADeprecatedType() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
DeprecatedFieldInjectionTypeSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenFieldInjectionsUsesADeprecatedTypeWithReflection() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
DeprecatedPrivateFieldInjectionTypeSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenFieldInjectionsIsDeprecated() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
DeprecatedFieldInjectionPointSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenMethodInjectionsUsesADeprecatedType() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
DeprecatedMethodInjectionTypeSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenMethodInjectionsUsesADeprecatedTypeWithReflection() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
DeprecatedPrivateMethodInjectionTypeSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenMethodInjectionsIsDeprecated() {
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
DeprecatedMethodInjectionPointSample.class);
|
||||
compileAndCheckWarnings(registeredBean);
|
||||
}
|
||||
|
||||
|
||||
private void compileAndCheckWarnings(RegisteredBean registeredBean) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, registeredBean,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private RegisteredBean getAndApplyContribution(Class<?> beanClass) {
|
||||
RegisteredBean registeredBean = registerBean(beanClass);
|
||||
BeanRegistrationAotContribution contribution = this.beanPostProcessor.processAheadOfTime(registeredBean);
|
||||
@@ -218,11 +291,17 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
|
||||
return compiled.getSourceFileFromPackage(sample.getPackageName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(RegisteredBean registeredBean,
|
||||
BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) {
|
||||
compile(TestCompiler.forSystem(), registeredBean, result);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(TestCompiler testCompiler, RegisteredBean registeredBean,
|
||||
BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) {
|
||||
Class<?> target = registeredBean.getBeanClass();
|
||||
MethodReference methodReference = this.beanRegistrationCode.getInstancePostProcessors().get(0);
|
||||
CodeWarnings codeWarnings = new CodeWarnings();
|
||||
this.beanRegistrationCode.getTypeBuilder().set(type -> {
|
||||
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(
|
||||
ArgumentCodeGenerator.of(RegisteredBean.class, "registeredBean").and(target, "instance"),
|
||||
@@ -235,10 +314,11 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
|
||||
.addParameter(target, "instance").returns(target)
|
||||
.addStatement("return $L", methodInvocation)
|
||||
.build());
|
||||
|
||||
codeWarnings.detectDeprecation(target);
|
||||
codeWarnings.suppress(type);
|
||||
});
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(this.generationContext).compile(compiled ->
|
||||
testCompiler.with(this.generationContext).printFiles(System.out).compile(compiled ->
|
||||
result.accept(compiled.getInstance(BiFunction.class), compiled));
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* Tests for {@link JakartaAnnotationsRuntimeHints}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class JakartaAnnotationsRuntimeHintsTests {
|
||||
|
||||
@@ -62,4 +63,14 @@ class JakartaAnnotationsRuntimeHintsTests {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)).accepts(this.hints);
|
||||
}
|
||||
|
||||
@Test // gh-33345
|
||||
void javaxInjectAnnotationHasHints() {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Inject.class)).accepts(this.hints);
|
||||
}
|
||||
|
||||
@Test // gh-33345
|
||||
void javaxQualifierAnnotationHasHints() {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Qualifier.class)).accepts(this.hints);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -25,6 +25,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -51,6 +52,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@@ -292,6 +294,33 @@ class BeanInstanceSupplierTests {
|
||||
assertThat(instance).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test // gh-33180
|
||||
void getWithNestedInvocationRetainsFactoryMethod() throws Exception {
|
||||
AtomicReference<Method> testMethodReference = new AtomicReference<>();
|
||||
AtomicReference<Method> anotherMethodReference = new AtomicReference<>();
|
||||
|
||||
BeanInstanceSupplier<Object> nestedInstanceSupplier = BeanInstanceSupplier
|
||||
.forFactoryMethod(AnotherTestStringFactory.class, "another")
|
||||
.withGenerator(registeredBean -> {
|
||||
anotherMethodReference.set(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
|
||||
return "Another";
|
||||
});
|
||||
RegisteredBean nestedRegisteredBean = new Source(String.class, nestedInstanceSupplier).registerBean(this.beanFactory);
|
||||
BeanInstanceSupplier<Object> instanceSupplier = BeanInstanceSupplier
|
||||
.forFactoryMethod(TestStringFactory.class, "test")
|
||||
.withGenerator(registeredBean -> {
|
||||
Object nested = nestedInstanceSupplier.get(nestedRegisteredBean);
|
||||
testMethodReference.set(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
|
||||
return "custom" + nested;
|
||||
});
|
||||
RegisteredBean registeredBean = new Source(String.class, instanceSupplier).registerBean(this.beanFactory);
|
||||
Object value = instanceSupplier.get(registeredBean);
|
||||
|
||||
assertThat(value).isEqualTo("customAnother");
|
||||
assertThat(testMethodReference.get()).isEqualTo(instanceSupplier.getFactoryMethod());
|
||||
assertThat(anotherMethodReference.get()).isEqualTo(nestedInstanceSupplier.getFactoryMethod());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveArgumentsWithNoArgConstructor() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
@@ -1030,4 +1059,18 @@ class BeanInstanceSupplierTests {
|
||||
|
||||
}
|
||||
|
||||
static class TestStringFactory {
|
||||
|
||||
String test() {
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
|
||||
static class AnotherTestStringFactory {
|
||||
|
||||
String another() {
|
||||
return "another";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+82
-18
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -34,8 +34,10 @@ import org.springframework.beans.testfixture.beans.factory.generator.deprecation
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.FieldSpec;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.MethodSpec.Builder;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -59,30 +61,49 @@ class CodeWarningsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerNoWarningDoesNotIncludeAnnotation() {
|
||||
compile(method -> {
|
||||
void registerNoWarningDoesNotIncludeAnnotationOnMethod() {
|
||||
compileWithMethod(method -> {
|
||||
this.codeWarnings.suppress(method);
|
||||
method.addStatement("$T bean = $S", String.class, "Hello");
|
||||
}, compiled -> assertThat(compiled.getSourceFile()).doesNotContain("@SuppressWarnings"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerNoWarningDoesNotIncludeAnnotationOnType() {
|
||||
compile(type -> {
|
||||
this.codeWarnings.suppress(type);
|
||||
type.addField(FieldSpec.builder(String.class, "type").build());
|
||||
}, compiled -> assertThat(compiled.getSourceFile()).doesNotContain("@SuppressWarnings"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void registerWarningSuppressesIt() {
|
||||
void registerWarningSuppressesItOnMethod() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
compile(method -> {
|
||||
compileWithMethod(method -> {
|
||||
this.codeWarnings.suppress(method);
|
||||
method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class);
|
||||
}, compiled -> assertThat(compiled.getSourceFile())
|
||||
.contains("@SuppressWarnings(\"deprecation\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void registerWarningSuppressesItOnType() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
compile(type -> {
|
||||
this.codeWarnings.suppress(type);
|
||||
type.addField(FieldSpec.builder(DeprecatedBean.class, "bean").build());
|
||||
}, compiled -> assertThat(compiled.getSourceFile())
|
||||
.contains("@SuppressWarnings(\"deprecation\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
void registerSeveralWarningsSuppressesThem() {
|
||||
void registerSeveralWarningsSuppressesThemOnMethod() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
this.codeWarnings.register("removal");
|
||||
compile(method -> {
|
||||
compileWithMethod(method -> {
|
||||
this.codeWarnings.suppress(method);
|
||||
method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class);
|
||||
method.addStatement("$T another = new $T()", DeprecatedForRemovalBean.class, DeprecatedForRemovalBean.class);
|
||||
@@ -90,6 +111,19 @@ class CodeWarningsTests {
|
||||
.contains("@SuppressWarnings({ \"deprecation\", \"removal\" })"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
void registerSeveralWarningsSuppressesThemOnType() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
this.codeWarnings.register("removal");
|
||||
compile(type -> {
|
||||
this.codeWarnings.suppress(type);
|
||||
type.addField(FieldSpec.builder(DeprecatedBean.class, "bean").build());
|
||||
type.addField(FieldSpec.builder(DeprecatedForRemovalBean.class, "another").build());
|
||||
}, compiled -> assertThat(compiled.getSourceFile())
|
||||
.contains("@SuppressWarnings({ \"deprecation\", \"removal\" })"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void detectDeprecationOnAnnotatedElementWithDeprecated() {
|
||||
@@ -97,6 +131,13 @@ class CodeWarningsTests {
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void detectDeprecationOnAnnotatedElementWhoseEnclosingElementIsDeprecated() {
|
||||
this.codeWarnings.detectDeprecation(DeprecatedBean.Nested.class);
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void detectDeprecationOnAnnotatedElementWithDeprecatedForRemoval() {
|
||||
@@ -104,6 +145,13 @@ class CodeWarningsTests {
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void detectDeprecationOnAnnotatedElementWhoseEnclosingElementIsDeprecatedForRemoval() {
|
||||
this.codeWarnings.detectDeprecation(DeprecatedForRemovalBean.Nested.class);
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("resolvableTypesWithDeprecated")
|
||||
void detectDeprecationOnResolvableTypeWithDeprecated(ResolvableType resolvableType) {
|
||||
@@ -113,11 +161,17 @@ class CodeWarningsTests {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static Stream<Arguments> resolvableTypesWithDeprecated() {
|
||||
Class<?> deprecatedBean = DeprecatedBean.class;
|
||||
Class<?> nested = DeprecatedBean.Nested.class;
|
||||
return Stream.of(
|
||||
Arguments.of(ResolvableType.forClass(DeprecatedBean.class)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class)),
|
||||
Arguments.of(ResolvableType.forClass(deprecatedBean)),
|
||||
Arguments.of(ResolvableType.forClass(nested)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, nested)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class)))
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean))),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, nested)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,11 +184,17 @@ class CodeWarningsTests {
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
static Stream<Arguments> resolvableTypesWithDeprecatedForRemoval() {
|
||||
Class<?> deprecatedBean = DeprecatedForRemovalBean.class;
|
||||
Class<?> nested = DeprecatedForRemovalBean.Nested.class;
|
||||
return Stream.of(
|
||||
Arguments.of(ResolvableType.forClass(DeprecatedForRemovalBean.class)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedForRemovalBean.class)),
|
||||
Arguments.of(ResolvableType.forClass(deprecatedBean)),
|
||||
Arguments.of(ResolvableType.forClass(nested)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, nested)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedForRemovalBean.class)))
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean))),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, nested)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,16 +205,20 @@ class CodeWarningsTests {
|
||||
assertThat(this.codeWarnings).hasToString("CodeWarnings[deprecation, rawtypes]");
|
||||
}
|
||||
|
||||
private void compile(Consumer<Builder> method, Consumer<Compiled> result) {
|
||||
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
|
||||
this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
|
||||
typeBuilder.set(type -> {
|
||||
private void compileWithMethod(Consumer<Builder> method, Consumer<Compiled> result) {
|
||||
compile(type -> {
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
Builder methodBuilder = MethodSpec.methodBuilder("apply")
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
method.accept(methodBuilder);
|
||||
type.addMethod(methodBuilder.build());
|
||||
});
|
||||
}, result);
|
||||
}
|
||||
|
||||
private void compile(Consumer<TypeSpec.Builder> type, Consumer<Compiled> result) {
|
||||
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
|
||||
this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
|
||||
typeBuilder.set(type);
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TEST_COMPILER.with(this.generationContext).compile(result);
|
||||
}
|
||||
|
||||
+4
@@ -648,6 +648,10 @@ class CustomEditorTests {
|
||||
assertThat(localeEditor.getValue()).isEqualTo(Locale.CANADA);
|
||||
assertThat(localeEditor.getAsText()).isEqualTo("en_CA");
|
||||
|
||||
localeEditor = new LocaleEditor();
|
||||
localeEditor.setAsText("zh-Hans");
|
||||
assertThat(localeEditor.getValue()).isEqualTo(Locale.forLanguageTag("zh-Hans"));
|
||||
|
||||
localeEditor = new LocaleEditor();
|
||||
assertThat(localeEditor.getAsText()).isEmpty();
|
||||
}
|
||||
|
||||
+12
-2
@@ -46,9 +46,9 @@ class FileEditorTests {
|
||||
|
||||
@Test
|
||||
void testWithNonExistentResource() {
|
||||
PropertyEditor propertyEditor = new FileEditor();
|
||||
PropertyEditor fileEditor = new FileEditor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
propertyEditor.setAsText("classpath:no_way_this_file_is_found.doc"));
|
||||
fileEditor.setAsText("classpath:no_way_this_file_is_found.doc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,6 +71,16 @@ class FileEditorTests {
|
||||
assertThat(file).doesNotExist();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCurrentDirectory() {
|
||||
PropertyEditor fileEditor = new FileEditor();
|
||||
fileEditor.setAsText("file:.");
|
||||
Object value = fileEditor.getValue();
|
||||
assertThat(value).isInstanceOf(File.class);
|
||||
File file = (File) value;
|
||||
assertThat(file).isEqualTo(new File("."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnqualifiedFileNameFound() {
|
||||
PropertyEditor fileEditor = new FileEditor();
|
||||
|
||||
+13
-2
@@ -19,6 +19,7 @@ package org.springframework.beans.propertyeditors;
|
||||
import java.beans.PropertyEditor;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -46,9 +47,9 @@ class PathEditorTests {
|
||||
|
||||
@Test
|
||||
void testWithNonExistentResource() {
|
||||
PropertyEditor propertyEditor = new PathEditor();
|
||||
PropertyEditor pathEditor = new PathEditor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
propertyEditor.setAsText("classpath:/no_way_this_file_is_found.doc"));
|
||||
pathEditor.setAsText("classpath:/no_way_this_file_is_found.doc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,6 +99,16 @@ class PathEditorTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCurrentDirectory() {
|
||||
PropertyEditor pathEditor = new PathEditor();
|
||||
pathEditor.setAsText("file:.");
|
||||
Object value = pathEditor.getValue();
|
||||
assertThat(value).isInstanceOf(Path.class);
|
||||
Path path = (Path) value;
|
||||
assertThat(path).isEqualTo(Paths.get("."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnqualifiedPathNameFound() {
|
||||
PropertyEditor pathEditor = new PathEditor();
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.beans.testfixture.beans.factory.annotation;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
public abstract class DeprecatedInjectionSamples {
|
||||
|
||||
@Deprecated
|
||||
public static class DeprecatedEnvironment {}
|
||||
|
||||
@Deprecated
|
||||
public static class DeprecatedSample {
|
||||
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
}
|
||||
|
||||
public static class DeprecatedFieldInjectionPointSample {
|
||||
|
||||
@Autowired
|
||||
@Deprecated
|
||||
Environment environment;
|
||||
|
||||
}
|
||||
|
||||
public static class DeprecatedFieldInjectionTypeSample {
|
||||
|
||||
@Autowired
|
||||
DeprecatedEnvironment environment;
|
||||
}
|
||||
|
||||
public static class DeprecatedPrivateFieldInjectionTypeSample {
|
||||
|
||||
@Autowired
|
||||
private DeprecatedEnvironment environment;
|
||||
}
|
||||
|
||||
public static class DeprecatedMethodInjectionPointSample {
|
||||
|
||||
@Autowired
|
||||
@Deprecated
|
||||
void setEnvironment(Environment environment) {}
|
||||
}
|
||||
|
||||
public static class DeprecatedMethodInjectionTypeSample {
|
||||
|
||||
@Autowired
|
||||
void setEnvironment(DeprecatedEnvironment environment) {}
|
||||
}
|
||||
|
||||
public static class DeprecatedPrivateMethodInjectionTypeSample {
|
||||
|
||||
@Autowired
|
||||
private void setEnvironment(DeprecatedEnvironment environment) {}
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -23,4 +23,8 @@ package org.springframework.beans.testfixture.beans.factory.generator.deprecatio
|
||||
*/
|
||||
@Deprecated
|
||||
public class DeprecatedBean {
|
||||
|
||||
// This isn't flag deprecated on purpose
|
||||
public static class Nested {}
|
||||
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -23,4 +23,7 @@ package org.springframework.beans.testfixture.beans.factory.generator.deprecatio
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public class DeprecatedForRemovalBean {
|
||||
|
||||
// This isn't flag deprecated on purpose
|
||||
public static class Nested {}
|
||||
}
|
||||
|
||||
+73
-59
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -41,9 +41,11 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Factory that configures a FreeMarker Configuration. Can be used standalone, but
|
||||
* typically you will either use FreeMarkerConfigurationFactoryBean for preparing a
|
||||
* Configuration as bean reference, or FreeMarkerConfigurer for web views.
|
||||
* Factory that configures a FreeMarker {@link Configuration}.
|
||||
*
|
||||
* <p>Can be used standalone, but typically you will either use
|
||||
* {@link FreeMarkerConfigurationFactoryBean} for preparing a {@code Configuration}
|
||||
* as a bean reference, or {@code FreeMarkerConfigurer} for web views.
|
||||
*
|
||||
* <p>The optional "configLocation" property sets the location of a FreeMarker
|
||||
* properties file, within the current application. FreeMarker properties can be
|
||||
@@ -52,17 +54,18 @@ import org.springframework.util.CollectionUtils;
|
||||
* subject to constraints set by FreeMarker.
|
||||
*
|
||||
* <p>The "freemarkerVariables" property can be used to specify a Map of
|
||||
* shared variables that will be applied to the Configuration via the
|
||||
* shared variables that will be applied to the {@code Configuration} via the
|
||||
* {@code setAllSharedVariables()} method. Like {@code setSettings()},
|
||||
* these entries are subject to FreeMarker constraints.
|
||||
*
|
||||
* <p>The simplest way to use this class is to specify a "templateLoaderPath";
|
||||
* FreeMarker does not need any further configuration then.
|
||||
*
|
||||
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
|
||||
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3.21 or higher.
|
||||
*
|
||||
* @author Darren Davison
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 03.03.2004
|
||||
* @see #setConfigLocation
|
||||
* @see #setFreemarkerSettings
|
||||
@@ -107,7 +110,7 @@ public class FreeMarkerConfigurationFactory {
|
||||
|
||||
/**
|
||||
* Set the location of the FreeMarker config file.
|
||||
* Alternatively, you can specify all setting locally.
|
||||
* <p>Alternatively, you can specify all settings locally.
|
||||
* @see #setFreemarkerSettings
|
||||
* @see #setTemplateLoaderPath
|
||||
*/
|
||||
@@ -134,25 +137,33 @@ public class FreeMarkerConfigurationFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default encoding for the FreeMarker configuration.
|
||||
* If not specified, FreeMarker will use the platform file encoding.
|
||||
* <p>Used for template rendering unless there is an explicit encoding specified
|
||||
* for the rendering process (for example, on Spring's FreeMarkerView).
|
||||
* Set the default encoding for the FreeMarker {@link Configuration}, which
|
||||
* is used to decode byte sequences to character sequences when reading template
|
||||
* files.
|
||||
* <p>If not specified, FreeMarker will read template files using the platform
|
||||
* file encoding (defined by the JVM system property {@code file.encoding})
|
||||
* or {@code "utf-8"} if the platform file encoding is undefined.
|
||||
* <p>Note that the encoding is not used for template rendering. Instead, an
|
||||
* explicit encoding must be specified for the rendering process — for
|
||||
* example, via Spring's {@code FreeMarkerView} or {@code FreeMarkerViewResolver}.
|
||||
* @see freemarker.template.Configuration#setDefaultEncoding
|
||||
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerView#setEncoding
|
||||
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerView#setContentType
|
||||
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver#setContentType
|
||||
*/
|
||||
public void setDefaultEncoding(String defaultEncoding) {
|
||||
this.defaultEncoding = defaultEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a List of {@code TemplateLoader}s that will be used to search
|
||||
* for templates. For example, one or more custom loaders such as database
|
||||
* loaders could be configured and injected here.
|
||||
* <p>The {@link TemplateLoader TemplateLoaders} specified here will be
|
||||
* registered <i>before</i> the default template loaders that this factory
|
||||
* registers (such as loaders for specified "templateLoaderPaths" or any
|
||||
* loaders registered in {@link #postProcessTemplateLoaders}).
|
||||
* Set a List of {@link TemplateLoader TemplateLoaders} that will be used to
|
||||
* search for templates.
|
||||
* <p>For example, one or more custom loaders such as database loaders could
|
||||
* be configured and injected here.
|
||||
* <p>The {@code TemplateLoaders} specified here will be registered <i>before</i>
|
||||
* the default template loaders that this factory registers (such as loaders
|
||||
* for specified "templateLoaderPaths" or any loaders registered in
|
||||
* {@link #postProcessTemplateLoaders}).
|
||||
* @see #setTemplateLoaderPaths
|
||||
* @see #postProcessTemplateLoaders
|
||||
*/
|
||||
@@ -161,13 +172,14 @@ public class FreeMarkerConfigurationFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a List of {@code TemplateLoader}s that will be used to search
|
||||
* for templates. For example, one or more custom loaders such as database
|
||||
* loaders can be configured.
|
||||
* <p>The {@link TemplateLoader TemplateLoaders} specified here will be
|
||||
* registered <i>after</i> the default template loaders that this factory
|
||||
* registers (such as loaders for specified "templateLoaderPaths" or any
|
||||
* loaders registered in {@link #postProcessTemplateLoaders}).
|
||||
* Set a List of {@link TemplateLoader TemplateLoaders} that will be used to
|
||||
* search for templates.
|
||||
* <p>For example, one or more custom loaders such as database loaders could
|
||||
* be configured and injected here.
|
||||
* <p>The {@code TemplateLoaders} specified here will be registered <i>after</i>
|
||||
* the default template loaders that this factory registers (such as loaders
|
||||
* for specified "templateLoaderPaths" or any loaders registered in
|
||||
* {@link #postProcessTemplateLoaders}).
|
||||
* @see #setTemplateLoaderPaths
|
||||
* @see #postProcessTemplateLoaders
|
||||
*/
|
||||
@@ -177,7 +189,7 @@ public class FreeMarkerConfigurationFactory {
|
||||
|
||||
/**
|
||||
* Set the Freemarker template loader path via a Spring resource location.
|
||||
* See the "templateLoaderPaths" property for details on path handling.
|
||||
* <p>See the "templateLoaderPaths" property for details on path handling.
|
||||
* @see #setTemplateLoaderPaths
|
||||
*/
|
||||
public void setTemplateLoaderPath(String templateLoaderPath) {
|
||||
@@ -188,28 +200,29 @@ public class FreeMarkerConfigurationFactory {
|
||||
* Set multiple Freemarker template loader paths via Spring resource locations.
|
||||
* <p>When populated via a String, standard URLs like "file:" and "classpath:"
|
||||
* pseudo URLs are supported, as understood by ResourceEditor. Allows for
|
||||
* relative paths when running in an ApplicationContext.
|
||||
* <p>Will define a path for the default FreeMarker template loader.
|
||||
* If a specified resource cannot be resolved to a {@code java.io.File},
|
||||
* a generic SpringTemplateLoader will be used, without modification detection.
|
||||
* <p>To enforce the use of SpringTemplateLoader, i.e. to not resolve a path
|
||||
* as file system resource in any case, turn off the "preferFileSystemAccess"
|
||||
* relative paths when running in an {@code ApplicationContext}.
|
||||
* <p>Will define a path for the default FreeMarker template loader. If a
|
||||
* specified resource cannot be resolved to a {@code java.io.File}, a generic
|
||||
* {@link SpringTemplateLoader} will be used, without modification detection.
|
||||
* <p>To enforce the use of {@code SpringTemplateLoader}, i.e. to not resolve
|
||||
* a path as file system resource in any case, turn off the "preferFileSystemAccess"
|
||||
* flag. See the latter's javadoc for details.
|
||||
* <p>If you wish to specify your own list of TemplateLoaders, do not set this
|
||||
* property and instead use {@code setTemplateLoaders(List templateLoaders)}
|
||||
* property and instead use {@link #setPostTemplateLoaders(TemplateLoader...)}.
|
||||
* @see org.springframework.core.io.ResourceEditor
|
||||
* @see org.springframework.context.ApplicationContext#getResource
|
||||
* @see freemarker.template.Configuration#setDirectoryForTemplateLoading
|
||||
* @see SpringTemplateLoader
|
||||
* @see #setPreferFileSystemAccess(boolean)
|
||||
*/
|
||||
public void setTemplateLoaderPaths(String... templateLoaderPaths) {
|
||||
this.templateLoaderPaths = templateLoaderPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Spring ResourceLoader to use for loading FreeMarker template files.
|
||||
* The default is DefaultResourceLoader. Will get overridden by the
|
||||
* ApplicationContext if running in a context.
|
||||
* Set the {@link ResourceLoader} to use for loading FreeMarker template files.
|
||||
* <p>The default is {@link DefaultResourceLoader}. Will get overridden by the
|
||||
* {@code ApplicationContext} if running in a context.
|
||||
* @see org.springframework.core.io.DefaultResourceLoader
|
||||
*/
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
@@ -217,7 +230,7 @@ public class FreeMarkerConfigurationFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Spring ResourceLoader to use for loading FreeMarker template files.
|
||||
* Return the {@link ResourceLoader} to use for loading FreeMarker template files.
|
||||
*/
|
||||
protected ResourceLoader getResourceLoader() {
|
||||
return this.resourceLoader;
|
||||
@@ -225,11 +238,11 @@ public class FreeMarkerConfigurationFactory {
|
||||
|
||||
/**
|
||||
* Set whether to prefer file system access for template loading.
|
||||
* File system access enables hot detection of template changes.
|
||||
* <p>File system access enables hot detection of template changes.
|
||||
* <p>If this is enabled, FreeMarkerConfigurationFactory will try to resolve
|
||||
* the specified "templateLoaderPath" as file system resource (which will work
|
||||
* for expanded class path resources and ServletContext resources too).
|
||||
* <p>Default is "true". Turn this off to always load via SpringTemplateLoader
|
||||
* <p>Default is "true". Turn this off to always load via {@link SpringTemplateLoader}
|
||||
* (i.e. as stream, without hot detection of template changes), which might
|
||||
* be necessary if some of your templates reside in an expanded classes
|
||||
* directory while others reside in jar files.
|
||||
@@ -248,8 +261,8 @@ public class FreeMarkerConfigurationFactory {
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the FreeMarker Configuration and return it.
|
||||
* @return the FreeMarker Configuration object
|
||||
* Prepare the FreeMarker {@link Configuration} and return it.
|
||||
* @return the FreeMarker {@code Configuration} object
|
||||
* @throws IOException if the config file wasn't found
|
||||
* @throws TemplateException on FreeMarker initialization failure
|
||||
*/
|
||||
@@ -314,11 +327,12 @@ public class FreeMarkerConfigurationFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new Configuration object. Subclasses can override this for custom
|
||||
* initialization (e.g. specifying a FreeMarker compatibility level which is a
|
||||
* new feature in FreeMarker 2.3.21), or for using a mock object for testing.
|
||||
* <p>Called by {@code createConfiguration()}.
|
||||
* @return the Configuration object
|
||||
* Return a new {@link Configuration} object.
|
||||
* <p>Subclasses can override this for custom initialization — for example,
|
||||
* to specify a FreeMarker compatibility level (which is a new feature in
|
||||
* FreeMarker 2.3.21), or to use a mock object for testing.
|
||||
* <p>Called by {@link #createConfiguration()}.
|
||||
* @return the {@code Configuration} object
|
||||
* @throws IOException if a config file wasn't found
|
||||
* @throws TemplateException on FreeMarker initialization failure
|
||||
* @see #createConfiguration()
|
||||
@@ -328,11 +342,11 @@ public class FreeMarkerConfigurationFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a FreeMarker TemplateLoader for the given path.
|
||||
* <p>Default implementation creates either a FileTemplateLoader or
|
||||
* a SpringTemplateLoader.
|
||||
* Determine a FreeMarker {@link TemplateLoader} for the given path.
|
||||
* <p>Default implementation creates either a {@link FileTemplateLoader} or
|
||||
* a {@link SpringTemplateLoader}.
|
||||
* @param templateLoaderPath the path to load templates from
|
||||
* @return an appropriate TemplateLoader
|
||||
* @return an appropriate {@code TemplateLoader}
|
||||
* @see freemarker.cache.FileTemplateLoader
|
||||
* @see SpringTemplateLoader
|
||||
*/
|
||||
@@ -366,9 +380,9 @@ public class FreeMarkerConfigurationFactory {
|
||||
|
||||
/**
|
||||
* To be overridden by subclasses that want to register custom
|
||||
* TemplateLoader instances after this factory created its default
|
||||
* {@link TemplateLoader} instances after this factory created its default
|
||||
* template loaders.
|
||||
* <p>Called by {@code createConfiguration()}. Note that specified
|
||||
* <p>Called by {@link #createConfiguration()}. Note that specified
|
||||
* "postTemplateLoaders" will be registered <i>after</i> any loaders
|
||||
* registered by this callback; as a consequence, they are <i>not</i>
|
||||
* included in the given List.
|
||||
@@ -381,10 +395,10 @@ public class FreeMarkerConfigurationFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a TemplateLoader based on the given TemplateLoader list.
|
||||
* If more than one TemplateLoader has been registered, a FreeMarker
|
||||
* MultiTemplateLoader needs to be created.
|
||||
* @param templateLoaders the final List of TemplateLoader instances
|
||||
* Return a {@link TemplateLoader} based on the given {@code TemplateLoader} list.
|
||||
* <p>If more than one TemplateLoader has been registered, a FreeMarker
|
||||
* {@link MultiTemplateLoader} will be created.
|
||||
* @param templateLoaders the final List of {@code TemplateLoader} instances
|
||||
* @return the aggregate TemplateLoader
|
||||
*/
|
||||
@Nullable
|
||||
@@ -404,10 +418,10 @@ public class FreeMarkerConfigurationFactory {
|
||||
|
||||
/**
|
||||
* To be overridden by subclasses that want to perform custom
|
||||
* post-processing of the Configuration object after this factory
|
||||
* post-processing of the {@link Configuration} object after this factory
|
||||
* performed its default initialization.
|
||||
* <p>Called by {@code createConfiguration()}.
|
||||
* @param config the current Configuration object
|
||||
* <p>Called by {@link #createConfiguration()}.
|
||||
* @param config the current {@code Configuration} object
|
||||
* @throws IOException if a config file wasn't found
|
||||
* @throws TemplateException on FreeMarker initialization failure
|
||||
* @see #createConfiguration()
|
||||
|
||||
+12
-9
@@ -27,22 +27,25 @@ import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Factory bean that creates a FreeMarker Configuration and provides it as
|
||||
* bean reference. This bean is intended for any kind of usage of FreeMarker
|
||||
* in application code, e.g. for generating email content. For web views,
|
||||
* FreeMarkerConfigurer is used to set up a FreeMarkerConfigurationFactory.
|
||||
* <p>
|
||||
* The simplest way to use this class is to specify just a "templateLoaderPath";
|
||||
* Factory bean that creates a FreeMarker {@link Configuration} and provides it
|
||||
* as a bean reference.
|
||||
*
|
||||
* <p>This bean is intended for any kind of usage of FreeMarker in application
|
||||
* code — for example, for generating email content. For web views,
|
||||
* {@code FreeMarkerConfigurer} is used to set up a {@link FreeMarkerConfigurationFactory}.
|
||||
*
|
||||
* <p>The simplest way to use this class is to specify just a "templateLoaderPath";
|
||||
* you do not need any further configuration then. For example, in a web
|
||||
* application context:
|
||||
*
|
||||
* <pre class="code"> <bean id="freemarkerConfiguration" class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean">
|
||||
* <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
|
||||
* </bean></pre>
|
||||
|
||||
* See the base class FreeMarkerConfigurationFactory for configuration details.
|
||||
*
|
||||
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
|
||||
* <p>See the {@link FreeMarkerConfigurationFactory} base class for configuration
|
||||
* details.
|
||||
*
|
||||
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3.21 or higher.
|
||||
*
|
||||
* @author Darren Davison
|
||||
* @since 03.03.2004
|
||||
|
||||
+6
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -24,7 +24,8 @@ import freemarker.template.TemplateException;
|
||||
|
||||
/**
|
||||
* Utility class for working with FreeMarker.
|
||||
* Provides convenience methods to process a FreeMarker template with a model.
|
||||
*
|
||||
* <p>Provides convenience methods to process a FreeMarker template with a model.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 14.03.2004
|
||||
@@ -33,12 +34,12 @@ public abstract class FreeMarkerTemplateUtils {
|
||||
|
||||
/**
|
||||
* Process the specified FreeMarker template with the given model and write
|
||||
* the result to the given Writer.
|
||||
* <p>When using this method to prepare a text for a mail to be sent with Spring's
|
||||
* the result to a String.
|
||||
* <p>When using this method to prepare text for a mail to be sent with Spring's
|
||||
* mail support, consider wrapping IO/TemplateException in MailPreparationException.
|
||||
* @param model the model object, typically a Map that contains model names
|
||||
* as keys and model objects as values
|
||||
* @return the result as String
|
||||
* @return the result as a String
|
||||
* @throws IOException if the template wasn't found or couldn't be read
|
||||
* @throws freemarker.template.TemplateException if rendering failed
|
||||
* @see org.springframework.mail.MailPreparationException
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user