Compare commits

...

89 Commits

Author SHA1 Message Date
Spring Builds b932df6ad2 Release v6.1.6 2024-04-11 08:14:00 +00:00
Brian Clozel 494ed4e852 Refine UriComponentsBuilder parsing
This commit refines the expressions for the scheme, user info, host and
port parts of the URL in UriComponentsBuilder to better conform to
RFC 3986.

Fixes gh-32616
2024-04-11 08:49:11 +02:00
Juergen Hoeller 39cd31613b Polishing 2024-04-10 18:06:32 +02:00
Sébastien Deleuze ca2b3c170c Add Kotlin hints for enclosing class
This is needed by Kotlin reflection in order to be able to list
class members on native.

Closes gh-32472
2024-04-10 17:53:12 +02:00
Brian Clozel 019ce4418c BufferingClientHttpRequestWrapper should not write empty body
Prior to this commit, `BufferingClientHttpRequestWrapper` would always
write to the actual client request body, even if the buffered content
was empty (empty byte array).
This would cause issues with specific client request factories,
especially the OkHttp variant, that would consider empty byte arrays as
non-empty body and would reject such cases for GET requests with an
"IllegalArgumentException: method GET must not have a request body".

This commit only writes to the request if the buffered content is not
empty.

Fixes gh-32612
2024-04-10 16:41:16 +02:00
Stéphane Nicoll a0136a276a Upgrade to Reactor 2023.0.5
Closes gh-32592
2024-04-09 19:51:08 +02:00
rstoyanchev 8d05028c2e RequestParam resolver supports empty array suffix
Closes gh-32577
2024-04-09 18:24:57 +01:00
rstoyanchev 4a68c44a27 Update content-length when reconstructing body
Closes gh-32471
2024-04-09 18:24:57 +01:00
Sam Brannen e702733c7b Detect bridge methods across ApplicationContexts in MethodIntrospector
Prior to this commit, MethodIntrospector failed to properly detect
bridge methods for subsequent invocations of selectMethods() with the
same targetType and MetadataLookup, if such subsequent invocations
occurred after the ApplicationContext had been refreshed.

The reason this occurs is due to the following.

- Class#getDeclaredMethods() always returns "child copies" of the
  underlying Method instances -- which means that `equals()` should be
  used instead of `==` whenever the compared Method instances can come
  from different sources (such as the static caches mentioned below).

- BridgeMethodResolver caches resolved bridge methods in a static cache
  -- which is never cleared.

- ReflectionUtils caches declared methods in a static cache
  -- which gets cleared when an ApplicationContext is refreshed.

Consequently, if you attempt to load an ApplicationContext twice in the
same ClassLoader, the second attempt uses the existing, populated cache
for bridged methods but a cleared, empty cache for declared methods.
This results in new invocations of Class#getDeclaredMethods(), and
identity checks with `==` then fail to detect equivalent bridge methods.

This commit addresses this by additionally comparing bridge methods
using `equals()` in MethodIntrospector.selectMethods().

Note that the `==` checks remain in place as an optimization for when
`equals()` is unnecessary.

Closes gh-32586
2024-04-09 18:56:41 +02:00
Sam Brannen 187f641409 Polishing 2024-04-09 18:55:39 +02:00
Brian Clozel ac74116d76 Instrument JmsTemplate#sendAndReceive
Prior to this commit, the `JmsTemplate#sendAndReceive` method would not
instrument the JMS session. This means that no metric would be recorded
when sending the message and no trace would be propagated downstream.

This commit ensures that the JMS session is instrumented in this case as
well. Note, the reception of the response message does not create a
`"jms.message.process"` observation as the session is only receiving the
message, no listener has been configured on the message consumer.

Fixes gh-32606
2024-04-09 18:34:09 +02:00
Juergen Hoeller c5590ae9e6 Log column type for limited support message in getResultSetValue
Closes gh-32601
2024-04-09 16:14:45 +02:00
Juergen Hoeller 39b551c334 Document hand-off to execution thread (including ScheduledFuture impact)
Closes gh-32589
2024-04-09 16:14:38 +02:00
Sébastien Deleuze 23696b7db8 Add a classpath check for AOP Coroutines/Reactive conversion
Closes gh-32599
2024-04-09 15:21:21 +02:00
Stéphane Nicoll 7b7cfb27a3 Upgrade to Micrometer 1.12.5
Closes gh-32596
2024-04-09 12:59:46 +02:00
Juergen Hoeller f2889b1b43 Consistent support for generic FactoryBean type matching
Closes gh-32590
See gh-32489
2024-04-08 22:39:29 +02:00
Stéphane Nicoll 802967fc98 Add reproducer
See gh-32489
2024-04-08 11:09:56 +02:00
Juergen Hoeller 22328905da Do not extract FactoryBean generic in case of targetType mismatch
Closes gh-32489
2024-04-08 09:51:59 +02:00
Juergen Hoeller 67a81004a2 Upgrade to AspectJ 1.9.22, Groovy 4.0.20, Tomcat 10.1.20, Netty 4.1.108, OpenPDF 1.3.43, Woodstox 6.6.2, Checkstyle 10.15 2024-04-05 18:04:58 +02:00
Juergen Hoeller 84963ba4d8 Polishing 2024-04-05 18:04:08 +02:00
Juergen Hoeller 07472758ee Detect Undertow RequestTooBigException message in handleParseFailure
Closes gh-32549
2024-04-05 18:03:44 +02:00
Arjen Poutsma 47c9c7eb07 Update copyright
See gh-32528
2024-04-05 16:13:39 +02:00
Stéphane Nicoll 8568aa691c Stop linking to Websphere's Javadoc
Closes gh-32581
2024-04-05 15:57:58 +02:00
Arjen Poutsma b24221754a ReactorNettyClientResponse should close response body
This commit ensures that the response body is drained and closed when
the response itself is closed, instead of disposing the connection, as
this will disable the connection pool.

Closes gh-32528
2024-04-05 15:38:18 +02:00
Sébastien Deleuze 7a666cfd86 Revert some null-safety changes
This commit reverts some null-safety changes which make sense
on main but are too impactful for 6.1.x for Kotlin developers
using -Xjsr305=strict.

See gh-32475
2024-04-05 14:59:39 +02:00
Sébastien Deleuze 4a7c24d90f Refine null-safety
See gh-32475
2024-04-05 14:30:50 +02:00
Stéphane Nicoll d955549037 Clarify scope of exception
See gh-29491
2024-04-04 14:42:41 +02:00
Stéphane Nicoll b68f76c86e Polish
See gh-32502
2024-04-04 14:42:41 +02:00
Simon Baslé 521cda009b Use non-blocking thread in WebFlux controller with RequestBody parameter
This commit ensures that `InvocableHandlerMethod` executes the method
on the desired thread if a non-blocking thread is specified, even in the
case where arguments resolution happens on a different thread. This is
notably the case if the method body is resolved as an input argument to
the controller method (`@RequestBody`).

Closes gh-32502
2024-04-04 14:20:16 +02:00
Stéphane Nicoll 08c9b5c96c Upgrade to Gradle 8.7
Closes gh-32566
2024-04-02 18:01:37 +02:00
Stéphane Nicoll a942a6e172 Document behavior of profiles enabled for AOT optimizations
Closes gh-32543
2024-04-02 15:47:43 +02:00
Stéphane Nicoll 9b1d5ea6e0 Merge pull request #32557 from Banuelorigni
* pr/32557:
  Polish reference guide

Closes gh-32557
2024-04-02 09:33:00 +02:00
kexin.an 640b97f33c Polish reference guide
See gh-32557
2024-04-02 09:29:55 +02:00
Stéphane Nicoll 1fb99e4192 Merge pull request #32560 from quaff
* pr/32560:
  Polish javadoc of AnnotatedBeanDefinitionReader

Closes gh-32560
2024-04-01 12:14:51 +02:00
Yanming Zhou 23d89362b0 Polish javadoc of AnnotatedBeanDefinitionReader
See gh-32560
2024-04-01 12:12:02 +02:00
Sébastien Deleuze 048e4f0aa2 Invoke KCallablesJvm.setAccessible only when required
Closes-32536
2024-03-28 11:12:50 +01:00
T45K b28153e1d7 Fix handling of value class with private constructor
See gh-32536
2024-03-28 11:11:58 +01:00
Sébastien Deleuze 88b9c0567d Refine null-safety in spring-test
See gh-32475
2024-03-26 18:09:55 +01:00
Stéphane Nicoll c7c9da56da Consistent atMost period for Awaitility-based tests
Closes gh-32537
2024-03-26 17:45:14 +01:00
Sébastien Deleuze 290a41d398 Refine null-safety in more modules
This commit refines the null-safety in all remaining modules
except spring-test.

See gh-32475
2024-03-26 15:39:18 +01:00
Sébastien Deleuze 1b563f8ba4 Refine null-safety in more modules
This commit refines the null-safety in:
 - spring-jdbc
 - spring-r2dbc
 - spring-orm
 - spring-beans
 - spring-aop

See gh-32475
2024-03-26 09:46:34 +01:00
Sébastien Deleuze dea31dd55e Refine null-safety in spring-messaging
See gh-32475
2024-03-25 12:24:53 +01:00
Sébastien Deleuze 06b91c4ea5 Refine null-safety in spring-jms
See gh-32475
2024-03-25 11:28:00 +01:00
Sébastien Deleuze a63ebe7e9d Refine null-safety in spring-web and spring-websocket
See gh-32475
2024-03-25 11:02:44 +01:00
Stéphane Nicoll c4b615052e Merge pull request #32517 from izeye
* pr/32517:
  Add Javadoc since to KotlinSerializationStringEncoder.setStreamingMediaTypes()

Closes gh-32517
2024-03-24 09:23:05 +01:00
Johnny Lim dca5f1dcd8 Add Javadoc since to KotlinSerializationStringEncoder.setStreamingMediaTypes()
See gh-32517
2024-03-24 09:21:36 +01:00
Sébastien Deleuze a16e8c34b2 Refine null-safety in spring-webmvc
See gh-32475
2024-03-22 17:44:35 +01:00
Sam Brannen 52b8c71dcd Retain null-safe syntax in AST representation of selection & projection
Prior to this commit, SpEL's CompoundExpression omitted the null-safe
syntax in AST string representations of the selection and projection
operators.

To address this, this commit implements isNullSafe() in Projection and
Selection.

Closes gh-32515
2024-03-22 17:05:48 +01:00
Sam Brannen f941754db6 Introduce isNullSafe() in SpelNodeImpl
Prior to this commit, MethodReference and PropertyOrFieldReference
already defined local isNullSafe() methods, but we need identical
methods in Selection, Projection, and Indexer, and we may potentially
need null-safe support for additional operators in the future.

To address the common need for an is-null-safe check, this commit
introduces an isNullSafe() method in SpelNodeImpl with a default
implementation that returns false.

Closes gh-32516
2024-03-22 16:37:08 +01:00
Sam Brannen d4495a5654 Revise PropertyOrFieldReference since isNullSafe() is not final 2024-03-22 16:37:08 +01:00
Stéphane Nicoll c7c61e13e1 Merge pull request #32514 from izeye
* pr/32514:
  Apply UnnecessarySemicolonInEnumeration Checkstyle module

Closes gh-32514
2024-03-22 16:31:54 +01:00
Johnny Lim 4d722414da Apply UnnecessarySemicolonInEnumeration Checkstyle module
This commit also fixes its violation.

See gh-32514
2024-03-22 16:30:06 +01:00
Sébastien Deleuze 87dfa492af Refine null-safety in spring-webflux
See gh-32475
2024-03-22 16:05:17 +01:00
Sébastien Deleuze cf9033a60d Polishing 2024-03-22 16:04:03 +01:00
Sébastien Deleuze 1e80694daf Fix a regression with Kotlin generic controllers
This commit reintroduces a more defensive parameter type check that
skips KClass casting for generic parameters.

Closes gh-32510
2024-03-21 17:55:57 +01:00
Stéphane Nicoll e17c3d3be4 Upgrade actions that use deprecated features 2024-03-20 10:16:09 +01:00
Sébastien Deleuze 2e98a8a2a4 Refine null-safety in spring-expression
See gh-32475
2024-03-20 10:07:56 +01:00
Stéphane Nicoll 592f23674d Merge pull request #32494 from qww1552
* pr/32494:
  Fix link to vavr in the reference guide

Closes gh-32494
2024-03-20 10:01:47 +01:00
Minsung Oh 0b3a05b463 Fix link to vavr in the reference guide
See gh-32494
2024-03-20 09:48:00 +01:00
Sam Brannen ef02f0bad8 Wrap InvalidMimeTypeException in HttpMediaTypeNotAcceptableException
The fix for #31254 resulted in an InvalidMimeTypeException being thrown
by MimeTypeUtils.sortBySpecificity() instead of an
IllegalArgumentException. However, InvalidMimeTypeException extends
IllegalArgumentException. Consequently, the change from
IllegalArgumentException to InvalidMimeTypeException did not result in
the desired effect in HeaderContentNegotiationStrategy.

HeaderContentNegotiationStrategy.resolveMediaTypes() still allows the
InvalidMimeTypeException to propagate as-is without wrapping it in an
HttpMediaTypeNotAcceptableException.

To address this issue, this commit catches InvalidMediaTypeException
and InvalidMimeTypeException in HeaderContentNegotiationStrategy and
wraps the exception in an HttpMediaTypeNotAcceptableException.

See gh-31254
See gh-31769
Closes gh-32483
2024-03-19 15:51:27 +01:00
Sam Brannen 836a0b3a40 Polishing 2024-03-19 14:19:01 +01:00
Sam Brannen 09f210f5f7 Polish wording in reference manual 2024-03-19 14:15:19 +01:00
Sébastien Deleuze aa0fb97ba5 Nullability refinements
See gh-32475
2024-03-19 13:32:13 +01:00
Sébastien Deleuze 71a117c0fd Document AOT limitations related to Kotlin backticks
Closes gh-32487
2024-03-19 11:03:56 +01:00
Juergen Hoeller bd27ddf83a Polishing (aligned with main) 2024-03-19 10:05:32 +01:00
Juergen Hoeller c531a8a705 Nullability refinements and related polishing
See gh-32475
2024-03-19 09:58:44 +01:00
Juergen Hoeller cd7ba1835c Propagate JMS IllegalStateException from commit/rollbackIfNecessary
Closes gh-32473
2024-03-18 16:03:13 +01:00
Juergen Hoeller 5dc6a16c0b Nullability refinements
See gh-32475
2024-03-18 16:03:00 +01:00
Sam Brannen 42a4f28962 Avoid unnecessary Annotation array cloning in TypeDescriptor
Closes gh-32476
2024-03-18 15:17:04 +01:00
Brian Clozel 521d4f24d0 Remove flaky test in JMS observation
There is no way to consistently test this use case because listener
container implementations have different behavior for unhandled errors
and might retry or close the container altogether.

See gh-32458
2024-03-18 14:35:42 +01:00
Stéphane Nicoll 039cbb3466 Merge pull request #32474 from izeye
* pr/32474:
  Add Javadoc since to AbstractClientHttpRequestFactoryWrapper.getDelegate()

Closes gh-32474
2024-03-18 14:10:58 +01:00
Johnny Lim d50c94fca9 Add Javadoc since to AbstractClientHttpRequestFactoryWrapper.getDelegate()
See gh-32474
2024-03-18 14:08:59 +01:00
Brian Clozel c1424acb94 Fix flaky test in JMS observability
See gh-32458
2024-03-18 12:17:57 +01:00
Juergen Hoeller 36d1bc57d7 Upgrade to Checkstyle 10.14.2 2024-03-17 20:54:54 +01:00
Juergen Hoeller b1c3b6e34b Restore original toString representation (revert accidental backport)
See gh-32405
2024-03-17 20:42:29 +01:00
Juergen Hoeller eb8b7c4331 Remove superfluous @NonNull declarations 2024-03-16 14:22:17 +01:00
Juergen Hoeller 58bd057a24 Avoid cloning empty Annotation array in TypeDescriptor (backport)
Closes gh-32405
2024-03-16 14:21:34 +01:00
Brian Clozel a78704af24 Record unhanlded JMS exceptions as observation errors
Prior to this commit, when using the `DefaultMessageListenerContainer`
as a backing listener container for JMS messages, the instrumentation
would not record JMS exceptions as observation errors when they were not
handled by configured `ErrorHandler`.

This commit ensures that this is the case.

Fixes gh-32458
2024-03-15 22:45:36 +01:00
Juergen Hoeller d2000efbd8 Polishing 2024-03-15 21:17:01 +01:00
Juergen Hoeller c23fd784a0 Consistently apply TaskDecorator to ManagedExecutorService as well
Closes gh-32455
2024-03-15 21:16:37 +01:00
Stéphane Nicoll 68a3993897 Merge pull request #32446 from LinorDolev
* pr/32446:
  Polish "Fix default strategy description of ProblemDetail error codes"
  Fix default strategy description of ProblemDetail error codes

Closes gh-32446
2024-03-15 08:28:19 +01:00
Stéphane Nicoll dfc1b839f3 Polish "Fix default strategy description of ProblemDetail error codes"
See gh-32446
2024-03-15 08:27:59 +01:00
Linor Dolev 302c24aaa8 Fix default strategy description of ProblemDetail error codes
See gh-32446
2024-03-15 08:02:20 +01:00
Stéphane Nicoll 7357812b4e Polish "Only include production branches for docs deployment"
See gh-32434
2024-03-14 17:47:12 +01:00
Stéphane Nicoll 30087e1aea Only include production branches for docs deployment
Closes gh-32434
2024-03-14 17:42:02 +01:00
Stéphane Nicoll cd5758e610 Restore CI execution per commit
As scheduled only works on the base branch anyway

See gh-32436
2024-03-14 15:28:23 +01:00
Stéphane Nicoll b82a0dd31a Polish "Move CI and snapshot deployment to GitHub actions"
See gh-32436
2024-03-14 15:16:02 +01:00
Stéphane Nicoll b7280400ee Move CI and snapshot deployment to GitHub actions
Closes gh-32436
2024-03-14 15:01:26 +01:00
Spring Builds a3345920ec Next development version (v6.1.6-SNAPSHOT) 2024-03-14 09:58:35 +00:00
446 changed files with 2796 additions and 1370 deletions
@@ -0,0 +1,33 @@
name: Send notification
description: Sends a Google Chat message as a notification of the job's outcome
inputs:
webhook-url:
description: 'Google Chat Webhook URL'
required: true
status:
description: 'Status of the job'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
run-name:
description: 'Name of the run to include in the notification'
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
runs:
using: composite
steps:
- shell: bash
run: |
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
- shell: bash
if: ${{ inputs.status == 'success' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'failure' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'cancelled' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
+6 -4
View File
@@ -18,11 +18,13 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- name: Check out code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
distribution: 'liberica'
java-version: 17
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
@@ -0,0 +1,64 @@
name: Build and deploy snapshot
on:
push:
branches:
- 6.1.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and deploy snapshot
runs-on: ubuntu-latest
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- 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: Build and publish
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@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)}}
repository: 'libs-snapshot-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
- 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) }}
+80
View File
@@ -0,0 +1,80 @@
name: CI
on:
push:
branches:
- 6.1.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
os:
- id: ubuntu-latest
name: Linux
java:
- version: 17
toolchain: false
- version: 21
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
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'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
run: ./gradlew check antora
- 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) }}
+8 -6
View File
@@ -1,12 +1,14 @@
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
tags: '**'
branches:
- 'main'
- '*.x'
- '!gh-pages'
tags:
- 'v*'
repository_dispatch:
types: request-build-reference # legacy
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
actions: write
@@ -15,8 +17,8 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check out code
uses: actions/checkout@v4
with:
ref: docs-build
fetch-depth: 1
@@ -9,5 +9,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-6.1.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=6.1.x)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
-1
View File
@@ -90,7 +90,6 @@ configure([rootProject] + javaProjects) { project ->
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.14.1");
checkstyle.setToolVersion("10.15.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+2
View File
@@ -1,5 +1,7 @@
== 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].
-1
View File
@@ -8,4 +8,3 @@ milestone: "6.1.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
task-timeout: 1h00m
+25 -165
View File
@@ -23,14 +23,6 @@ anchors:
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
[$TEXT_FILE_CONTENT]
text_file: git-repo/build/build-scan-uri.txt
silent: true
icon_emoji: ":concourse:"
username: concourse-ci
changelog-task-params: &changelog-task-params
name: generated-changelog/tag
tag: generated-changelog/tag
@@ -64,12 +56,6 @@ resource_types:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: registry-image
source:
<<: *docker-resource-source
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
- name: git-repo
type: git
@@ -90,13 +76,6 @@ resources:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
tag: ((milestone))
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@@ -105,35 +84,6 @@ resources:
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk21-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk21-build
- name: repo-status-jdk23-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk23-build
- name: slack-alert
type: slack-notification
icon: slack
source:
url: ((slack-webhook-url))
- name: github-pre-release
type: github-release
icon: briefcase-download-outline
@@ -168,115 +118,6 @@ jobs:
- put: ci-image
params:
image: ci-image/image.tar
- name: build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
trigger: true
- put: repo-status-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: build-project
image: ci-image
file: git-repo/ci/tasks/build-project.yml
privileged: true
timeout: ((task-timeout))
params:
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-build
params: { state: "success", commit: "git-repo" }
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
repo: libs-snapshot-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
- name: jdk21-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk21-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 21
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk21-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk21-build
params: { state: "success", commit: "git-repo" }
- name: jdk23-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: false
- put: repo-status-jdk23-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 23
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk23-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk23-build
params: { state: "success", commit: "git-repo" }
- name: stage-milestone
serial: true
plan:
@@ -290,9 +131,32 @@ jobs:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
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
@@ -337,7 +201,6 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -382,7 +245,6 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -427,8 +289,6 @@ jobs:
<<: *changelog-task-params
groups:
- name: "builds"
jobs: ["build", "jdk21-build", "jdk23-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
--no-daemon --max-workers=4 check
popd > /dev/null
-10
View File
@@ -1,10 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK23 \
--no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK23 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
popd > /dev/null
-19
View File
@@ -1,19 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-pr.sh
-22
View File
@@ -1,22 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-project.sh
-24
View File
@@ -1,24 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
MAIN_TOOLCHAIN:
TEST_TOOLCHAIN:
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/check-project.sh
@@ -15,7 +15,7 @@ Applying such optimizations early implies the following restrictions:
* The classpath is fixed and fully defined at build time.
* The beans defined in your application cannot change at runtime, meaning:
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
** `@Profile`, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.
* Beans registered as singletons (using `registerSingleton`, typically from
@@ -152,8 +152,23 @@ Java::
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Configuration(proxyBeanMethods = false)
class DataSourceConfiguration {
@Bean
fun dataSource() = SimpleDataSource()
}
----
======
WARNING: Kotlin class names with backticks that use invalid Java identifiers (not starting with a letter, containing spaces, etc.) are not supported.
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
The AOT engine will convert the configuration class above to code similar to the following:
@@ -209,6 +224,17 @@ There is a bean definition for `dataSourceConfiguration` and one for `dataSource
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
[[aot.running]]
== Running with AOT optimizations
AOT is a mandatory step to transform a Spring application to a native executable, so it
is automatically enabled when running in this mode. It is possible to use those optimizations
on the JVM by setting the `spring.aot.enabled` System property to `true`.
NOTE: When AOT optimizations are included, some decisions that have been taken at build-time
are hard-coded in the application setup. For instance, profiles that have been enabled at
build-time are automatically enabled at runtime as well.
[[aot.bestpractices]]
== Best Practices
@@ -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 [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try).
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
Here's an example of how to use Vavr's Try with a transactional method:
[tabs]
@@ -81,7 +81,7 @@ Kotlin::
----
======
NOTE: `@JsonView` allows an array of view classes but you can only specify only one per
NOTE: `@JsonView` allows an array of view classes but you can specify only one per
controller method. Use a composite interface if you need to activate multiple views.
@@ -36,7 +36,7 @@ to render the response content as XML. If the model contains multiple entries, y
explicitly set the object to be serialized by using the `modelKey` bean property. If the
model contains a single entry, it is serialized automatically.
You can customized XML mapping as needed by using JAXB or Jackson's provided
You can customize XML mapping as needed by using JAXB or Jackson's provided
annotations. When you need further control, you can inject a custom `XmlMapper`
through the `ObjectMapper` property, for cases where custom XML
you need to provide serializers and deserializers for specific types.
@@ -683,7 +683,7 @@ the HTML would be as follows:
----
What if we want to display the entire list of errors for a given page? The next example
shows that the `errors` tag also supports some basic wildcarding functionality.
shows that the `errors` tag also supports some basic wildcard functionality.
* `path="{asterisk}"`: Displays all errors.
* `path="lastName"`: Displays all errors associated with the `lastName` field.
@@ -500,8 +500,8 @@ The MVC configuration exposes the following options for asynchronous request pro
You can configure the following:
* Default timeout value for async requests, which if not set, depends
on the underlying Servlet container.
* The default timeout value for async requests depends
on the underlying Servlet container, unless it is set explicitly.
* `AsyncTaskExecutor` to use for blocking writes when streaming with
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] and for
executing `Callable` instances returned from controller methods.
@@ -79,9 +79,11 @@ message code arguments for the "detail" field. `ResponseEntityExceptionHandler`
these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
and updates the corresponding `ProblemDetail` fields accordingly.
The default strategy for message codes follows the pattern:
The default strategy for message codes is as follows:
`problemDetail.[type|title|detail].[fully qualified exception class name]`
* "type": `problemDetail.type.[fully qualified exception class name]`
* "title": `problemDetail.title.[fully qualified exception class name]`
* "detail": `problemDetail.[fully qualified exception class name][suffix]`
An `ErrorResponse` may expose more than one message code, typically adding a suffix
to the default message code. The table below lists message codes, and arguments for
@@ -136,7 +138,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
| `MethodArgumentNotValidException`
| (default)
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
Message codes and arguments for each error are also resolvedvia `MessageSource`.
Message codes and arguments for each error are also resolved via `MessageSource`.
| `MissingRequestHeaderException`
| (default)
+13 -13
View File
@@ -8,12 +8,12 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
api(platform("io.micrometer:micrometer-bom:1.12.4"))
api(platform("io.netty:netty-bom:4.1.107.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.5"))
api(platform("io.netty:netty-bom:4.1.108.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.4"))
api(platform("io.projectreactor:reactor-bom:2023.0.5"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.19"))
api(platform("org.apache.groovy:groovy-bom:4.0.20"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.25.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.7"))
@@ -25,9 +25,9 @@ dependencies {
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.6.1")
api("com.fasterxml.woodstox:woodstox-core:6.6.2")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.librepdf:openpdf:1.3.42")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.10.1")
@@ -103,13 +103,13 @@ dependencies {
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.19")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.19")
api("org.apache.tomcat:tomcat-util:10.1.19")
api("org.apache.tomcat:tomcat-websocket:10.1.19")
api("org.aspectj:aspectjrt:1.9.21.1")
api("org.aspectj:aspectjtools:1.9.21.1")
api("org.aspectj:aspectjweaver:1.9.21.1")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.20")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.20")
api("org.apache.tomcat:tomcat-util:10.1.20")
api("org.apache.tomcat:tomcat-websocket:10.1.20")
api("org.aspectj:aspectjrt:1.9.22")
api("org.aspectj:aspectjtools:1.9.22")
api("org.aspectj:aspectjweaver:1.9.22")
api("org.awaitility:awaitility:4.2.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.1.5-SNAPSHOT
version=6.1.6
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -22,6 +22,7 @@ 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.support.RegisteredBean;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
@@ -38,6 +39,7 @@ class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotP
@Override
@Nullable
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (aspectjPresent) {
Class<?> beanClass = registeredBean.getBeanClass();
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.lang.Nullable;
/**
* {@link BeanDefinitionParser} responsible for parsing the
@@ -51,6 +52,7 @@ class SpringConfiguredBeanDefinitionParser implements BeanDefinitionParser {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(BEAN_CONFIGURER_ASPECT_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
@@ -94,14 +94,17 @@ class CglibAopProxy implements AopProxy, Serializable {
private static final int INVOKE_HASHCODE = 6;
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
"kotlinx.coroutines.reactor.MonoKt", CglibAopProxy.class.getClassLoader());
/** Logger available to subclasses; static to optimize serialization. */
protected static final Log logger = LogFactory.getLog(CglibAopProxy.class);
/** Keeps track of the Classes that we have validated for final methods. */
private static final Map<Class<?>, Boolean> validatedClasses = new WeakHashMap<>();
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
/** The configuration used to configure this proxy. */
protected final AdvisedSupport advised;
@@ -421,7 +424,7 @@ class CglibAopProxy implements AopProxy, Serializable {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
if (KotlinDetector.isSuspendingFunction(method)) {
if (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method)) {
return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
CoroutinesUtils.asFlow(returnValue) :
CoroutinesUtils.awaitSingleOrNull(returnValue, arguments[arguments.length - 1]);
@@ -75,6 +75,9 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
"kotlinx.coroutines.reactor.MonoKt", JdkDynamicAopProxy.class.getClassLoader());
/** We use a static Log to avoid serialization issues. */
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
@@ -234,7 +237,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
if (KotlinDetector.isSuspendingFunction(method)) {
if (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method)) {
return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
CoroutinesUtils.asFlow(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);
}
@@ -270,6 +270,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
}
@Override
@Nullable
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName);
@@ -311,6 +312,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
@Nullable
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
@@ -81,6 +81,7 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
* @see #setBeanNames(String...)
*/
@Override
@Nullable
protected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) {
return (isSupportedBeanName(beanClass, beanName) ?
super.getCustomTargetSource(beanClass, beanName) : null);
@@ -53,6 +53,7 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
@Override
@Nullable
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
if (beanClass.equals(ScopedProxyFactoryBean.class)) {
@@ -117,6 +117,7 @@ public class ScopedProxyFactoryBean extends ProxyConfig
@Override
@Nullable
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
@@ -125,6 +126,7 @@ public class ScopedProxyFactoryBean extends ProxyConfig
}
@Override
@Nullable
public Class<?> getObjectType() {
if (this.proxy != null) {
return this.proxy.getClass();
@@ -28,7 +28,6 @@ import java.util.Set;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.Job;
import org.reactivestreams.Publisher;
import org.springframework.aop.Advisor;
import org.springframework.aop.AopInvocationException;
@@ -65,6 +64,10 @@ import org.springframework.util.ReflectionUtils;
*/
public abstract class AopUtils {
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
"kotlinx.coroutines.reactor.MonoKt", AopUtils.class.getClassLoader());
/**
* Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
* <p>This method additionally checks if the given object is an instance
@@ -347,8 +350,8 @@ public abstract class AopUtils {
// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
return KotlinDetector.isSuspendingFunction(method) ?
KotlinDelegate.invokeSuspendingFunction(method, target, args) : method.invoke(target, args);
return (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method) ?
KotlinDelegate.invokeSuspendingFunction(method, target, args) : method.invoke(target, args));
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
@@ -364,18 +367,18 @@ public abstract class AopUtils {
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
*/
private static class KotlinDelegate {
public static Publisher<?> invokeSuspendingFunction(Method method, Object target, Object... args) {
public static Object invokeSuspendingFunction(Method method, @Nullable Object target, Object... args) {
Continuation<?> continuation = (Continuation<?>) args[args.length -1];
Assert.state(continuation != null, "No Continuation available");
CoroutineContext context = continuation.getContext().minusKey(Job.Key);
return CoroutinesUtils.invokeSuspendingFunction(context, method, target, args);
}
}
}
@@ -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.
@@ -845,8 +845,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
* @return the PropertyAccessor instance, either cached or newly created
*/
private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
if (this.nestedPropertyAccessors == null) {
this.nestedPropertyAccessors = new HashMap<>();
Map<String, AbstractNestablePropertyAccessor> nestedAccessors = this.nestedPropertyAccessors;
if (nestedAccessors == null) {
nestedAccessors = new HashMap<>();
this.nestedPropertyAccessors = nestedAccessors;
}
// Get value of bean property.
PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty);
@@ -862,7 +864,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
// Lookup cached sub-PropertyAccessor, create new one if not found.
AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName);
AbstractNestablePropertyAccessor nestedPa = nestedAccessors.get(canonicalName);
if (nestedPa == null || nestedPa.getWrappedInstance() != ObjectUtils.unwrapOptional(value)) {
if (logger.isTraceEnabled()) {
logger.trace("Creating new nested " + getClass().getSimpleName() + " for property '" + canonicalName + "'");
@@ -871,7 +873,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
// Inherit all type-specific PropertyEditors.
copyDefaultEditorsTo(nestedPa);
copyCustomEditorsTo(nestedPa, canonicalName);
this.nestedPropertyAccessors.put(canonicalName, nestedPa);
nestedAccessors.put(canonicalName, nestedPa);
}
else {
if (logger.isTraceEnabled()) {
@@ -69,7 +69,7 @@ public class BeanInstantiationException extends FatalBeanException {
* @param cause the root cause
* @since 4.3
*/
public BeanInstantiationException(Constructor<?> constructor, String msg, @Nullable Throwable cause) {
public BeanInstantiationException(Constructor<?> constructor, @Nullable String msg, @Nullable Throwable cause) {
super("Failed to instantiate [" + constructor.getDeclaringClass().getName() + "]: " + msg, cause);
this.beanClass = constructor.getDeclaringClass();
this.constructor = constructor;
@@ -84,7 +84,7 @@ public class BeanInstantiationException extends FatalBeanException {
* @param cause the root cause
* @since 4.3
*/
public BeanInstantiationException(Method constructingMethod, String msg, @Nullable Throwable cause) {
public BeanInstantiationException(Method constructingMethod, @Nullable String msg, @Nullable Throwable cause) {
super("Failed to instantiate [" + constructingMethod.getReturnType().getName() + "]: " + msg, cause);
this.beanClass = constructingMethod.getReturnType();
this.constructor = null;
@@ -18,6 +18,8 @@ package org.springframework.beans;
import java.beans.PropertyChangeEvent;
import org.springframework.lang.Nullable;
/**
* Thrown when a bean property getter or setter method throws an exception,
* analogous to an InvocationTargetException.
@@ -38,7 +40,7 @@ public class MethodInvocationException extends PropertyAccessException {
* @param propertyChangeEvent the PropertyChangeEvent that resulted in an exception
* @param cause the Throwable raised by the invoked method
*/
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, Throwable cause) {
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, @Nullable Throwable cause) {
super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause);
}
@@ -94,7 +94,7 @@ public class BeanCreationException extends FatalBeanException {
* @param beanName the name of the bean requested
* @param msg the detail message
*/
public BeanCreationException(@Nullable String resourceDescription, @Nullable String beanName, String msg) {
public BeanCreationException(@Nullable String resourceDescription, @Nullable String beanName, @Nullable String msg) {
super("Error creating bean with name '" + beanName + "'" +
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": " + msg);
this.resourceDescription = resourceDescription;
@@ -110,7 +110,7 @@ public class BeanCreationException extends FatalBeanException {
* @param msg the detail message
* @param cause the root cause
*/
public BeanCreationException(@Nullable String resourceDescription, String beanName, String msg, Throwable cause) {
public BeanCreationException(@Nullable String resourceDescription, String beanName, @Nullable String msg, Throwable cause) {
this(resourceDescription, beanName, msg);
initCause(cause);
}
@@ -44,7 +44,7 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
* @param msg the detail message
*/
public UnsatisfiedDependencyException(
@Nullable String resourceDescription, @Nullable String beanName, String propertyName, String msg) {
@Nullable String resourceDescription, @Nullable String beanName, String propertyName, @Nullable String msg) {
super(resourceDescription, beanName,
"Unsatisfied dependency expressed through bean property '" + propertyName + "'" +
@@ -75,7 +75,7 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
* @since 4.3
*/
public UnsatisfiedDependencyException(
@Nullable String resourceDescription, @Nullable String beanName, @Nullable InjectionPoint injectionPoint, String msg) {
@Nullable String resourceDescription, @Nullable String beanName, @Nullable InjectionPoint injectionPoint, @Nullable String msg) {
super(resourceDescription, beanName,
"Unsatisfied dependency expressed through " + injectionPoint +
@@ -37,6 +37,7 @@ import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.javapoet.AnnotationSpec;
import org.springframework.javapoet.CodeBlock;
import org.springframework.lang.Nullable;
/**
* Code generator {@link Delegate} for common bean definition property values.
@@ -101,6 +102,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
private static final CodeBlock EMPTY_RESULT = CodeBlock.of("$T.ofEntries()", ManagedMap.class);
@Override
@Nullable
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof ManagedMap<?, ?> managedMap) {
return generateManagedMapCode(valueCodeGenerator, managedMap);
@@ -137,6 +139,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
private static class LinkedHashMapDelegate extends MapDelegate {
@Override
@Nullable
protected CodeBlock generateMapCode(ValueCodeGenerator valueCodeGenerator, Map<?, ?> map) {
GeneratedMethods generatedMethods = valueCodeGenerator.getGeneratedMethods();
if (map instanceof LinkedHashMap<?, ?> && generatedMethods != null) {
@@ -172,6 +175,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
private static class BeanReferenceDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof RuntimeBeanReference runtimeBeanReference &&
runtimeBeanReference.getBeanType() != null) {
@@ -193,6 +197,7 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
private static class TypedStringValueDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(ValueCodeGenerator valueCodeGenerator, Object value) {
if (value instanceof TypedStringValue typedStringValue) {
return generateTypeStringValueCode(valueCodeGenerator, typedStringValue);
@@ -226,6 +226,7 @@ public class FieldRetrievingFactoryBean
}
@Override
@Nullable
public Class<?> getObjectType() {
return (this.fieldObject != null ? this.fieldObject.getType() : null);
}
@@ -136,6 +136,7 @@ public class MethodInvokingFactoryBean extends MethodInvokingBean implements Fac
* or {@code null} if not known in advance.
*/
@Override
@Nullable
public Class<?> getObjectType() {
if (!isPrepared()) {
// Not fully initialized yet -> return null to indicate "not known yet".
@@ -224,6 +224,7 @@ public class PropertyPathFactoryBean implements FactoryBean<Object>, BeanNameAwa
}
@Override
@Nullable
public Class<?> getObjectType() {
return this.resultType;
}
@@ -335,6 +335,7 @@ public class ServiceLocatorFactoryBean implements FactoryBean<Object>, BeanFacto
}
@Override
@Nullable
public Class<?> getObjectType() {
return this.serviceLocatorInterface;
}
@@ -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.
@@ -54,6 +54,7 @@ import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -250,6 +251,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
@SuppressWarnings("serial")
Closure<Object> beans = new Closure<>(this) {
@Override
@Nullable
public Object call(Object... args) {
invokeBeanDefiningClosure((Closure<?>) args[0]);
return null;
@@ -425,6 +427,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
private boolean addDeferredProperty(String property, Object newValue) {
if (newValue instanceof List || newValue instanceof Map) {
Assert.state(this.currentBeanDefinition != null, "No current bean definition set");
this.deferredProperties.put(this.currentBeanDefinition.getBeanName() + '.' + property,
new DeferredProperty(this.currentBeanDefinition, property, newValue));
return true;
@@ -640,6 +643,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
this.currentBeanDefinition = current;
}
}
Assert.state(this.currentBeanDefinition != null, "No current bean definition set");
this.currentBeanDefinition.addProperty(name, value);
}
@@ -654,6 +658,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
* </ul>
*/
@Override
@Nullable
public Object getProperty(String name) {
Binding binding = getBinding();
if (binding != null && binding.hasVariable(name)) {
@@ -727,9 +732,10 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
private final String name;
@Nullable
public Object value;
public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, Object value) {
public DeferredProperty(GroovyBeanDefinitionWrapper beanDefinition, String name, @Nullable Object value) {
this.beanDefinition = beanDefinition;
this.name = name;
this.value = value;
@@ -762,6 +768,7 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
}
@Override
@Nullable
public Object getProperty(String property) {
if (property.equals("beanName")) {
return getBeanName();
@@ -769,13 +776,10 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
else if (property.equals("source")) {
return getSource();
}
else if (this.beanDefinition != null) {
else {
return new GroovyPropertyValue(
property, this.beanDefinition.getBeanDefinition().getPropertyValues().get(property));
}
else {
return this.metaClass.getProperty(this, property);
}
}
@Override
@@ -804,9 +808,10 @@ public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader imp
private final String propertyName;
@Nullable
private final Object propertyValue;
public GroovyPropertyValue(String propertyName, Object propertyValue) {
public GroovyPropertyValue(String propertyName, @Nullable Object propertyValue) {
this.propertyName = propertyName;
this.propertyValue = propertyValue;
}
@@ -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.
@@ -84,7 +84,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport {
this(beanName, clazz, null);
}
GroovyBeanDefinitionWrapper(@Nullable String beanName, Class<?> clazz, @Nullable Collection<?> constructorArgs) {
GroovyBeanDefinitionWrapper(@Nullable String beanName, @Nullable Class<?> clazz, @Nullable Collection<?> constructorArgs) {
this.beanName = beanName;
this.clazz = clazz;
this.constructorArgs = constructorArgs;
@@ -130,11 +130,12 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport {
}
BeanDefinitionHolder getBeanDefinitionHolder() {
return new BeanDefinitionHolder(getBeanDefinition(), getBeanName());
Assert.state(this.beanName != null, "Bean name must be set");
return new BeanDefinitionHolder(getBeanDefinition(), this.beanName);
}
void setParent(Object obj) {
Assert.notNull(obj, "Parent bean cannot be set to a null runtime bean reference.");
void setParent(@Nullable Object obj) {
Assert.notNull(obj, "Parent bean cannot be set to a null runtime bean reference");
if (obj instanceof String name) {
this.parentName = name;
}
@@ -148,7 +149,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport {
getBeanDefinition().setAbstract(false);
}
GroovyBeanDefinitionWrapper addProperty(String propertyName, Object propertyValue) {
GroovyBeanDefinitionWrapper addProperty(String propertyName, @Nullable Object propertyValue) {
if (propertyValue instanceof GroovyBeanDefinitionWrapper wrapper) {
propertyValue = wrapper.getBeanDefinition();
}
@@ -158,6 +159,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport {
@Override
@Nullable
public Object getProperty(String property) {
Assert.state(this.definitionWrapper != null, "BeanDefinition wrapper not initialized");
if (this.definitionWrapper.isReadableProperty(property)) {
@@ -170,7 +172,7 @@ class GroovyBeanDefinitionWrapper extends GroovyObjectSupport {
}
@Override
public void setProperty(String property, Object newValue) {
public void setProperty(String property, @Nullable Object newValue) {
if (PARENT.equals(property)) {
setParent(newValue);
}
@@ -525,42 +525,73 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
// Check manually registered singletons.
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
// Determine target for FactoryBean match if necessary.
if (beanInstance instanceof FactoryBean<?> factoryBean) {
if (!isFactoryDereference) {
Class<?> type = getTypeForFactoryBean(factoryBean);
return (type != null && typeToMatch.isAssignableFrom(type));
}
else {
return typeToMatch.isInstance(beanInstance);
}
}
else if (!isFactoryDereference) {
if (typeToMatch.isInstance(beanInstance)) {
// Direct match for exposed instance?
return true;
}
else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {
// Generics potentially only match on the target class, not on the proxy...
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
Class<?> targetType = mbd.getTargetType();
if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) {
// Check raw class match as well, making sure it's exposed on the proxy.
Class<?> classToMatch = typeToMatch.resolve();
if (classToMatch != null && !classToMatch.isInstance(beanInstance)) {
if (type == null) {
return false;
}
if (typeToMatch.isAssignableFrom(type)) {
return true;
}
else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
ResolvableType targetType = mbd.targetType;
if (targetType == null) {
targetType = mbd.factoryMethodReturnType;
}
if (targetType == null) {
return false;
}
if (typeToMatch.isAssignableFrom(targetType)) {
return true;
Class<?> targetClass = targetType.resolve();
if (targetClass != null && FactoryBean.class.isAssignableFrom(targetClass)) {
Class<?> classToMatch = typeToMatch.resolve();
if (classToMatch != null && !FactoryBean.class.isAssignableFrom(classToMatch) &&
!classToMatch.isAssignableFrom(targetType.toClass())) {
return typeToMatch.isAssignableFrom(targetType.getGeneric());
}
}
else {
return typeToMatch.isAssignableFrom(targetType);
}
}
ResolvableType resolvableType = mbd.targetType;
if (resolvableType == null) {
resolvableType = mbd.factoryMethodReturnType;
}
return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType));
return false;
}
}
return false;
else if (isFactoryDereference) {
return false;
}
// Actual matching against bean instance...
if (typeToMatch.isInstance(beanInstance)) {
// Direct match for exposed instance?
return true;
}
else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) {
// Generics potentially only match on the target class, not on the proxy...
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
Class<?> targetType = mbd.getTargetType();
if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) {
// Check raw class match as well, making sure it's exposed on the proxy.
Class<?> classToMatch = typeToMatch.resolve();
if (classToMatch != null && !classToMatch.isInstance(beanInstance)) {
return false;
}
if (typeToMatch.isAssignableFrom(targetType)) {
return true;
}
}
ResolvableType resolvableType = mbd.targetType;
if (resolvableType == null) {
resolvableType = mbd.factoryMethodReturnType;
}
return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType));
}
else {
return false;
}
}
else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
// null instance registered
@@ -241,6 +241,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
}
@Override
@Nullable
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
@@ -1434,6 +1434,7 @@ class ConstructorResolver {
}
@Override
@Nullable
public Object resolveShortcut(BeanFactory beanFactory) {
String shortcut = this.shortcut;
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
@@ -342,7 +342,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
void logDestroyMethodException(Method destroyMethod, Throwable ex) {
void logDestroyMethodException(Method destroyMethod, @Nullable Throwable ex) {
if (logger.isWarnEnabled()) {
String msg = "Custom destroy method '" + destroyMethod.getName() + "' on bean with name '" +
this.beanName + "' propagated an 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.
@@ -102,22 +102,6 @@ public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCan
}
}
}
else {
// Pre-existing target type: In case of a generic FactoryBean type,
// unwrap nested generic type when matching a non-FactoryBean type.
Class<?> resolvedClass = targetType.resolve();
if (resolvedClass != null && FactoryBean.class.isAssignableFrom(resolvedClass)) {
Class<?> typeToBeMatched = dependencyType.resolve();
if (typeToBeMatched != null && !FactoryBean.class.isAssignableFrom(typeToBeMatched)) {
targetType = targetType.getGeneric();
if (descriptor.fallbackMatchAllowed()) {
// Matching the Class-based type determination for FactoryBean
// objects in the lazy-determination getType code path below.
targetType = ResolvableType.forClass(targetType.resolve());
}
}
}
}
}
if (targetType == null) {
@@ -144,6 +128,23 @@ public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCan
if (cacheType) {
rbd.targetType = targetType;
}
// Pre-declared target type: In case of a generic FactoryBean type,
// unwrap nested generic type when matching a non-FactoryBean type.
Class<?> targetClass = targetType.resolve();
if (targetClass != null && FactoryBean.class.isAssignableFrom(targetClass)) {
Class<?> classToMatch = dependencyType.resolve();
if (classToMatch != null && !FactoryBean.class.isAssignableFrom(classToMatch) &&
!classToMatch.isAssignableFrom(targetClass)) {
targetType = targetType.getGeneric();
if (descriptor.fallbackMatchAllowed()) {
// Matching the Class-based type determination for FactoryBean
// objects in the lazy-determination getType code path above.
targetType = ResolvableType.forClass(targetType.resolve());
}
}
}
if (descriptor.fallbackMatchAllowed() &&
(targetType.hasUnresolvableGenerics() || targetType.resolve() == Properties.class)) {
// Fallback matches allow unresolvable generics, e.g. plain HashMap to Map<String,String>;
@@ -83,6 +83,7 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
return after.applyWithException(registeredBean, InstanceSupplier.this.get(registeredBean));
}
@Override
@Nullable
public Method getFactoryMethod() {
return InstanceSupplier.this.getFactoryMethod();
}
@@ -126,6 +127,7 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
return supplier.getWithException();
}
@Override
@Nullable
public Method getFactoryMethod() {
return factoryMethod;
}
@@ -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.
@@ -277,6 +277,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
@Override
@Nullable
public String getParentName() {
return null;
}
@@ -638,7 +639,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
}
}
private static boolean hasAnyExternallyManagedMethod(Set<String> candidates, String methodName) {
private static boolean hasAnyExternallyManagedMethod(@Nullable Set<String> candidates, String methodName) {
if (candidates != null) {
for (String candidate : candidates) {
int indexOfDot = candidate.lastIndexOf('.');
@@ -232,11 +232,13 @@ public class StaticListableBeanFactory implements ListableBeanFactory {
}
@Override
@Nullable
public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
return getType(name, true);
}
@Override
@Nullable
public Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException {
String beanName = BeanFactoryUtils.transformedBeanName(name);
@@ -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.
@@ -126,9 +126,10 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
BeanDefinitionParserDelegate current = createDelegate(getReaderContext(), root, parent);
this.delegate = current;
if (this.delegate.isDefaultNamespace(root)) {
if (current.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
@@ -146,7 +147,7 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
parseBeanDefinitions(root, current);
postProcessXml(root);
this.delegate = parent;
@@ -29,9 +29,12 @@ import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.TypedStringValue;
@@ -63,27 +66,25 @@ class BeanFactoryGenericsTests {
@Test
void genericSetProperty() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.getPropertyValues().add("integerSet", Set.of("4", "5"));
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.getPropertyValues().add("integerSet", Set.of("4", "5"));
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
}
@Test
void genericListProperty() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
List<String> input = List.of("http://localhost:8080", "http://localhost:9090");
rbd.getPropertyValues().add("resourceList", input);
bd.getPropertyValues().add("resourceList", input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getResourceList())
.containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090"));
}
@@ -94,11 +95,11 @@ class BeanFactoryGenericsTests {
bf.registerSingleton("resource1", new UrlResource("http://localhost:8080"));
bf.registerSingleton("resource2", new UrlResource("http://localhost:9090"));
RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class);
rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
bf.registerBeanDefinition("genericBean", rbd);
GenericIntegerBean gb = (GenericIntegerBean) bf.getBean("genericBean");
RootBeanDefinition bd = new RootBeanDefinition(GenericIntegerBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
bf.registerBeanDefinition("genericBean", bd);
GenericIntegerBean gb = (GenericIntegerBean) bf.getBean("genericBean");
assertThat(gb.getResourceList())
.containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090"));
}
@@ -106,43 +107,42 @@ class BeanFactoryGenericsTests {
@Test
void genericListPropertyWithInvalidElementType() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class);
rbd.getPropertyValues().add("testBeanList", List.of(1));
RootBeanDefinition bd = new RootBeanDefinition(GenericIntegerBean.class);
bd.getPropertyValues().add("testBeanList", List.of(1));
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> bf.getBean("genericBean"))
.withMessageContaining("genericBean")
.withMessageContaining("testBeanList[0]")
.withMessageContaining(TestBean.class.getName())
.withMessageContaining("Integer");
.withMessageContaining("genericBean")
.withMessageContaining("testBeanList[0]")
.withMessageContaining(TestBean.class.getName())
.withMessageContaining("Integer");
}
@Test
void genericListPropertyWithOptionalAutowiring() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
bf.registerBeanDefinition("genericBean", bd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getResourceList()).isNull();
}
@Test
void genericMapProperty() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<String, String> input = Map.of(
"4", "5",
"6", "7");
rbd.getPropertyValues().add("shortMap", input);
bd.getPropertyValues().add("shortMap", input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5);
assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7);
}
@@ -152,22 +152,21 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
GenericBean<?> gb = (GenericBean<?>) bf.getBean("listOfArrays");
GenericBean<?> gb = (GenericBean<?>) bf.getBean("listOfArrays");
assertThat(gb.getListOfArrays()).containsExactly(new String[] {"value1", "value2"});
}
@Test
void genericSetConstructor() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Set<String> input = Set.of("4", "5");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
}
@@ -177,11 +176,11 @@ class BeanFactoryGenericsTests {
bf.registerSingleton("integer1", 4);
bf.registerSingleton("integer2", 5);
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", bd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
}
@@ -189,27 +188,26 @@ class BeanFactoryGenericsTests {
void genericSetConstructorWithOptionalAutowiring() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", bd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).isNull();
}
@Test
void genericSetListConstructor() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Set<String> input1 = Set.of("4", "5");
List<String> input2 = List.of("http://localhost:8080", "http://localhost:9090");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input1);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bd.getConstructorArgumentValues().addGenericArgumentValue(input1);
bd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
assertThat(gb.getResourceList())
.containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090"));
@@ -223,11 +221,11 @@ class BeanFactoryGenericsTests {
bf.registerSingleton("resource1", new UrlResource("http://localhost:8080"));
bf.registerSingleton("resource2", new UrlResource("http://localhost:9090"));
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", bd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
assertThat(gb.getResourceList())
.containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090"));
@@ -239,11 +237,11 @@ class BeanFactoryGenericsTests {
bf.registerSingleton("resource1", new UrlResource("http://localhost:8080"));
bf.registerSingleton("resource2", new UrlResource("http://localhost:9090"));
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("genericBean", bd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).isNull();
assertThat(gb.getResourceList()).isNull();
}
@@ -251,18 +249,17 @@ class BeanFactoryGenericsTests {
@Test
void genericSetMapConstructor() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Set<String> input1 = Set.of("4", "5");
Map<String, String> input2 = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input1);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bd.getConstructorArgumentValues().addGenericArgumentValue(input1);
bd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5);
assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7);
@@ -271,17 +268,16 @@ class BeanFactoryGenericsTests {
@Test
void genericMapResourceConstructor() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<String, String> input = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
rbd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080");
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080");
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5);
assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7);
assertThat(gb.getResourceList()).containsExactly(new UrlResource("http://localhost:8080"));
@@ -290,20 +286,19 @@ class BeanFactoryGenericsTests {
@Test
void genericMapMapConstructor() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<String, String> input1 = Map.of(
"1", "0",
"2", "3");
Map<String, String> input2 = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input1);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bd.getConstructorArgumentValues().addGenericArgumentValue(input1);
bd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getShortMap()).isNotSameAs(gb.getPlainMap());
assertThat(gb.getPlainMap()).hasSize(2);
assertThat(gb.getPlainMap().get("1")).isEqualTo("0");
@@ -316,17 +311,16 @@ class BeanFactoryGenericsTests {
@Test
void genericMapMapConstructorWithSameRefAndConversion() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<String, String> input = Map.of(
"1", "0",
"2", "3");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getShortMap()).isNotSameAs(gb.getPlainMap());
assertThat(gb.getPlainMap()).hasSize(2);
assertThat(gb.getPlainMap().get("1")).isEqualTo("0");
@@ -339,17 +333,16 @@ class BeanFactoryGenericsTests {
@Test
void genericMapMapConstructorWithSameRefAndNoConversion() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<Short, Integer> input = new HashMap<>();
input.put((short) 1, 0);
input.put((short) 2, 3);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getShortMap()).isSameAs(gb.getPlainMap());
assertThat(gb.getShortMap()).hasSize(2);
assertThat(gb.getShortMap().get(Short.valueOf("1"))).isEqualTo(0);
@@ -359,16 +352,15 @@ class BeanFactoryGenericsTests {
@Test
void genericMapWithKeyTypeConstructor() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<String, String> input = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getLongMap().get(4L)).isEqualTo("5");
assertThat(gb.getLongMap().get(6L)).isEqualTo("7");
}
@@ -376,18 +368,18 @@ class BeanFactoryGenericsTests {
@Test
void genericMapWithCollectionValueConstructor() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)));
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
bf.addPropertyEditorRegistrar(registry ->
registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)));
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
Map<String, Collection<?>> input = Map.of(
"1", Set.of(1),
"2", List.of(Boolean.TRUE));
rbd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getCollectionMap().get(1)).isInstanceOf(Set.class);
assertThat(gb.getCollectionMap().get(2)).isInstanceOf(List.class);
}
@@ -395,32 +387,30 @@ class BeanFactoryGenericsTests {
@Test
void genericSetFactoryMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Set<String> input = Set.of("4", "5");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
}
@Test
void genericSetListFactoryMethod() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Set<String> input1 = Set.of("4", "5");
List<String> input2 = List.of("http://localhost:8080", "http://localhost:9090");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input1);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bd.getConstructorArgumentValues().addGenericArgumentValue(input1);
bd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
assertThat(gb.getResourceList())
.containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090"));
@@ -429,19 +419,18 @@ class BeanFactoryGenericsTests {
@Test
void genericSetMapFactoryMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Set<String> input1 = Set.of("4", "5");
Map<String, String> input2 = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input1);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bd.getConstructorArgumentValues().addGenericArgumentValue(input1);
bd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5);
assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5);
assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7);
@@ -450,18 +439,17 @@ class BeanFactoryGenericsTests {
@Test
void genericMapResourceFactoryMethod() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Map<String, String> input = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
rbd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080");
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080");
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5);
assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7);
assertThat(gb.getResourceList()).containsExactly(new UrlResource("http://localhost:8080"));
@@ -470,21 +458,20 @@ class BeanFactoryGenericsTests {
@Test
void genericMapMapFactoryMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Map<String, String> input1 = Map.of(
"1", "0",
"2", "3");
Map<String, String> input2 = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input1);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bd.getConstructorArgumentValues().addGenericArgumentValue(input1);
bd.getConstructorArgumentValues().addGenericArgumentValue(input2);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getPlainMap().get("1")).isEqualTo("0");
assertThat(gb.getPlainMap().get("2")).isEqualTo("3");
assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5);
@@ -494,17 +481,16 @@ class BeanFactoryGenericsTests {
@Test
void genericMapWithKeyTypeFactoryMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Map<String, String> input = Map.of(
"4", "5",
"6", "7");
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getLongMap().get(Long.valueOf("4"))).isEqualTo("5");
assertThat(gb.getLongMap().get(Long.valueOf("6"))).isEqualTo("7");
}
@@ -512,19 +498,19 @@ class BeanFactoryGenericsTests {
@Test
void genericMapWithCollectionValueFactoryMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)));
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
rbd.setFactoryMethodName("createInstance");
bf.addPropertyEditorRegistrar(registry ->
registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)));
RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class);
bd.setFactoryMethodName("createInstance");
Map<String, Collection<?>> input = Map.of(
"1", Set.of(1),
"2", List.of(Boolean.TRUE));
rbd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE);
rbd.getConstructorArgumentValues().addGenericArgumentValue(input);
bd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE);
bd.getConstructorArgumentValues().addGenericArgumentValue(input);
bf.registerBeanDefinition("genericBean", bd);
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
assertThat(gb.getCollectionMap().get(1)).isInstanceOf(Set.class);
assertThat(gb.getCollectionMap().get(2)).isInstanceOf(List.class);
}
@@ -534,6 +520,7 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
NamedUrlList list = bf.getBean("list", NamedUrlList.class);
assertThat(list).containsExactly(new URL("http://localhost:8080"));
}
@@ -543,6 +530,7 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
NamedUrlSet set = bf.getBean("set", NamedUrlSet.class);
assertThat(set).containsExactly(new URL("http://localhost:8080"));
}
@@ -552,6 +540,7 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
NamedUrlMap map = bf.getBean("map", NamedUrlMap.class);
assertThat(map).containsExactly(entry(10, new URL("http://localhost:8080")));
}
@@ -561,6 +550,7 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
GenericIntegerBean gb = (GenericIntegerBean) bf.getBean("integerBean");
assertThat(gb.getGenericProperty()).isEqualTo(10);
assertThat(gb.getGenericListProperty()).containsExactly(20, 30);
@@ -571,6 +561,7 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
GenericSetOfIntegerBean gb = (GenericSetOfIntegerBean) bf.getBean("setOfIntegerBean");
assertThat(gb.getGenericProperty()).singleElement().isEqualTo(10);
assertThat(gb.getGenericListProperty()).satisfiesExactly(
@@ -583,6 +574,7 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
new ClassPathResource("genericBeanTests.xml", getClass()));
UrlSet urlSet = bf.getBean("setBean", UrlSet.class);
assertThat(urlSet).containsExactly(new URL("https://www.springframework.org"));
}
@@ -599,27 +591,27 @@ class BeanFactoryGenericsTests {
*/
@Test
void parameterizedStaticFactoryMethod() {
RootBeanDefinition rbd = new RootBeanDefinition(getClass());
rbd.setFactoryMethodName("createMockitoMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
RootBeanDefinition bd = new RootBeanDefinition(getClass());
bd.setFactoryMethodName("createMockitoMock");
bd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
assertRunnableMockFactory(rbd);
assertRunnableMockFactory(bd);
}
@Test
void parameterizedStaticFactoryMethodWithWrappedClassName() {
RootBeanDefinition rbd = new RootBeanDefinition();
rbd.setBeanClassName(getClass().getName());
rbd.setFactoryMethodName("createMockitoMock");
RootBeanDefinition bd = new RootBeanDefinition();
bd.setBeanClassName(getClass().getName());
bd.setFactoryMethodName("createMockitoMock");
// TypedStringValue is used as an equivalent to an XML-defined argument String
rbd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName()));
bd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName()));
assertRunnableMockFactory(rbd);
assertRunnableMockFactory(bd);
}
private void assertRunnableMockFactory(RootBeanDefinition rbd) {
private void assertRunnableMockFactory(RootBeanDefinition bd) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("mock", rbd);
bf.registerBeanDefinition("mock", bd);
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
assertThat(bf.getType("mock")).isEqualTo(Runnable.class);
@@ -642,14 +634,14 @@ class BeanFactoryGenericsTests {
void parameterizedInstanceFactoryMethod() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", rbd);
RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", bd1);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
bf.registerBeanDefinition("mock", rbd);
RootBeanDefinition bd2 = new RootBeanDefinition();
bd2.setFactoryBeanName("mocksControl");
bd2.setFactoryMethodName("createMock");
bd2.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
bf.registerBeanDefinition("mock", bd2);
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
@@ -663,14 +655,14 @@ class BeanFactoryGenericsTests {
void parameterizedInstanceFactoryMethodWithNonResolvedClassName() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", rbd);
RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", bd1);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName());
bf.registerBeanDefinition("mock", rbd);
RootBeanDefinition bd2 = new RootBeanDefinition();
bd2.setFactoryBeanName("mocksControl");
bd2.setFactoryMethodName("createMock");
bd2.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName());
bf.registerBeanDefinition("mock", bd2);
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
@@ -684,14 +676,14 @@ class BeanFactoryGenericsTests {
void parameterizedInstanceFactoryMethodWithInvalidClassName() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", rbd);
RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", bd1);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue("x");
bf.registerBeanDefinition("mock", rbd);
RootBeanDefinition rbd2 = new RootBeanDefinition();
rbd2.setFactoryBeanName("mocksControl");
rbd2.setFactoryMethodName("createMock");
rbd2.getConstructorArgumentValues().addGenericArgumentValue("x");
bf.registerBeanDefinition("mock", rbd2);
assertThat(bf.isTypeMatch("mock", Runnable.class)).isFalse();
assertThat(bf.isTypeMatch("mock", Runnable.class)).isFalse();
@@ -705,14 +697,14 @@ class BeanFactoryGenericsTests {
void parameterizedInstanceFactoryMethodWithIndexedArgument() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", rbd);
RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", bd1);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class);
bf.registerBeanDefinition("mock", rbd);
RootBeanDefinition bd2 = new RootBeanDefinition();
bd2.setFactoryBeanName("mocksControl");
bd2.setFactoryMethodName("createMock");
bd2.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class);
bf.registerBeanDefinition("mock", bd2);
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
@@ -727,14 +719,14 @@ class BeanFactoryGenericsTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setTempClassLoader(new OverridingClassLoader(getClass().getClassLoader()));
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", rbd);
RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", bd1);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
bf.registerBeanDefinition("mock", rbd);
RootBeanDefinition bd2 = new RootBeanDefinition();
bd2.setFactoryBeanName("mocksControl");
bd2.setFactoryMethodName("createMock");
bd2.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
bf.registerBeanDefinition("mock", bd2);
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue();
@@ -766,25 +758,31 @@ class BeanFactoryGenericsTests {
assertThat(floatStoreNames).isEmpty();
}
@Test
void genericMatchingWithFullTypeDifferentiation() {
@ParameterizedTest
@ValueSource(classes = {NumberStoreFactory.class, NumberStoreFactoryBeans.class})
void genericMatchingWithFullTypeDifferentiation(Class<?> factoryClass) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
RootBeanDefinition bd1 = new RootBeanDefinition(NumberStoreFactory.class);
RootBeanDefinition bd1 = new RootBeanDefinition(factoryClass);
bd1.setFactoryMethodName("newDoubleStore");
bf.registerBeanDefinition("store1", bd1);
RootBeanDefinition bd2 = new RootBeanDefinition(NumberStoreFactory.class);
RootBeanDefinition bd2 = new RootBeanDefinition(factoryClass);
bd2.setFactoryMethodName("newFloatStore");
bf.registerBeanDefinition("store2", bd2);
bf.registerBeanDefinition("numberBean",
new RootBeanDefinition(NumberBean.class, RootBeanDefinition.AUTOWIRE_CONSTRUCTOR, false));
RootBeanDefinition bd3 = new RootBeanDefinition(NumberBean.class);
bd3.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bd3.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
bf.registerBeanDefinition("numberBean", bd3);
NumberBean nb = bf.getBean(NumberBean.class);
NumberStore<?> store1 = bf.getBean("store1", NumberStore.class);
assertThat(nb.getDoubleStore()).isSameAs(store1);
NumberStore<?> store2 = bf.getBean("store2", NumberStore.class);
NumberBean nb = bf.getBean(NumberBean.class);
assertThat(nb.getDoubleStore()).isSameAs(store1);
assertThat(nb.getFloatStore()).isSameAs(store2);
nb = bf.getBean(NumberBean.class);
assertThat(nb.getDoubleStore()).isSameAs(store1);
assertThat(nb.getFloatStore()).isSameAs(store2);
String[] numberStoreNames = bf.getBeanNamesForType(ResolvableType.forClass(NumberStore.class));
@@ -832,16 +830,17 @@ class BeanFactoryGenericsTests {
assertThat(floatStoreProvider.orderedStream()).singleElement().isEqualTo(store2);
}
@Test
void genericMatchingWithUnresolvedOrderedStream() {
@ParameterizedTest
@ValueSource(classes = {NumberStoreFactory.class, NumberStoreFactoryBeans.class})
void genericMatchingWithUnresolvedOrderedStream(Class<?> factoryClass) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
RootBeanDefinition bd1 = new RootBeanDefinition(NumberStoreFactory.class);
RootBeanDefinition bd1 = new RootBeanDefinition(factoryClass);
bd1.setFactoryMethodName("newDoubleStore");
bf.registerBeanDefinition("store1", bd1);
RootBeanDefinition bd2 = new RootBeanDefinition(NumberStoreFactory.class);
RootBeanDefinition bd2 = new RootBeanDefinition(factoryClass);
bd2.setFactoryMethodName("newFloatStore");
bf.registerBeanDefinition("store2", bd2);
@@ -850,6 +849,43 @@ class BeanFactoryGenericsTests {
bf.getBean("store2", NumberStore.class), bf.getBean("store1", NumberStore.class));
}
@Test // gh-32489
void genericMatchingAgainstFactoryBeanClass() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
RootBeanDefinition bd = new RootBeanDefinition(MyFactoryBean.class);
bd.setTargetType(ResolvableType.forClassWithGenerics(MyFactoryBean.class, String.class));
bf.registerBeanDefinition("myFactoryBean", bd);
bf.registerBeanDefinition("myFactoryBeanHolder",
new RootBeanDefinition(MyFactoryBeanHolder.class, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, false));
assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).containsOnly(bf.getBean(MyFactoryBean.class));
assertThat(bf.getBeanProvider(MyGenericInterfaceForFactoryBeans.class)).containsOnly(bf.getBean(MyFactoryBean.class));
assertThat(bf.getBeanProvider(bd.getResolvableType())).containsOnly(bf.getBean(MyFactoryBean.class));
}
@Test // gh-32489
void genericMatchingAgainstLazyFactoryBeanClass() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
RootBeanDefinition bd = new RootBeanDefinition(MyFactoryBean.class);
// Replicate org.springframework.data.repository.config.RepositoryConfigurationDelegate#registerRepositoriesIn
// behavior of setting targetType, required to hit other branch in
// org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver.checkGenericTypeMatch
bd.setTargetType(ResolvableType.forClassWithGenerics(MyFactoryBean.class, String.class));
bd.setLazyInit(true);
bf.registerBeanDefinition("myFactoryBean", bd);
bf.registerBeanDefinition("myFactoryBeanHolder",
new RootBeanDefinition(MyFactoryBeanHolder.class, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, false));
assertThat(bf.getBeanProvider(bd.getResolvableType())).containsOnly(bf.getBean(MyFactoryBean.class));
assertThat(bf.getBeanProvider(MyGenericInterfaceForFactoryBeans.class)).containsOnly(bf.getBean(MyFactoryBean.class));
assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).containsOnly(bf.getBean(MyFactoryBean.class));
assertThat(bf.getBeanProvider(bd.getResolvableType())).containsOnly(bf.getBean(MyFactoryBean.class));
}
/**
* Mimics and delegates to {@link Mockito#mock(Class)} -- created here to avoid factory
@@ -966,4 +1002,64 @@ class BeanFactoryGenericsTests {
}
}
public static class NumberStoreFactoryBeans {
@Order(1)
public static FactoryBean<NumberStore<Double>> newDoubleStore() {
return new FactoryBean<>() {
@Override
public NumberStore<Double> getObject() {
return new DoubleStore();
}
@Override
public Class<?> getObjectType() {
return DoubleStore.class;
}
};
}
@Order(0)
public static FactoryBean<NumberStore<Float>> newFloatStore() {
return new FactoryBean<>() {
@Override
public NumberStore<Float> getObject() {
return new FloatStore();
}
@Override
public Class<?> getObjectType() {
return FloatStore.class;
}
};
}
}
public interface MyGenericInterfaceForFactoryBeans<T> {
}
public static class MyFactoryBean<T extends CharSequence> implements FactoryBean<T>, MyGenericInterfaceForFactoryBeans<T> {
@Override
public T getObject() {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getObjectType() {
return String.class;
}
}
public static class MyFactoryBeanHolder {
List<MyGenericInterfaceForFactoryBeans<?>> factoryBeans; // Requested type is not a FactoryBean type
public MyFactoryBeanHolder(List<MyGenericInterfaceForFactoryBeans<?>> factoryBeans) {
this.factoryBeans = factoryBeans;
}
}
}
@@ -121,6 +121,7 @@ public class JCacheCacheManager extends AbstractTransactionSupportingCacheManage
}
@Override
@Nullable
protected Cache getMissingCache(String name) {
CacheManager cacheManager = getCacheManager();
Assert.state(cacheManager != null, "No CacheManager set");
@@ -46,6 +46,7 @@ import org.springframework.util.StringUtils;
public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJCacheOperationSource {
@Override
@Nullable
protected JCacheOperation<?> findCacheOperation(Method method, @Nullable Class<?> targetType) {
CacheResult cacheResult = method.getAnnotation(CacheResult.class);
CachePut cachePut = method.getAnnotation(CachePut.class);
@@ -23,6 +23,7 @@ import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.lang.Nullable;
/**
* Intercept methods annotated with {@link CachePut}.
@@ -39,6 +40,7 @@ class CachePutInterceptor extends AbstractKeyCacheInterceptor<CachePutOperation,
@Override
@Nullable
protected Object invoke(
CacheOperationInvocationContext<CachePutOperation> context, CacheOperationInvoker invoker) {
@@ -22,6 +22,7 @@ import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.lang.Nullable;
/**
* Intercept methods annotated with {@link CacheRemoveAll}.
@@ -38,6 +39,7 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor<CacheRemoveAllO
@Override
@Nullable
protected Object invoke(
CacheOperationInvocationContext<CacheRemoveAllOperation> context, CacheOperationInvoker invoker) {
@@ -22,6 +22,7 @@ import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.lang.Nullable;
/**
* Intercept methods annotated with {@link CacheRemove}.
@@ -38,6 +39,7 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemov
@Override
@Nullable
protected Object invoke(
CacheOperationInvocationContext<CacheRemoveOperation> context, CacheOperationInvoker invoker) {
@@ -180,6 +180,7 @@ public class JCacheAspectSupport extends AbstractCacheInvoker implements Initial
}
@Override
@Nullable
public Object invoke() throws ThrowableWrapper {
return invokeOperation(this.delegate);
}
@@ -24,6 +24,7 @@ import org.springframework.cache.interceptor.AbstractCacheResolver;
import org.springframework.cache.interceptor.BasicOperation;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.lang.Nullable;
/**
* A simple {@link CacheResolver} that resolves the exception cache
@@ -41,6 +42,7 @@ public class SimpleExceptionCacheResolver extends AbstractCacheResolver {
}
@Override
@Nullable
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
BasicOperation operation = context.getOperation();
if (!(operation instanceof CacheResultOperation cacheResultOperation)) {
@@ -83,6 +83,7 @@ public class TransactionAwareCacheDecorator implements Cache {
}
@Override
@Nullable
public <T> T get(Object key, @Nullable Class<T> type) {
return this.targetCache.get(key, type);
}
@@ -199,6 +199,7 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
* Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature.
*/
@Override
@Nullable
public Class<?> getTargetClass() {
Class<?> targetClass = super.getTargetClass();
if (targetClass == null && this.targetBeanName != null) {
@@ -212,6 +213,7 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
* Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature.
*/
@Override
@Nullable
public Object getTargetObject() {
Object targetObject = super.getTargetObject();
if (targetObject == null && this.targetBeanName != null) {
@@ -22,6 +22,7 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
@@ -40,7 +41,7 @@ class SchedulerFactoryBeanRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
if (!ClassUtils.isPresent(SCHEDULER_FACTORY_CLASS_NAME, classLoader)) {
return;
}
@@ -315,7 +315,7 @@ public interface Cache {
@Nullable
private final Object key;
public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {
public ValueRetrievalException(@Nullable Object key, Callable<?> loader, @Nullable Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
@@ -223,6 +223,7 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
}
@Override
@Nullable
protected Object fromStoreValue(@Nullable Object storeValue) {
if (storeValue != null && this.serialization != null) {
try {
@@ -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.
@@ -1096,7 +1096,13 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
return Mono.fromFuture(cache.retrieve(key, () -> ((Mono<?>) invokeOperation(invoker)).toFuture()));
return Mono.fromFuture(cache.retrieve(key, () -> {
Mono<?> mono = ((Mono<?>) invokeOperation(invoker));
if (mono == null) {
mono = Mono.empty();
}
return mono.toFuture();
}));
}
return NOT_HANDLED;
}
@@ -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.
@@ -78,12 +78,13 @@ public class CacheInterceptor extends CacheAspectSupport implements MethodInterc
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
*/
private static class KotlinDelegate {
public static Publisher<?> invokeSuspendingFunction(Method method, Object target, Object... args) {
public static Publisher<?> invokeSuspendingFunction(Method method, @Nullable Object target, Object... args) {
Continuation<?> continuation = (Continuation<?>) args[args.length - 1];
CoroutineContext coroutineContext = continuation.getContext().minusKey(Job.Key);
return CoroutinesUtils.invokeSuspendingFunction(coroutineContext, method, target, args);
@@ -52,6 +52,7 @@ public class NamedCacheResolver extends AbstractCacheResolver {
}
@Override
@Nullable
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
return this.cacheNames;
}
@@ -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.
@@ -100,7 +100,6 @@ public class AnnotatedBeanDefinitionReader {
* Set the {@code Environment} to use when evaluating whether
* {@link Conditional @Conditional}-annotated component classes should be registered.
* <p>The default is a {@link StandardEnvironment}.
* @see #registerBean(Class, String, Class...)
*/
public void setEnvironment(Environment environment) {
this.conditionEvaluator = new ConditionEvaluator(this.registry, environment, null);
@@ -314,6 +314,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
@Override
@Nullable
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
BeanRegistrationAotContribution parentAotContribution = super.processAheadOfTime(registeredBean);
Class<?> beanClass = registeredBean.getBeanClass();
@@ -350,6 +351,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
@Override
@Nullable
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
return null;
}
@@ -334,6 +334,7 @@ class ConfigurationClassEnhancer {
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
@Nullable
private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
ConfigurableBeanFactory beanFactory, String beanName) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -257,6 +257,7 @@ public abstract class AbstractAotProcessor<T> {
* @return this builder for method chaining
*/
public Builder groupId(String groupId) {
Assert.hasText(groupId, "'groupId' must not be empty");
this.groupId = groupId;
return this;
}
@@ -268,6 +269,7 @@ public abstract class AbstractAotProcessor<T> {
* @return this builder for method chaining
*/
public Builder artifactId(String artifactId) {
Assert.hasText(artifactId, "'artifactId' must not be empty");
this.artifactId = artifactId;
return this;
}
@@ -279,14 +281,12 @@ public abstract class AbstractAotProcessor<T> {
Assert.notNull(this.sourceOutput, "'sourceOutput' must not be null");
Assert.notNull(this.resourceOutput, "'resourceOutput' must not be null");
Assert.notNull(this.classOutput, "'classOutput' must not be null");
Assert.hasText(this.groupId, "'groupId' must not be null or empty");
Assert.hasText(this.artifactId, "'artifactId' must not be null or empty");
Assert.notNull(this.groupId, "'groupId' must not be null");
Assert.notNull(this.artifactId, "'artifactId' must not be null");
return new Settings(this.sourceOutput, this.resourceOutput, this.classOutput,
this.groupId, this.artifactId);
}
}
}
}
@@ -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,8 +35,8 @@ import org.springframework.lang.Nullable;
*/
class KotlinReflectionBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
@Nullable
@Override
@Nullable
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
if (KotlinDetector.isKotlinType(beanClass)) {
@@ -45,6 +45,7 @@ class KotlinReflectionBeanRegistrationAotProcessor implements BeanRegistrationAo
return null;
}
private static class AotContribution implements BeanRegistrationAotContribution {
private final Class<?> beanClass;
@@ -66,6 +67,10 @@ class KotlinReflectionBeanRegistrationAotProcessor implements BeanRegistrationAo
if (superClass != null) {
registerHints(superClass, runtimeHints);
}
Class<?> enclosingClass = type.getEnclosingClass();
if (enclosingClass != null) {
runtimeHints.reflection().registerType(enclosingClass);
}
}
}
@@ -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.
@@ -318,8 +318,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
}
}
private void publishEvents(Object result) {
if (result.getClass().isArray()) {
private void publishEvents(@Nullable Object result) {
if (result != null && result.getClass().isArray()) {
Object[] events = ObjectUtils.toObjectArray(result);
for (Object event : events) {
publishEvent(event);
@@ -350,7 +350,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
* Invoke the event listener method with the given argument values.
*/
@Nullable
protected Object doInvoke(Object... args) {
protected Object doInvoke(@Nullable Object... args) {
Object bean = getTargetBean();
// Detect package-protected NullBean instance through equals(null) check
if (bean.equals(null)) {
@@ -416,8 +416,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
* the given error message.
* @param message error message to append the HandlerMethod details to
*/
protected String getDetailedErrorMessage(Object bean, String message) {
StringBuilder sb = new StringBuilder(message).append('\n');
protected String getDetailedErrorMessage(Object bean, @Nullable String message) {
StringBuilder sb = (StringUtils.hasLength(message) ? new StringBuilder(message).append('\n') : new StringBuilder());
sb.append("HandlerMethod details: \n");
sb.append("Bean [").append(bean.getClass().getName()).append("]\n");
sb.append("Method [").append(this.method.toGenericString()).append("]\n");
@@ -431,7 +431,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
* beans, and others). Event listener beans that require proxying should prefer
* class-based proxy mechanisms.
*/
private void assertTargetBean(Method method, Object targetBean, Object[] args) {
private void assertTargetBean(Method method, Object targetBean, @Nullable Object[] args) {
Class<?> methodDeclaringClass = method.getDeclaringClass();
Class<?> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
@@ -443,7 +443,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
}
}
private String getInvocationErrorMessage(Object bean, String message, Object[] resolvedArgs) {
private String getInvocationErrorMessage(Object bean, @Nullable String message, @Nullable Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(bean, message));
sb.append("Resolved arguments: \n");
for (int i = 0; i < resolvedArgs.length; i++) {
@@ -1463,6 +1463,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
//---------------------------------------------------------------------
@Override
@Nullable
public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
return getMessageSource().getMessage(code, args, defaultMessage, locale);
}
@@ -137,6 +137,7 @@ public abstract class AbstractMessageSource extends MessageSourceSupport impleme
@Override
@Nullable
public final String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
String msg = getMessageInternal(code, args, locale);
if (msg != null) {
@@ -123,6 +123,7 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
}
@Override
@Nullable
protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException {
byte[] bytes = bytesCache.get(name);
if (bytes == null) {
@@ -191,6 +191,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
* returning the value found in the bundle as-is (without MessageFormat parsing).
*/
@Override
@Nullable
protected String resolveCodeWithoutArguments(String code, Locale locale) {
if (getCacheMillis() < 0) {
PropertiesHolder propHolder = getMergedProperties(locale);
@@ -145,6 +145,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
* returning the value found in the bundle as-is (without MessageFormat parsing).
*/
@Override
@Nullable
protected String resolveCodeWithoutArguments(String code, Locale locale) {
Set<String> basenames = getBasenameSet();
for (String basename : basenames) {
@@ -19,6 +19,7 @@ package org.springframework.format.support;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} to register hints for {@link DefaultFormattingConversionService}.
@@ -29,7 +30,7 @@ import org.springframework.aot.hint.TypeReference;
class FormattingConversionServiceRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerType(TypeReference.of("javax.money.MonetaryAmount"));
}
}
@@ -18,6 +18,8 @@ package org.springframework.jmx.access;
import javax.management.JMRuntimeException;
import org.springframework.lang.Nullable;
/**
* Thrown when trying to invoke an operation on a proxy that is not exposed
* by the proxied MBean resource's management interface.
@@ -35,7 +37,7 @@ public class InvalidInvocationException extends JMRuntimeException {
* error message.
* @param msg the detail message
*/
public InvalidInvocationException(String msg) {
public InvalidInvocationException(@Nullable String msg) {
super(msg);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@@ -83,18 +83,21 @@ public class MBeanProxyFactoryBean extends MBeanClientInterceptor
public void afterPropertiesSet() throws MBeanServerNotFoundException, MBeanInfoRetrievalException {
super.afterPropertiesSet();
Class<?> interfaceToUse;
if (this.proxyInterface == null) {
this.proxyInterface = getManagementInterface();
if (this.proxyInterface == null) {
interfaceToUse = getManagementInterface();
if (interfaceToUse == null) {
throw new IllegalArgumentException("Property 'proxyInterface' or 'managementInterface' is required");
}
this.proxyInterface = interfaceToUse;
}
else {
interfaceToUse = this.proxyInterface;
if (getManagementInterface() == null) {
setManagementInterface(this.proxyInterface);
setManagementInterface(interfaceToUse);
}
}
this.mbeanProxy = new ProxyFactory(this.proxyInterface, this).getProxy(this.beanClassLoader);
this.mbeanProxy = new ProxyFactory(interfaceToUse, this).getProxy(this.beanClassLoader);
}
@@ -105,6 +108,7 @@ public class MBeanProxyFactoryBean extends MBeanClientInterceptor
}
@Override
@Nullable
public Class<?> getObjectType() {
return this.proxyInterface;
}
@@ -273,6 +273,7 @@ public class JndiObjectFactoryBean extends JndiObjectLocator
}
@Override
@Nullable
public Class<?> getObjectType() {
if (this.proxyInterfaces != null) {
if (this.proxyInterfaces.length == 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.
@@ -68,7 +68,7 @@ public interface TaskScheduler {
* @param trigger an implementation of the {@link Trigger} interface,
* e.g. a {@link org.springframework.scheduling.support.CronTrigger} object
* wrapping a cron expression
* @return a {@link ScheduledFuture} representing pending completion of the task,
* @return a {@link ScheduledFuture} representing pending execution of the task,
* or {@code null} if the given Trigger object never fires (i.e. returns
* {@code null} from {@link Trigger#nextExecution})
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
@@ -85,7 +85,7 @@ public interface TaskScheduler {
* @param task the Runnable to execute whenever the trigger fires
* @param startTime the desired execution time for the task
* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @since 5.0
@@ -99,7 +99,7 @@ public interface TaskScheduler {
* @param task the Runnable to execute whenever the trigger fires
* @param startTime the desired execution time for the task
* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @deprecated as of 6.0, in favor of {@link #schedule(Runnable, Instant)}
@@ -118,7 +118,7 @@ public interface TaskScheduler {
* @param startTime the desired first execution time for the task
* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)
* @param period the interval between successive executions of the task
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @since 5.0
@@ -134,7 +134,7 @@ public interface TaskScheduler {
* @param startTime the desired first execution time for the task
* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)
* @param period the interval between successive executions of the task (in milliseconds)
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @deprecated as of 6.0, in favor of {@link #scheduleAtFixedRate(Runnable, Instant, Duration)}
@@ -151,7 +151,7 @@ public interface TaskScheduler {
* {@link ScheduledFuture} gets cancelled.
* @param task the Runnable to execute whenever the trigger fires
* @param period the interval between successive executions of the task
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @since 5.0
@@ -165,7 +165,7 @@ public interface TaskScheduler {
* {@link ScheduledFuture} gets cancelled.
* @param task the Runnable to execute whenever the trigger fires
* @param period the interval between successive executions of the task (in milliseconds)
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @deprecated as of 6.0, in favor of {@link #scheduleAtFixedRate(Runnable, Duration)}
@@ -185,7 +185,7 @@ public interface TaskScheduler {
* @param startTime the desired first execution time for the task
* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)
* @param delay the delay between the completion of one execution and the start of the next
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @since 5.0
@@ -203,7 +203,7 @@ public interface TaskScheduler {
* (if this is in the past, the task will be executed immediately, i.e. as soon as possible)
* @param delay the delay between the completion of one execution and the start of the next
* (in milliseconds)
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @deprecated as of 6.0, in favor of {@link #scheduleWithFixedDelay(Runnable, Instant, Duration)}
@@ -220,7 +220,7 @@ public interface TaskScheduler {
* {@link ScheduledFuture} gets cancelled.
* @param task the Runnable to execute whenever the trigger fires
* @param delay the delay between the completion of one execution and the start of the next
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @since 5.0
@@ -235,7 +235,7 @@ public interface TaskScheduler {
* @param task the Runnable to execute whenever the trigger fires
* @param delay the delay between the completion of one execution and the start of the next
* (in milliseconds)
* @return a {@link ScheduledFuture} representing pending completion of the task
* @return a {@link ScheduledFuture} representing pending execution of the task
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @deprecated as of 6.0, in favor of {@link #scheduleWithFixedDelay(Runnable, Duration)}
@@ -34,6 +34,7 @@ import org.springframework.lang.Nullable;
public class AsyncConfigurerSupport implements AsyncConfigurer {
@Override
@Nullable
public Executor getAsyncExecutor() {
return null;
}
@@ -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.
@@ -143,11 +143,6 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
* execution callback (which may be a wrapper around the user-supplied task).
* <p>The primary use case is to set some execution context around the task's
* invocation, or to provide some monitoring/statistics for task execution.
* <p><b>NOTE:</b> Exception handling in {@code TaskDecorator} implementations
* is limited to plain {@code Runnable} execution via {@code execute} calls.
* In case of {@code #submit} calls, the exposed {@code Runnable} will be a
* {@code FutureTask} which does not propagate any exceptions; you might
* have to cast it and call {@code Future#get} to evaluate exceptions.
* @since 4.3
*/
public final void setTaskDecorator(TaskDecorator taskDecorator) {
@@ -188,11 +183,10 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
}
private TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
if (managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(concurrentExecutor)) {
return new ManagedTaskExecutorAdapter(concurrentExecutor);
}
TaskExecutorAdapter adapter = new TaskExecutorAdapter(concurrentExecutor);
private TaskExecutorAdapter getAdaptedExecutor(Executor originalExecutor) {
TaskExecutorAdapter adapter =
(managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(originalExecutor) ?
new ManagedTaskExecutorAdapter(originalExecutor) : new TaskExecutorAdapter(originalExecutor));
if (this.taskDecorator != null) {
adapter.setTaskDecorator(this.taskDecorator);
}
@@ -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.
@@ -191,6 +191,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -333,16 +334,19 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
}
@Override
@Nullable
public Instant lastScheduledExecution() {
return (this.le != null ? toInstant(this.le.getScheduledStart()) : null);
}
@Override
@Nullable
public Instant lastActualExecution() {
return (this.le != null ? toInstant(this.le.getRunStart()) : null);
}
@Override
@Nullable
public Instant lastCompletion() {
return (this.le != null ? toInstant(this.le.getRunEnd()) : null);
}
@@ -41,6 +41,7 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
import org.springframework.scheduling.support.TaskUtils;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
/**
@@ -73,6 +74,12 @@ import org.springframework.util.ErrorHandler;
* which tend to have specific constraints for the scheduler thread pool,
* requiring a separate thread pool for general executor purposes in practice.
*
* <p><b>NOTE: This scheduler variant does not track the actual completion of tasks
* but rather just the hand-off to an execution thread.</b> As a consequence,
* a {@link ScheduledFuture} handle (e.g. from {@link #schedule(Runnable, Instant)})
* represents that hand-off rather than the actual completion of the provided task
* (or series of repeated tasks).
*
* <p>As an alternative to the built-in thread-per-task capability, this scheduler
* can also be configured with a separate target executor for scheduled task
* execution through {@link #setTargetTaskExecutor}: e.g. pointing to a shared
@@ -114,10 +121,10 @@ public class SimpleAsyncTaskScheduler extends SimpleAsyncTaskExecutor implements
/**
* Set the clock to use for scheduling purposes.
* <p>The default clock is the system clock for the default time zone.
* @since 5.3
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -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.
@@ -48,7 +48,14 @@ import org.springframework.util.concurrent.ListenableFutureTask;
/**
* A standard implementation of Spring's {@link TaskScheduler} interface, wrapping
* a native {@link java.util.concurrent.ScheduledThreadPoolExecutor} and providing
* all applicable configuration options for it.
* all applicable configuration options for it. The default number of scheduler
* threads is 1; a higher number can be configured through {@link #setPoolSize}.
*
* <p>This is Spring's traditional scheduler variant, staying as close as possible to
* {@link java.util.concurrent.ScheduledExecutorService} semantics. Task execution happens
* on the scheduler thread(s) rather than on separate execution threads. As a consequence,
* a {@link ScheduledFuture} handle (e.g. from {@link #schedule(Runnable, Instant)})
* represents the actual completion of the provided task (or series of repeated tasks).
*
* @author Juergen Hoeller
* @author Mark Fisher
@@ -59,6 +66,8 @@ import org.springframework.util.concurrent.ListenableFutureTask;
* @see #setExecuteExistingDelayedTasksAfterShutdownPolicy
* @see #setThreadFactory
* @see #setErrorHandler
* @see ThreadPoolTaskExecutor
* @see SimpleAsyncTaskScheduler
*/
@SuppressWarnings({"serial", "deprecation"})
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@@ -159,6 +168,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -106,6 +106,7 @@ public class TaskSchedulerRouter implements TaskScheduler, BeanNameAware, BeanFa
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
return determineTargetScheduler(task).schedule(task, trigger);
}
@@ -112,6 +112,7 @@ public class CronTrigger implements Trigger {
* previous execution; therefore, overlapping executions won't occur.
*/
@Override
@Nullable
public Instant nextExecution(TriggerContext triggerContext) {
Instant timestamp = determineLatestTimestamp(triggerContext);
ZoneId zone = (this.zoneId != null ? this.zoneId : triggerContext.getClock().getZone());
@@ -338,6 +338,7 @@ final class QuartzCronField extends CronField {
@Override
@Nullable
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
T result = adjust(temporal);
if (result != null) {
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
@@ -38,6 +39,7 @@ class ScriptingDefaultsParser implements BeanDefinitionParser {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinition bd =
LangNamespaceUtils.registerScriptFactoryPostProcessorIfNecessary(parserContext.getRegistry());

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