Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel 5024bb7227 Release v6.2.0 2024-11-14 16:43:48 +01:00
332 changed files with 2511 additions and 7207 deletions
@@ -31,11 +31,10 @@ runs:
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
develocity-token-expiry: 4
- name: Configure Gradle Properties
shell: bash
run: |
@@ -20,7 +20,7 @@ runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
@@ -2,7 +2,7 @@ name: Build and Deploy Snapshot
on:
push:
branches:
- 6.2.x
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
@@ -21,7 +21,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
+14 -3
View File
@@ -9,11 +9,22 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Check Out Code
- name: Set Up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: '17'
- name: Check Out
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Build
id: build
uses: ./.github/actions/build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
- name: Print JVM Thread Dumps When Cancelled
if: cancelled()
uses: ./.github/actions/print-jvm-thread-dumps
+2 -3
View File
@@ -1,8 +1,7 @@
name: CI
on:
push:
branches:
- 6.2.x
schedule:
- cron: '30 9 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
+79
View File
@@ -0,0 +1,79 @@
name: Release Milestone
on:
push:
tags:
- v6.2.0-M[1-9]
- v6.2.0-RC[1-9]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-stage-release:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and Stage Release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-staging-local'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-stage-release
uses: ./.github/workflows/verify.yml
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
promote-release:
name: Promote Release
needs:
- build-and-stage-release
- verify
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local
create-github-release:
name: Create GitHub Release
needs:
- build-and-stage-release
- promote-release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
milestone: ${{ needs.build-and-stage-release.outputs.version }}
pre-release: true
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
+1 -1
View File
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
@@ -12,9 +12,8 @@ permissions:
jobs:
update-antora-ui-spring:
name: Update on Supported Branches
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
name: Update on Supported Branches
strategy:
matrix:
branch: [ '6.1.x' ]
@@ -26,9 +25,8 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'framework-docs/antora-playbook.yml'
update-antora-ui-spring-docs-build:
name: Update on docs-build
if: ${{ github.repository == 'spring-projects/spring-framework' }}
runs-on: ubuntu-latest
name: Update on docs-build
steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
name: Update
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
with:
cache-read-only: false
- name: Configure Gradle Properties
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.13-librca
java=17.0.12-librca
+5 -6
View File
@@ -16,7 +16,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
### Code of Conduct
This project is governed by the [Spring Code of Conduct](https://github.com/spring-projects/spring-framework#coc-ov-file).
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
By participating you are expected to uphold this code.
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
@@ -65,6 +65,10 @@ follow-up reports will need to be created as new issues with a fresh description
#### Submit a Pull Request
1. If you have not previously done so, please sign the
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
automatically when you submit the PR.
1. Should you create an issue first? No, just create the pull request and use the
description to provide context and motivation, as you would for an issue. If you want
to start a discussion first or have already created an issue, once a pull request is
@@ -81,11 +85,6 @@ multiple edits or corrections of the same logical change. See
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
for an overview of streamlining the commit history.
1. All commits must include a _Signed-off-by_ trailer at the end of each commit message
to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post
[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring).
1. Format commit messages using 55 characters for the subject line, 72 characters per line
for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
+4 -4
View File
@@ -25,17 +25,17 @@ configure(allprojects) { project ->
repositories {
mavenCentral()
maven {
url = "https://repo.spring.io/milestone"
url "https://repo.spring.io/milestone"
content {
// Netty 5 optional support
includeGroup 'io.projectreactor.netty'
}
}
if (version.contains('-')) {
maven { url = "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/milestone" }
}
if (version.endsWith('-SNAPSHOT')) {
maven { url = "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/snapshot" }
}
}
configurations.all {
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.11.4/api/",
// "https://junit.org/junit5/docs/5.11.3/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -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.21.1");
checkstyle.setToolVersion("10.20.1");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+2 -2
View File
@@ -9,7 +9,7 @@ apply from: "${rootDir}/gradle/publications.gradle"
repositories {
maven {
url = "https://repo.spring.io/release"
url "https://repo.spring.io/release"
}
}
@@ -87,7 +87,7 @@ tasks.register('schemaZip', Zip) {
archiveClassifier.set("schema")
description = "Builds -${archiveClassifier} archive containing all " +
"XSDs for deployment at https://springframework.org/schema."
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
duplicatesStrategy DuplicatesStrategy.EXCLUDE
moduleProjects.each { module ->
def Properties schemas = new Properties();
+1 -1
View File
@@ -36,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.17/ui-bundle.zip
+1 -1
View File
@@ -37,7 +37,7 @@ javadoc {
repositories {
maven {
url = "https://repo.spring.io/release"
url "https://repo.spring.io/release"
}
}
-1
View File
@@ -60,7 +60,6 @@
**** xref:core/expressions/language-ref/constructors.adoc[]
**** xref:core/expressions/language-ref/variables.adoc[]
**** xref:core/expressions/language-ref/functions.adoc[]
**** xref:core/expressions/language-ref/varargs.adoc[]
**** xref:core/expressions/language-ref/bean-references.adoc[]
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
@@ -33,11 +33,11 @@ arbitrary advice types. This section describes the basic concepts and standard a
[[aop-api-advice-around]]
=== Interception Around Advice
The most fundamental advice type in Spring is _interception around advice_.
The most fundamental advice type in Spring is interception around advice.
Spring is compliant with the AOP Alliance interface for around advice that uses method
interception. Classes that implement around advice should therefore implement the
following `MethodInterceptor` interface from the `org.aopalliance.intercept` package:
Spring is compliant with the AOP `Alliance` interface for around advice that uses method
interception. Classes that implement `MethodInterceptor` and that implement around advice should also implement the
following interface:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -49,8 +49,8 @@ following `MethodInterceptor` interface from the `org.aopalliance.intercept` pac
The `MethodInvocation` argument to the `invoke()` method exposes the method being
invoked, the target join point, the AOP proxy, and the arguments to the method. The
`invoke()` method should return the invocation's result: typically the return value of
the join point.
`invoke()` method should return the invocation's result: the return value of the join
point.
The following example shows a simple `MethodInterceptor` implementation:
@@ -64,9 +64,9 @@ Java::
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object result = invocation.proceed();
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return result;
return rval;
}
}
----
@@ -79,9 +79,9 @@ Kotlin::
override fun invoke(invocation: MethodInvocation): Any {
println("Before: invocation=[$invocation]")
val result = invocation.proceed()
val rval = invocation.proceed()
println("Invocation returned")
return result
return rval
}
}
----
@@ -105,7 +105,7 @@ currently define pointcut interfaces.
[[aop-api-advice-before]]
=== Before Advice
A simpler advice type is a _before advice_. This does not need a `MethodInvocation`
A simpler advice type is a before advice. This does not need a `MethodInvocation`
object, since it is called only before entering the method.
The main advantage of a before advice is that there is no need to invoke the `proceed()`
@@ -122,6 +122,10 @@ The following listing shows the `MethodBeforeAdvice` interface:
}
----
(Spring's API design would allow for
field before advice, although the usual objects apply to field interception and it is
unlikely for Spring to ever implement it.)
Note that the return type is `void`. Before advice can insert custom behavior before the join
point runs but cannot change the return value. If a before advice throws an
exception, it stops further execution of the interceptor chain. The exception
@@ -172,10 +176,10 @@ TIP: Before advice can be used with any pointcut.
[[aop-api-advice-throws]]
=== Throws Advice
_Throws advice_ is invoked after the return of the join point if the join point threw
Throws advice is invoked after the return of the join point if the join point threw
an exception. Spring offers typed throws advice. Note that this means that the
`org.springframework.aop.ThrowsAdvice` interface does not contain any methods. It is a
marker interface identifying that the given object implements one or more typed throws
tag interface identifying that the given object implements one or more typed throws
advice methods. These should be in the following form:
[source,java,indent=0,subs="verbatim,quotes"]
@@ -185,10 +189,9 @@ advice methods. These should be in the following form:
Only the last argument is required. The method signatures may have either one or four
arguments, depending on whether the advice method is interested in the method and
arguments. The next two listings show classes that are examples of throws advice.
arguments. The next two listing show classes that are examples of throws advice.
The following advice is invoked if a `RemoteException` is thrown (including subclasses of
`RemoteException`):
The following advice is invoked if a `RemoteException` is thrown (including from subclasses):
[tabs]
======
@@ -217,9 +220,9 @@ Kotlin::
----
======
Unlike the preceding advice, the next example declares four arguments, so that it has
access to the invoked method, method arguments, and target object. The following advice
is invoked if a `ServletException` is thrown:
Unlike the preceding
advice, the next example declares four arguments, so that it has access to the invoked method, method
arguments, and target object. The following advice is invoked if a `ServletException` is thrown:
[tabs]
======
@@ -301,7 +304,7 @@ TIP: Throws advice can be used with any pointcut.
[[aop-api-advice-after-returning]]
=== After Returning Advice
An _after returning advice_ in Spring must implement the
An after returning advice in Spring must implement the
`org.springframework.aop.AfterReturningAdvice` interface, which the following listing shows:
[source,java,indent=0,subs="verbatim,quotes"]
@@ -365,7 +368,7 @@ TIP: After returning advice can be used with any pointcut.
[[aop-api-advice-introduction]]
=== Introduction Advice
Spring treats _introduction advice_ as a special kind of interception advice.
Spring treats introduction advice as a special kind of interception advice.
Introduction requires an `IntroductionAdvisor` and an `IntroductionInterceptor` that
implement the following interface:
@@ -578,8 +578,6 @@ Kotlin::
----
======
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
The method signature once again declares the event type to which it listens,
but, this time, with a flexible name and without implementing a specific listener interface.
The event type can also be narrowed through generics as long as the actual event type
@@ -3,7 +3,7 @@
If a bean is a dependency of another bean, that usually means that one bean is set as a
property of another. Typically you accomplish this with the
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element]
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
in XML-based metadata or through xref:core/beans/dependencies/factory-autowire.adoc[autowiring].
However, sometimes dependencies between beans are less direct. An example is when a static
@@ -168,8 +168,8 @@ listings shows how to use the `parent` attribute:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<!-- in the child (descendant) context, bean name is the same as the parent bean -->
<bean id="accountService"
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
@@ -64,7 +64,7 @@ This prevents the same `@Bean` method from accidentally being invoked through a
Java method call, which helps to reduce subtle bugs that can be hard to track down.
When `@Bean` methods are declared within classes that are not annotated with
`@Configuration`, or when `@Configuration(proxyBeanMethods=false)` is declared,
`@Configuration` - or when `@Configuration(proxyBeanMethods=false)` is declared -,
they are referred to as being processed in a "lite" mode. In such scenarios,
`@Bean` methods are effectively a general-purpose factory method mechanism without
special runtime processing (that is, without generating a CGLIB subclass for it).
@@ -3,10 +3,8 @@
You can invoke constructors by using the `new` operator. You should use the fully
qualified class name for all types except those located in the `java.lang` package
(`Integer`, `Float`, `String`, and so on).
xref:core/expressions/language-ref/varargs.adoc[Varargs] are also supported.
The following example shows how to use the `new` operator to invoke constructors.
(`Integer`, `Float`, `String`, and so on). The following example shows how to use the
`new` operator to invoke constructors:
[tabs]
======
@@ -14,29 +12,30 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Inventor einstein = parser.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
parser.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext);
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val einstein = parser.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
val einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor::class.java)
// create new Inventor instance within the add() method of List
parser.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext)
----
======
@@ -2,12 +2,8 @@
= Functions
You can extend SpEL by registering user-defined functions that can be called within
expressions by using the `#functionName(...)` syntax, and like with standard method
invocations, xref:core/expressions/language-ref/varargs.adoc[varargs] are also supported
for function invocations.
Functions can be registered as _variables_ in `EvaluationContext` implementations via the
`setVariable()` method.
expressions by using the `#functionName(...)` syntax. Functions can be registered as
variables in `EvaluationContext` implementations via the `setVariable()` method.
[TIP]
====
@@ -114,9 +110,8 @@ potentially more efficient use cases if the `MethodHandle` target and parameters
been fully bound prior to registration; however, partially bound handles are also
supported.
Consider the `String#formatted(Object...)` instance method, which produces a message
according to a template and a variable number of arguments
(xref:core/expressions/language-ref/varargs.adoc[varargs]).
Consider the `String#formatted(String, Object...)` instance method, which produces a
message according to a template and a variable number of arguments.
You can register and use the `formatted` method as a `MethodHandle`, as the following
example shows:
@@ -156,10 +151,10 @@ Kotlin::
----
======
As mentioned above, binding a `MethodHandle` and registering the bound `MethodHandle` is
also supported. This is likely to be more performant if both the target and all the
arguments are bound. In that case no arguments are necessary in the SpEL expression, as
the following example shows:
As hinted above, binding a `MethodHandle` and registering the bound `MethodHandle` is also
supported. This is likely to be more performant if both the target and all the arguments
are bound. In that case no arguments are necessary in the SpEL expression, as the
following example shows:
[tabs]
======
@@ -173,10 +168,9 @@ Java::
String template = "This is a %s message with %s words: <%s>";
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
MethodType.methodType(String.class, Object[].class))
MethodType.methodType(String.class, Object[].class))
.bindTo(template)
// Here we have to provide the arguments in a single array binding:
.bindTo(varargs);
.bindTo(varargs); //here we have to provide arguments in a single array binding
context.setVariable("message", mh);
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
@@ -195,10 +189,9 @@ Kotlin::
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
MethodType.methodType(String::class.java, Array<Any>::class.java))
MethodType.methodType(String::class.java, Array<Any>::class.java))
.bindTo(template)
// Here we have to provide the arguments in a single array binding:
.bindTo(varargs)
.bindTo(varargs) //here we have to provide arguments in a single array binding
context.setVariable("message", mh)
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
@@ -208,3 +201,4 @@ Kotlin::
======
@@ -1,11 +1,9 @@
[[expressions-methods]]
= Methods
You can invoke methods by using the typical Java programming syntax. You can also invoke
methods directly on literals such as strings or numbers.
xref:core/expressions/language-ref/varargs.adoc[Varargs] are supported as well.
The following examples show how to invoke methods.
You can invoke methods by using typical Java programming syntax. You can also invoke methods
on literals. Variable arguments are also supported. The following examples show how to
invoke methods:
[tabs]
======
@@ -1,151 +0,0 @@
[[expressions-varargs]]
= Varargs Invocations
The Spring Expression Language supports
https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html[varargs]
invocations for xref:core/expressions/language-ref/constructors.adoc[constructors],
xref:core/expressions/language-ref/methods.adoc[methods], and user-defined
xref:core/expressions/language-ref/functions.adoc[functions].
The following example shows how to invoke the `java.lang.String#formatted(Object...)`
_varargs_ method within an expression by supplying the variable argument list as separate
arguments (`'blue', 1`).
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
String expression = "'%s is color #%d'.formatted('blue', 1)";
String message = parser.parseExpression(expression).getValue(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
val expression = "'%s is color #%d'.formatted('blue', 1)"
val message = parser.parseExpression(expression).getValue(String::class.java)
----
======
A variable argument list can also be supplied as an array, as demonstrated in the
following example (`new Object[] {'blue', 1}`).
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
String expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})";
String message = parser.parseExpression(expression).getValue(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
val expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})"
val message = parser.parseExpression(expression).getValue(String::class.java)
----
======
As an alternative, a variable argument list can be supplied as a `java.util.List` for
example, as an xref:core/expressions/language-ref/inline-lists.adoc[inline list]
(`{'blue', 1}`). The following example shows how to do that.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
String expression = "'%s is color #%d'.formatted({'blue', 1})";
String message = parser.parseExpression(expression).getValue(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
val expression = "'%s is color #%d'.formatted({'blue', 1})"
val message = parser.parseExpression(expression).getValue(String::class.java)
----
======
[[expressions-varargs-type-conversion]]
== Varargs Type Conversion
In contrast to the standard support for varargs invocations in Java,
xref:core/expressions/evaluation.adoc#expressions-type-conversion[type conversion] may be
applied to the individual arguments when invoking varargs constructors, methods, or
functions in SpEL.
For example, if we have registered a custom
xref:core/expressions/language-ref/functions.adoc[function] in the `EvaluationContext`
under the name `#reverseStrings` for a method with the signature
`String reverseStrings(String... strings)`, we can invoke that function within a SpEL
expression with any argument that can be converted to a `String`, as demonstrated in the
following example.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// evaluates to "3.0, 2.0, 1, SpEL"
String expression = "#reverseStrings('SpEL', 1, 10F / 5, 3.0000)";
String message = parser.parseExpression(expression)
.getValue(evaluationContext, String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// evaluates to "3.0, 2.0, 1, SpEL"
val expression = "#reverseStrings('SpEL', 1, 10F / 5, 3.0000)"
val message = parser.parseExpression(expression)
.getValue(evaluationContext, String::class.java)
----
======
Similarly, any array whose component type is a subtype of the required varargs type can
be supplied as the variable argument list for a varargs invocation. For example, a
`String[]` array can be supplied to a varargs invocation that accepts an `Object...`
argument list.
The following listing demonstrates that we can supply a `String[]` array to the
`java.lang.String#formatted(Object...)` _varargs_ method. It also highlights that `1`
will be automatically converted to `"1"`.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
String expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})";
String message = parser.parseExpression(expression).getValue(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// evaluates to "blue is color #1"
val expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})"
val message = parser.parseExpression(expression).getValue(String::class.java)
----
======
@@ -6,7 +6,7 @@ the same way as Spring's integration does for the JDBC API.
JMS can be roughly divided into two areas of functionality, namely the production and
consumption of messages. The `JmsTemplate` class is used for message production and
synchronous message receipt. For asynchronous receipt similar to Jakarta EE's
synchronous message reception. For asynchronous reception similar to Jakarta EE's
message-driven bean style, Spring provides a number of message-listener containers that
you can use to create Message-Driven POJOs (MDPs). Spring also provides a declarative way
to create message listeners.
@@ -5,7 +5,7 @@ This describes how to receive messages with JMS in Spring.
[[jms-receiving-sync]]
== Synchronous Receipt
== Synchronous Reception
While JMS is typically associated with asynchronous processing, you can
consume messages synchronously. The overloaded `receive(..)` methods provide this
@@ -16,11 +16,11 @@ the receiver should wait before giving up waiting for a message.
[[jms-receiving-async]]
== Asynchronous Receipt: Message-Driven POJOs
== Asynchronous reception: Message-Driven POJOs
NOTE: Spring also supports annotated-listener endpoints through the use of the `@JmsListener`
annotation and provides open infrastructure to register endpoints programmatically.
This is, by far, the most convenient way to set up an asynchronous receiver.
annotation and provides an open infrastructure to register endpoints programmatically.
This is, by far, the most convenient way to setup an asynchronous receiver.
See xref:integration/jms/annotated.adoc#jms-annotated-support[Enable Listener Endpoint Annotations] for more details.
In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Message-Driven
@@ -154,7 +154,7 @@ listener container.
You can activate local resource transactions through the `sessionTransacted` flag
on the listener container definition. Each message listener invocation then operates
within an active JMS transaction, with message receipt rolled back in case of listener
within an active JMS transaction, with message reception rolled back in case of listener
execution failure. Sending a response message (through `SessionAwareMessageListener`) is
part of the same local transaction, but any other resource operations (such as
database access) operate independently. This usually requires duplicate message
@@ -173,7 +173,7 @@ To configure a message listener container for XA transaction participation, you
to configure a `JtaTransactionManager` (which, by default, delegates to the Jakarta EE
server's transaction subsystem). Note that the underlying JMS `ConnectionFactory` needs to
be XA-capable and properly registered with your JTA transaction coordinator. (Check your
Jakarta EE server's configuration of JNDI resources.) This lets message receipt as well
Jakarta EE server's configuration of JNDI resources.) This lets message reception as well
as (for example) database access be part of the same transaction (with unified commit
semantics, at the expense of XA transaction log overhead).
@@ -167,15 +167,13 @@ operations that do not refer to a specific destination.
One of the most common uses of JMS messages in the EJB world is to drive message-driven
beans (MDBs). Spring offers a solution to create message-driven POJOs (MDPs) in a way
that does not tie a user to an EJB container. (See
xref:integration/jms/receiving.adoc#jms-receiving-async[Asynchronous Receipt: Message-Driven POJOs]
for detailed coverage of Spring's MDP support.) Endpoint methods can be annotated with
`@JmsListener` -- see xref:integration/jms/annotated.adoc[Annotation-driven Listener Endpoints]
for more details.
that does not tie a user to an EJB container. (See xref:integration/jms/receiving.adoc#jms-receiving-async[Asynchronous reception: Message-Driven POJOs] for detailed
coverage of Spring's MDP support.) Since Spring Framework 4.1, endpoint methods can be
annotated with `@JmsListener` -- see xref:integration/jms/annotated.adoc[Annotation-driven Listener Endpoints] for more details.
A message listener container is used to receive messages from a JMS message queue and
drive the `MessageListener` that is injected into it. The listener container is
responsible for all threading of message receipt and dispatches into the listener for
responsible for all threading of message reception and dispatches into the listener for
processing. A message listener container is the intermediary between an MDP and a
messaging provider and takes care of registering to receive messages, participating in
transactions, resource acquisition and release, exception conversion, and so on. This
@@ -229,7 +227,7 @@ the JMS provider, advanced functionality (such as participation in externally ma
transactions), and compatibility with Jakarta EE environments.
You can customize the cache level of the container. Note that, when no caching is enabled,
a new connection and a new session is created for each message receipt. Combining this
a new connection and a new session is created for each message reception. Combining this
with a non-durable subscription with high loads may lead to message loss. Make sure to
use a proper cache level in such a case.
@@ -248,7 +246,7 @@ in the form of a business entity existence check or a protocol table check.
Any such arrangements are significantly more efficient than the alternative:
wrapping your entire processing with an XA transaction (through configuring your
`DefaultMessageListenerContainer` with an `JtaTransactionManager`) to cover the
receipt of the JMS message as well as the execution of the business logic in your
reception of the JMS message as well as the execution of the business logic in your
message listener (including database operations, etc.).
IMPORTANT: The default `AUTO_ACKNOWLEDGE` mode does not provide proper reliability guarantees.
@@ -37,7 +37,7 @@ As outlined xref:integration/observability.adoc[at the beginning of this section
|Processing time for an execution of a `@Scheduled` task
|===
NOTE: Observations use Micrometer's official naming convention, but Metrics names will be automatically converted
NOTE: Observations are using Micrometer's official naming convention, but Metrics names will be automatically converted
{micrometer-docs}/concepts/naming.html[to the format preferred by the monitoring system backend]
(Prometheus, Atlas, Graphite, InfluxDB...).
@@ -97,7 +97,7 @@ This can be done by declaring a `SchedulingConfigurer` bean that sets the observ
include-code::./ObservationSchedulingConfigurer[]
It uses the `org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention` by default, backed by the `ScheduledTaskObservationContext`.
It is using the `org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention` by default, backed by the `ScheduledTaskObservationContext`.
You can configure a custom implementation on the `ObservationRegistry` directly.
During the execution of the scheduled method, the current observation is restored in the `ThreadLocal` context or the Reactor context (if the scheduled method returns a `Mono` or `Flux` type).
@@ -107,7 +107,7 @@ By default, the following `KeyValues` are created:
[cols="a,a"]
|===
|Name | Description
|`code.function` _(required)_|Name of the Java `Method` that is scheduled for execution.
|`code.function` _(required)_|Name of Java `Method` that is scheduled for execution.
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method, or `"ANONYMOUS"` for anonymous classes.
|`error` _(required)_|Class name of the exception thrown during the execution, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
@@ -126,7 +126,7 @@ This instrumentation will create 2 types of observations:
* `"jms.message.publish"` when a JMS message is sent to the broker, typically with `JmsTemplate`.
* `"jms.message.process"` when a JMS message is processed by the application, typically with a `MessageListener` or a `@JmsListener` annotated method.
NOTE: Currently there is no instrumentation for `"jms.message.receive"` observations as there is little value in measuring the time spent waiting for the receipt of a message.
NOTE: currently there is no instrumentation for `"jms.message.receive"` observations as there is little value in measuring the time spent waiting for the reception of a message.
Such an integration would typically instrument `MessageConsumer#receive` method calls. But once those return, the processing time is not measured and the trace scope cannot be propagated to the application.
By default, both observations share the same set of possible `KeyValues`:
@@ -138,7 +138,7 @@ By default, both observations share the same set of possible `KeyValues`:
|`error` |Class name of the exception thrown during the messaging operation (or "none").
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`messaging.destination.temporary` _(required)_|Whether the destination is a `TemporaryQueue` or `TemporaryTopic` (values: `"true"` or `"false"`).
|`messaging.operation` _(required)_|Name of the JMS operation being performed (values: `"publish"` or `"process"`).
|`messaging.operation` _(required)_|Name of JMS operation being performed (values: `"publish"` or `"process"`).
|===
.High cardinality Keys
@@ -146,7 +146,7 @@ By default, both observations share the same set of possible `KeyValues`:
|===
|Name | Description
|`messaging.message.conversation_id` |The correlation ID of the JMS message.
|`messaging.destination.name` |The name of the destination the current message was sent to.
|`messaging.destination.name` |The name of destination the current message was sent to.
|`messaging.message.id` |Value used by the messaging system as an identifier for the message.
|===
@@ -213,7 +213,7 @@ By default, the following `KeyValues` are created:
|Name | Description
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
@@ -235,10 +235,10 @@ This can be done on the `WebHttpHandlerBuilder`, as follows:
include-code::./HttpHandlerConfiguration[]
It uses the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
This will only record an observation as an error if the `Exception` has not been handled by an application Controller.
Typically, all exceptions handled by Spring WebFlux's `@ExceptionHandler` and xref:web/webflux/ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
Typically, all exceptions handled by Spring WebFlux's `@ExceptionHandler` and <<web.adoc#webflux-ann-rest-exceptions,`ProblemDetail` support>> will not be recorded with the observation.
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
include-code::./UserController[]
@@ -251,7 +251,7 @@ By default, the following `KeyValues` are created:
|Name | Description
|`error` _(required)_|Class name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
@@ -270,7 +270,6 @@ By default, the following `KeyValues` are created:
== HTTP Client Instrumentation
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
This observation measures the entire HTTP request/response exchange, from connection establishment up to body deserialization.
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
[[observability.http-client.resttemplate]]
@@ -285,8 +284,8 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -313,8 +312,8 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if the request could not be created.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -333,7 +332,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
[[observability.http-client.webclient]]
=== WebClient
Applications must configure an `ObservationRegistry` on the `WebClient.Builder` to enable the instrumentation; without that, observations are "no-ops".
Applications must configure an `ObservationRegistry` on the `WebClient` builder to enable the instrumentation; without that, observations are "no-ops".
Spring Boot will auto-configure `WebClient.Builder` beans with the observation registry already set.
Instrumentation uses the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
@@ -342,8 +341,8 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
[cols="a,a"]
|===
|Name | Description
|`method` _(required)_|Name of the HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. The protocol, host and port part of the URI are not considered.
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|`client.name` _(required)_|Client name derived from the request URI host.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
@@ -938,10 +938,10 @@ method parameters:
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
| `@RequestHeader`
| Add a request header or multiple headers. The argument may be a single value,
a `Collection<?>` of values, `Map<String, ?>`,`MultiValueMap<String, ?>`.
Type conversion is supported for non-String values. Header values are added and
do not override already added header values.
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
individual value. Type conversion is supported for non-String values. This overrides
the annotation's `headers` attribute.
| `@PathVariable`
| Add a variable for expand a placeholder in the request URL. The argument may be a
@@ -14,13 +14,16 @@ For example, `@Autowired lateinit var thing: Thing` implies that a bean
of type `Thing` must be registered in the application context, while `@Autowired lateinit var thing: Thing?`
does not raise an error if such a bean does not exist.
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, car)` implies
Following the same principle, `@Bean fun play(toy: Toy, car: Car?) = Baz(toy, Car)` implies
that a bean of type `Toy` must be registered in the application context, while a bean of
type `Car` may or may not exist. The same behavior applies to autowired constructor parameters.
NOTE: If you use bean validation on classes with properties or a primary constructor
with parameters, you may need to use
parameters, you may need to use
{kotlin-docs}/annotations.html#annotation-use-site-targets[annotation use-site targets],
such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in
{stackoverflow-site}/a/35853200/1092077[this Stack Overflow response].
@@ -1,31 +1,38 @@
[[spring-testing-annotation-beanoverriding-mockitobean]]
= `@MockitoBean` and `@MockitoSpyBean`
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
respectively. In the latter case, an early instance of the original bean is captured and
wrapped by the spy.
`@MockitoBean` and `@MockitoSpyBean` are used on fields in test classes to override beans
in the test's `ApplicationContext` with a Mockito _mock_ or _spy_, respectively. In the
latter case, an early instance of the original bean is captured and wrapped by the spy.
By default, the annotated field's type is used to search for candidate beans to override.
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
override. Alternatively, a candidate whose bean name matches the name of the field will
match.
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
exist. However, if you would like for the test to fail when a corresponding bean does not
exist, you can set the `enforceOverride` attribute to `true` for example,
`@MockitoBean(enforceOverride = true)`.
To use a by-name override rather than a by-type override, specify the `name` attribute
of the annotation.
[WARNING]
====
Qualifiers, including the name of the field, are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
the same bean in several test classes, make sure to name the field consistently to avoid
the same bean in several tests, make sure to name the field consistently to avoid
creating unnecessary contexts.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
By default, the `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
If no existing bean matches, a new bean is created on the fly. However, you can switch to
the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
following section for an example.
If no existing bean matches, a new bean is created on the fly. As mentioned previously,
you can switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to
`true`.
The `@MockitoSpyBean` annotation uses the `WRAP`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
@@ -44,26 +51,6 @@ When using `@MockitoSpyBean` to create a spy for a `FactoryBean`, a spy will be
for the object created by the `FactoryBean`, not for the `FactoryBean` itself.
====
[NOTE]
====
There are no restrictions on the visibility of `@MockitoBean` and `@MockitoSpyBean`
fields.
Such fields can therefore be `public`, `protected`, package-private (default visibility),
or `private` depending on the needs or coding practices of the project.
====
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
== `@MockitoBean` Examples
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
exist. However, if you would like for the test to fail when a corresponding bean does not
exist, you can set the `enforceOverride` attribute to `true` for example,
`@MockitoBean(enforceOverride = true)`.
To use a by-name override rather than a by-type override, specify the `name` (or `value`)
attribute of the annotation.
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
[tabs]
@@ -72,13 +59,11 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoBean // <1>
CustomService customService;
private CustomService customService;
// tests...
// test case body...
}
----
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
@@ -87,8 +72,8 @@ Java::
In the example above, we are creating a mock for `CustomService`. If more than one bean
of that type exists, the bean named `customService` is considered. Otherwise, the test
will fail, and you will need to provide a qualifier of some sort to identify which of the
`CustomService` beans you want to override. If no such bean exists, a bean will be
created with an auto-generated bean name.
`CustomService` beans you want to override. If no such bean exists, a bean definition
will be created with an auto-generated bean name.
The following example uses a by-name lookup, rather than a by-type lookup:
@@ -98,43 +83,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoBean("service") // <1>
CustomService customService;
private CustomService customService;
// tests...
// test case body...
}
----
<1> Replace the bean named `service` with a Mockito `mock`.
======
If no bean named `service` exists, one is created.
If no bean definition named `service` exists, one is created.
`@MockitoBean` can also be used at the type level:
- on a test class or any superclass or implemented interface in the type hierarchy above
the test class
- on an enclosing class for a `@Nested` test class or on any class or interface in the
type hierarchy or enclosing class hierarchy above the `@Nested` test class
When `@MockitoBean` is declared at the type level, the type of bean (or beans) to mock
must be supplied via the `types` attribute for example,
`@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple candidates
exist in the application context, you can explicitly specify a bean name to mock by
setting the `name` attribute. Note, however, that the `types` attribute must contain a
single type if an explicit bean `name` is configured for example,
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
To support reuse of mock configuration, `@MockitoBean` may be used as a meta-annotation
to create custom _composed annotations_ — for example, to define common mock
configuration in a single annotation that can be reused across a test suite.
`@MockitoBean` can also be used as a repeatable annotation at the type level — for
example, to mock several beans by name.
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation:
[tabs]
======
@@ -142,70 +104,11 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = {OrderService.class, UserService.class}) // <1>
@MockitoBean(name = "ps1", types = PrintingService.class) // <2>
public @interface SharedMocks {
}
----
<1> Register `OrderService` and `UserService` mocks by-type.
<2> Register `PrintingService` mock by-name.
======
The following demonstrates how `@SharedMocks` can be used on a test class.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@SharedMocks // <1>
class BeanOverrideTests {
@Autowired OrderService orderService; // <2>
@Autowired UserService userService; // <2>
@Autowired PrintingService ps1; // <2>
// Inject other components that rely on the mocks.
@Test
void testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common mocks via the custom `@SharedMocks` annotation.
<2> Optionally inject mocks to _stub_ or _verify_ them.
======
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
APIs.
[[spring-testing-annotation-beanoverriding-mockitospybean-examples]]
== `@MockitoSpyBean` Examples
The following example shows how to use the default behavior of the `@MockitoSpyBean`
annotation:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoSpyBean // <1>
CustomService customService;
private CustomService customService;
// tests...
// test case body...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
@@ -224,13 +127,12 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
class OverrideBeanTests {
@MockitoSpyBean("service") // <1>
CustomService customService;
private CustomService customService;
// test case body...
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito `spy`.
@@ -1,8 +1,8 @@
[[spring-testing-annotation-beanoverriding-testbean]]
= `@TestBean`
`@TestBean` is used on a non-static field in a test class to override a specific bean in
the test's `ApplicationContext` with an instance provided by a factory method.
`@TestBean` is used on a field in a test class to override a specific bean in the test's
`ApplicationContext` with an instance provided by a factory method.
The associated factory method name is derived from the annotated field's name, or the
bean name if specified. The factory method must be `static`, accept no arguments, and
@@ -30,14 +30,6 @@ same bean in several tests, make sure to name the field consistently to avoid cr
unnecessary contexts.
====
[NOTE]
====
There are no restrictions on the visibility of `@TestBean` fields or factory methods.
Such fields and methods can therefore be `public`, `protected`, package-private (default
visibility), or `private` depending on the needs or coding practices of the project.
====
The following example shows how to use the default behavior of the `@TestBean` annotation:
[tabs]
@@ -48,11 +40,11 @@ Java::
----
class OverrideBeanTests {
@TestBean // <1>
CustomService customService;
private CustomService customService;
// test case body...
static CustomService customService() { // <2>
private static CustomService customService() { // <2>
return new MyFakeCustomService();
}
}
@@ -76,11 +68,11 @@ Java::
----
class OverrideBeanTests {
@TestBean(name = "service", methodName = "createCustomService") // <1>
CustomService customService;
private CustomService customService;
// test case body...
static CustomService createCustomService() { // <2>
private static CustomService createCustomService() { // <2>
return new MyFakeCustomService();
}
}
@@ -92,10 +84,8 @@ Java::
[TIP]
====
To locate the factory method to invoke, Spring searches in the class in which the
`@TestBean` field is declared, in one of its superclasses, or in any implemented
interfaces. If the `@TestBean` field is declared in a `@Nested` test class, the enclosing
class hierarchy will also be searched.
Spring searches for the factory method to invoke in the test class, in the test class
hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
Alternatively, a factory method in an external class can be referenced via its
fully-qualified method name following the syntax `<fully-qualified class name>#<method name>`
@@ -2,8 +2,7 @@
= Bean Overriding in Tests
Bean overriding in tests refers to the ability to override specific beans in the
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
test class.
`ApplicationContext` for a test class, by annotating one or more fields in the test class.
NOTE: This feature is intended as a less risky alternative to the practice of registering
a bean via `@Bean` with the `DefaultListableBeanFactory`
@@ -42,10 +41,9 @@ The `spring-test` module registers implementations of the latter two
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
properties file].
The bean overriding infrastructure searches in test classes for any non-static field that
is meta-annotated with `@BeanOverride` and instantiates the corresponding
`BeanOverrideProcessor` which is responsible for creating an appropriate
`BeanOverrideHandler`.
The bean overriding infrastructure searches in test classes for any field meta-annotated
with `@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
responsible for creating an appropriate `BeanOverrideHandler`.
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
@@ -127,7 +127,7 @@ Kotlin::
======
For Spring MVC, use the following where the Spring `ApplicationContext` is passed to
{spring-framework-api}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup(org.springframework.web.context.WebApplicationContext)[MockMvcBuilders.webAppContextSetup]
{spring-framework-api}/test/web/servlet/setup/MockMvcBuilders.html#webAppContextSetup-org.springframework.web.context.WebApplicationContext-[MockMvcBuilders.webAppContextSetup]
to create a xref:testing/mockmvc.adoc[MockMvc] instance to handle
requests:
@@ -193,7 +193,7 @@ Kotlin::
[[webtestclient-fn-config]]
=== Bind to Router Function
This setup allows you to test xref:web/webflux-functional.adoc[functional endpoints] via
This setup allows you to test <<web-reactive.adoc#webflux-fn, functional endpoints>> via
mock request and response objects, without a running server.
For WebFlux, use the following which delegates to `RouterFunctions.toWebHandler` to
@@ -1,6 +1,5 @@
[[webflux-cors]]
= CORS
[.small]#xref:web/webmvc-cors.adoc[See equivalent in the Servlet stack]#
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
@@ -365,7 +364,7 @@ Kotlin::
You can apply CORS support through the built-in
{spring-framework-api}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
good fit with xref:web/webflux-functional.adoc[functional endpoints].
good fit with <<webflux-fn, functional endpoints>>.
NOTE: If you try to use the `CorsFilter` with Spring Security, keep in mind that Spring
Security has {docs-spring-security}/servlet/integrations/cors.html[built-in support] for
@@ -1,6 +1,5 @@
[[webflux-fn]]
= Functional Endpoints
[.small]#xref:web/webmvc-functional.adoc[See equivalent in the Servlet stack]#
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
@@ -1,6 +1,5 @@
[[webflux-websocket]]
= WebSockets
[.small]#xref:web/websocket.adoc[See equivalent in the Servlet stack]#
This part of the reference documentation covers support for reactive-stack WebSocket
@@ -605,4 +605,9 @@ For method parameters and returns values, generally, `@HttpExchange` supports a
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
server-side specific parameter types. For details, see the list for
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
`@HttpExchange` also supports a `headers()` parameter which accepts `"name=value"`-like
pairs like in `@RequestMapping(headers={})` on the client side. On the server side,
this extends to the full syntax that
xref:#webflux-ann-requestmapping-params-and-headers[`@RequestMapping`] supports.
@@ -101,7 +101,7 @@ On that foundation, Spring WebFlux provides a choice of two programming models:
from the `spring-web` module. Both Spring MVC and WebFlux controllers support reactive
(Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable
difference is that WebFlux also supports reactive `@RequestBody` arguments.
* xref:web/webflux-functional.adoc[Functional Endpoints]: Lambda-based, lightweight, and functional programming model. You can think of
* <<webflux-fn>>: Lambda-based, lightweight, and functional programming model. You can think of
this as a small library or a set of utilities that an application can use to route and
handle requests. The big difference with annotated controllers is that the application
is in charge of request handling from start to finish versus declaring intent through
@@ -1,6 +1,5 @@
[[mvc-cors]]
= CORS
[.small]#xref:web/webflux-cors.adoc[See equivalent in the Reactive stack]#
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
@@ -1,7 +1,6 @@
[[webmvc-fn]]
= Functional Endpoints
[.small]#xref:web/webflux-functional.adoc[See equivalent in the Reactive stack]#
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
are used to route and handle requests and contracts are designed for immutability.
@@ -30,7 +30,7 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method
[[filters-forwarded-headers]]
[[forwarded-headers]]
== Forwarded Headers
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-forwarded-headers[See equivalent in the Reactive stack]#
@@ -1,7 +1,6 @@
[[websocket]]
= WebSockets
:page-section-summary-toc: 1
[.small]#xref:web/webflux-websocket.adoc[See equivalent in the Reactive stack]#
This part of the reference documentation covers support for Servlet stack, WebSocket
+13 -13
View File
@@ -7,21 +7,21 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
api(platform("io.micrometer:micrometer-bom:1.14.3"))
api(platform("io.netty:netty-bom:4.1.117.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.1"))
api(platform("io.micrometer:micrometer-bom:1.14.0"))
api(platform("io.netty:netty-bom:4.1.115.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.2"))
api(platform("io.projectreactor:reactor-bom:2024.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.4"))
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.2"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.16"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.16"))
api(platform("org.assertj:assertj-bom:3.26.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.15"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.15"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.11.4"))
api(platform("org.mockito:mockito-bom:5.15.2"))
api(platform("org.junit:junit-bom:5.11.3"))
api(platform("org.mockito:mockito-bom:5.14.2"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
@@ -31,7 +31,7 @@ dependencies {
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.11.0")
api("com.google.protobuf:protobuf-java-util:4.29.3")
api("com.google.protobuf:protobuf-java-util:4.28.3")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
@@ -54,11 +54,11 @@ dependencies {
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.10")
api("io.reactivex.rxjava3:rxjava:3.1.9")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.18.Final")
api("io.undertow:undertow-servlet:2.3.18.Final")
api("io.undertow:undertow-websockets-jsr:2.3.18.Final")
api("io.undertow:undertow-servlet:2.3.17.Final")
api("io.undertow:undertow-websockets-jsr:2.3.17.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.2
version=6.2.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -1
View File
@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,11 +27,11 @@ package org.springframework.aop;
* <p>Some examples of valid methods would be:
*
* <pre class="code">public void afterThrowing(Exception ex)</pre>
* <pre class="code">public void afterThrowing(RemoteException ex)</pre>
* <pre class="code">public void afterThrowing(RemoteException)</pre>
* <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
* <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>
*
* <p>The first three arguments are optional, and only useful if we want further
* The first three arguments are optional, and only useful if we want further
* information about the joinpoint, as in AspectJ <b>after-throwing</b> advice.
*
* <p><b>Note:</b> If a throws-advice method throws an exception itself, it will
@@ -668,8 +668,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof MethodCacheKey that &&
(this.method == that.method || this.method.equals(that.method))));
return (this == other || (other instanceof MethodCacheKey that && this.method == that.method));
}
@Override
@@ -60,12 +60,11 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || !config.hasUserSuppliedInterfaces()) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null && config.getProxiedInterfaces().length == 0) {
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass == null || targetClass.isInterface() ||
Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -99,7 +99,7 @@ abstract class AbstractProxyExceptionHandlingTests {
assertThat(throwableSeenByInterceptor).isSameAs(undeclaredCheckedException);
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
.cause().isSameAs(undeclaredCheckedException);
.hasCauseReference(undeclaredCheckedException);
}
@Test
@@ -147,7 +147,7 @@ abstract class AbstractProxyExceptionHandlingTests {
invokeProxy();
assertThat(throwableSeenByCaller)
.isInstanceOf(UndeclaredThrowableException.class)
.cause().isSameAs(undeclaredCheckedException);
.hasCauseReference(undeclaredCheckedException);
}
@Test
@@ -340,18 +340,6 @@ class ProxyFactoryTests {
assertThat(AopProxyUtils.ultimateTargetClass(proxy)).isEqualTo(MyDate.class);
}
@Test
void proxyInterfaceInCaseOfIntroducedInterfaceOnly() {
ProxyFactory pf = new ProxyFactory();
pf.addInterface(TimeStamped.class);
TimestampIntroductionInterceptor ti = new TimestampIntroductionInterceptor(0L);
pf.addAdvisor(new DefaultIntroductionAdvisor(ti, TimeStamped.class));
Object proxy = pf.getProxy();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).as("Proxy is a JDK proxy").isTrue();
assertThat(proxy).isInstanceOf(TimeStamped.class);
assertThat(AopProxyUtils.ultimateTargetClass(proxy)).isEqualTo(proxy.getClass());
}
@Test
void proxyInterfaceInCaseOfNonTargetInterface() {
ProxyFactory pf = new ProxyFactory();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -95,7 +95,7 @@ public abstract class BeanFactoryAnnotationUtils {
// Full qualifier matching supported.
return qualifiedBeanOfType(lbf, beanType, qualifier);
}
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
else if (beanFactory.containsBean(qualifier)) {
// Fallback: target bean at least found by bean name.
return beanFactory.getBean(qualifier, beanType);
}
@@ -110,16 +110,16 @@ public abstract class BeanFactoryAnnotationUtils {
/**
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
* (for example, {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
* @param beanFactory the factory to get the target bean from
* @param bf the factory to get the target bean from
* @param beanType the type of bean to retrieve
* @param qualifier the qualifier for selecting between multiple bean matches
* @return the matching bean of type {@code T} (never {@code null})
*/
private static <T> T qualifiedBeanOfType(ListableBeanFactory beanFactory, Class<T> beanType, String qualifier) {
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType);
private static <T> T qualifiedBeanOfType(ListableBeanFactory bf, Class<T> beanType, String qualifier) {
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
String matchingBean = null;
for (String beanName : candidateBeans) {
if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) {
if (isQualifierMatch(qualifier::equals, beanName, bf)) {
if (matchingBean != null) {
throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
}
@@ -127,11 +127,11 @@ public abstract class BeanFactoryAnnotationUtils {
}
}
if (matchingBean != null) {
return beanFactory.getBean(matchingBean, beanType);
return bf.getBean(matchingBean, beanType);
}
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
else if (bf.containsBean(qualifier)) {
// Fallback: target bean at least found by bean name - probably a manually registered singleton.
return beanFactory.getBean(qualifier, beanType);
return bf.getBean(qualifier, beanType);
}
else {
throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +
@@ -123,7 +123,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
CodeBlock.Builder code = CodeBlock.builder();
RootBeanDefinition mbd = this.registeredBean.getMergedBeanDefinition();
Class<?> beanClass = (mbd.hasBeanClass() ? ClassUtils.getUserClass(mbd.getBeanClass()) : null);
Class<?> beanClass = (mbd.hasBeanClass() ? mbd.getBeanClass() : null);
CodeBlock beanClassCode = generateBeanClassCode(
beanRegistrationCode.getClassName().packageName(),
(beanClass != null ? beanClass : beanType.toClass()));
@@ -156,96 +156,91 @@ public class InstanceSupplierCodeGenerator {
}
private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Constructor<?> constructor) {
ConstructorDescriptor descriptor = new ConstructorDescriptor(
registeredBean.getBeanName(), constructor, registeredBean.getBeanClass());
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
Class<?> publicType = descriptor.publicType();
if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
return generateCodeForInaccessibleConstructor(descriptor,
hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(beanClass)) {
return generateCodeForInaccessibleConstructor(beanName, constructor,
hints -> hints.registerType(beanClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
}
if (!isVisible(constructor, constructor.getDeclaringClass())) {
return generateCodeForInaccessibleConstructor(descriptor,
return generateCodeForInaccessibleConstructor(beanName, constructor,
hints -> hints.registerConstructor(constructor, ExecutableMode.INVOKE));
}
return generateCodeForAccessibleConstructor(descriptor);
return generateCodeForAccessibleConstructor(beanName, constructor);
}
private CodeBlock generateCodeForAccessibleConstructor(ConstructorDescriptor descriptor) {
Constructor<?> constructor = descriptor.constructor();
private CodeBlock generateCodeForAccessibleConstructor(String beanName, Constructor<?> constructor) {
this.generationContext.getRuntimeHints().reflection().registerConstructor(
constructor, ExecutableMode.INTROSPECT);
if (constructor.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) {
return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, descriptor.actualType());
return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, constructor.getDeclaringClass());
}
if (!isThrowingCheckedException(constructor)) {
return CodeBlock.of("$T::new", descriptor.actualType());
return CodeBlock.of("$T::new", constructor.getDeclaringClass());
}
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, descriptor.actualType());
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, constructor.getDeclaringClass());
}
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForConstructor(method, descriptor, PRIVATE_STATIC));
buildGetInstanceMethodForConstructor(method, beanName, constructor, PRIVATE_STATIC));
return generateReturnStatement(generatedMethod);
}
private CodeBlock generateCodeForInaccessibleConstructor(ConstructorDescriptor descriptor,
Consumer<ReflectionHints> hints) {
private CodeBlock generateCodeForInaccessibleConstructor(String beanName,
Constructor<?> constructor, Consumer<ReflectionHints> hints) {
Constructor<?> constructor = descriptor.constructor();
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(constructor.getDeclaringClass(), constructor)
.detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType));
hints.accept(this.generationContext.getRuntimeHints().reflection());
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Get the bean instance supplier for '$L'.", descriptor.beanName());
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC);
codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, descriptor.publicType()));
method.addStatement(generateResolverForConstructor(descriptor));
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, constructor.getDeclaringClass()));
method.addStatement(generateResolverForConstructor(constructor));
});
return generateReturnStatement(generatedMethod);
}
private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, ConstructorDescriptor descriptor,
javax.lang.model.element.Modifier... modifiers) {
private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, String beanName,
Constructor<?> constructor, javax.lang.model.element.Modifier... modifiers) {
Constructor<?> constructor = descriptor.constructor();
Class<?> publicType = descriptor.publicType();
Class<?> actualType = descriptor.actualType();
Class<?> declaringClass = constructor.getDeclaringClass();
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(actualType, constructor)
codeWarnings.detectDeprecation(declaringClass, constructor)
.detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", descriptor.beanName());
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(modifiers);
codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, publicType));
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, declaringClass));
CodeBlock.Builder code = CodeBlock.builder();
code.add(generateResolverForConstructor(descriptor));
code.add(generateResolverForConstructor(constructor));
boolean hasArguments = constructor.getParameterCount() > 0;
boolean onInnerClass = ClassUtils.isInnerClass(actualType);
boolean onInnerClass = ClassUtils.isInnerClass(declaringClass);
CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(actualType, constructor)
new AutowiredArgumentsCodeGenerator(declaringClass, constructor)
.generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0))
: NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForConstructor(actualType, arguments);
CodeBlock newInstance = generateNewInstanceCodeForConstructor(declaringClass, arguments);
code.add(generateWithGeneratorCode(hasArguments, newInstance));
method.addStatement(code.build());
}
private CodeBlock generateResolverForConstructor(ConstructorDescriptor descriptor) {
CodeBlock parameterTypes = generateParameterTypesCode(descriptor.constructor().getParameterTypes());
private CodeBlock generateResolverForConstructor(Constructor<?> constructor) {
CodeBlock parameterTypes = generateParameterTypesCode(constructor.getParameterTypes());
return CodeBlock.of("return $T.<$T>forConstructor($L)", BeanInstanceSupplier.class,
descriptor.publicType(), parameterTypes);
constructor.getDeclaringClass(), parameterTypes);
}
private CodeBlock generateNewInstanceCodeForConstructor(Class<?> declaringClass, CodeBlock args) {
@@ -443,11 +438,4 @@ public class InstanceSupplierCodeGenerator {
}
}
record ConstructorDescriptor(String beanName, Constructor<?> constructor, Class<?> publicType) {
Class<?> actualType() {
return this.constructor.getDeclaringClass();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -991,64 +991,54 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
*/
@Nullable
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
boolean locked = this.singletonLock.tryLock();
if (!locked) {
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
if (bw != null) {
return (FactoryBean<?>) bw.getWrappedInstance();
}
Object beanInstance = getSingleton(beanName, false);
if (beanInstance instanceof FactoryBean<?> factoryBean) {
return factoryBean;
}
if (isSingletonCurrentlyInCreation(beanName) ||
(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
return null;
}
Object instance;
try {
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
if (bw != null) {
return (FactoryBean<?>) bw.getWrappedInstance();
// Mark this bean as currently in creation, even if just partially.
beforeSingletonCreation(beanName);
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
instance = resolveBeforeInstantiation(beanName, mbd);
if (instance == null) {
bw = createBeanInstance(beanName, mbd, null);
instance = bw.getWrappedInstance();
this.factoryBeanInstanceCache.put(beanName, bw);
}
Object beanInstance = getSingleton(beanName, false);
if (beanInstance instanceof FactoryBean<?> factoryBean) {
return factoryBean;
}
if (isSingletonCurrentlyInCreation(beanName) ||
(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
return null;
}
Object instance;
try {
// Mark this bean as currently in creation, even if just partially.
beforeSingletonCreation(beanName);
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
instance = resolveBeforeInstantiation(beanName, mbd);
if (instance == null) {
bw = createBeanInstance(beanName, mbd, null);
instance = bw.getWrappedInstance();
this.factoryBeanInstanceCache.put(beanName, bw);
}
}
catch (UnsatisfiedDependencyException ex) {
// Don't swallow, probably misconfiguration...
}
catch (UnsatisfiedDependencyException ex) {
// Don't swallow, probably misconfiguration...
throw ex;
}
catch (BeanCreationException ex) {
// Don't swallow a linkage error since it contains a full stacktrace on
// first occurrence... and just a plain NoClassDefFoundError afterwards.
if (ex.contains(LinkageError.class)) {
throw ex;
}
catch (BeanCreationException ex) {
// Don't swallow a linkage error since it contains a full stacktrace on
// first occurrence... and just a plain NoClassDefFoundError afterwards.
if (ex.contains(LinkageError.class)) {
throw ex;
}
// Instantiation failure, maybe too early...
if (logger.isDebugEnabled()) {
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
}
onSuppressedException(ex);
return null;
// Instantiation failure, maybe too early...
if (logger.isDebugEnabled()) {
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
}
finally {
// Finished partial creation of this bean.
afterSingletonCreation(beanName);
}
return getFactoryBean(beanName, instance);
onSuppressedException(ex);
return null;
}
finally {
this.singletonLock.unlock();
// Finished partial creation of this bean.
afterSingletonCreation(beanName);
}
return getFactoryBean(beanName, instance);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,22 +54,6 @@ public class BeanDefinitionOverrideException extends BeanDefinitionStoreExceptio
this.existingDefinition = existingDefinition;
}
/**
* Create a new BeanDefinitionOverrideException for the given new and existing definition.
* @param beanName the name of the bean
* @param beanDefinition the newly registered bean definition
* @param existingDefinition the existing bean definition for the same name
* @param msg the detail message to include
* @since 6.2.1
*/
public BeanDefinitionOverrideException(
String beanName, BeanDefinition beanDefinition, BeanDefinition existingDefinition, String msg) {
super(beanDefinition.getResourceDescription(), beanName, msg);
this.beanDefinition = beanDefinition;
this.existingDefinition = existingDefinition;
}
/**
* Return the description of the resource that the bean definition came from.
@@ -177,7 +177,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
/** Set of bean definition names with a primary marker. */
// Set of bean definition names with a primary marker. */
private final Set<String> primaryBeanNames = ConcurrentHashMap.newKeySet(16);
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
@@ -1170,11 +1170,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
else {
if (logger.isInfoEnabled()) {
logger.info("Removing alias '" + beanName + "' for bean '" + aliasedName +
"' due to registration of bean definition for bean '" + beanName + "': [" +
beanDefinition + "]");
}
removeAlias(beanName);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -76,9 +76,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
/** Common lock for singleton creation. */
final Lock singletonLock = new ReentrantLock();
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
@@ -94,6 +91,8 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
/** Set of registered singletons, containing the bean names in registration order. */
private final Set<String> registeredSingletons = Collections.synchronizedSet(new LinkedHashSet<>(256));
private final Lock singletonLock = new ReentrantLock();
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet(16);
@@ -279,25 +278,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
try {
beforeSingletonCreation(beanName);
}
catch (BeanCurrentlyInCreationException ex) {
if (locked) {
throw ex;
}
// Try late locking for waiting on specific bean to be finished.
this.singletonLock.lock();
locked = true;
// Singleton object should have appeared in the meantime.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
beforeSingletonCreation(beanName);
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (locked && this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
@@ -118,45 +118,39 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
*/
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && containsSingleton(beanName)) {
this.singletonLock.lock();
try {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (for example, because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (for example, because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet
return object;
}
beforeSingletonCreation(beanName);
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
afterSingletonCreation(beanName);
}
}
else {
if (shouldPostProcess) {
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet
return object;
}
beforeSingletonCreation(beanName);
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
afterSingletonCreation(beanName);
}
}
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
return object;
}
finally {
this.singletonLock.unlock();
}
return object;
}
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Juergen Hoeller
@@ -55,6 +56,9 @@ class BeanFactoryLockingTests {
@Override
public void afterPropertiesSet() throws Exception {
Thread thread = new Thread(() -> {
// Fail for circular reference from other thread
assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy(() ->
beanFactory.getBean(ThreadDuringInitialization.class));
// Leniently create unrelated other bean outside of singleton lock
assertThat(beanFactory.getBean(TestBean.class).getName()).isEqualTo("tb");
// Creation attempt in other thread was successful
@@ -870,15 +870,10 @@ class DefaultListableBeanFactoryTests {
void beanDefinitionOverriding() {
lbf.setAllowBeanDefinitionOverriding(true);
lbf.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class));
// Override "test" bean definition.
lbf.registerBeanDefinition("test", new RootBeanDefinition(NestedTestBean.class));
// Temporary "test2" alias for nonexistent bean.
lbf.registerAlias("otherTest", "test2");
// Reassign "test2" alias to "test".
lbf.registerAlias("test", "test2");
// Assign "testX" alias to "test" as well.
lbf.registerAlias("test", "testX");
// Register new "testX" bean definition which also removes the "testX" alias for "test".
lbf.registerBeanDefinition("testX", new RootBeanDefinition(TestBean.class));
assertThat(lbf.getBean("test")).isInstanceOf(NestedTestBean.class);
@@ -36,10 +36,10 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.parsing.SourceExtractor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
@@ -297,21 +297,13 @@ class ConfigurationClassBeanDefinitionReader {
return false;
}
BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);
ConfigurationClass configClass = beanMethod.getConfigurationClass();
// If the bean method is an overloaded case on the same configuration class,
// preserve the existing bean definition and mark it as overloaded.
if (existingBeanDef instanceof ConfigurationClassBeanDefinition ccbd) {
if (ccbd.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
if (ccbd.getFactoryMethodMetadata().getMethodName().equals(beanMethod.getMetadata().getMethodName())) {
ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
}
else if (!this.registry.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName,
new ConfigurationClassBeanDefinition(configClass, beanMethod.getMetadata(), beanName),
existingBeanDef,
"@Bean method override with same bean name but different method name: " + existingBeanDef);
}
if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName()) &&
ccbd.getFactoryMethodMetadata().getMethodName().equals(beanMethod.getMetadata().getMethodName())) {
ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
return true;
}
else {
@@ -337,11 +329,9 @@ class ConfigurationClassBeanDefinitionReader {
// At this point, it's a top-level override (probably XML), just having been parsed
// before configuration class processing kicks in...
if (!this.registry.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName,
new ConfigurationClassBeanDefinition(configClass, beanMethod.getMetadata(), beanName),
existingBeanDef,
"@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " +
@@ -29,8 +29,7 @@ import org.springframework.lang.Nullable;
/**
* Declares that a field or method parameter should be formatted as a
* {@link java.time.Duration}, according to the specified {@link #style Style}
* and {@link #defaultUnit Unit}.
* {@link java.time.Duration}, according to the specified {@link #style style}.
*
* @author Simon Baslé
* @since 6.2
@@ -41,20 +40,20 @@ import org.springframework.lang.Nullable;
public @interface DurationFormat {
/**
* The {@link Style} to use for parsing and printing a {@link Duration}.
* The {@code Style} to use for parsing and printing a {@code Duration}.
* <p>Defaults to the JDK style ({@link Style#ISO8601}).
*/
Style style() default Style.ISO8601;
/**
* The {@link Unit} to fall back to in case the {@link #style Style} needs a unit
* The {@link Unit} to fall back to in case the {@code style()} needs a unit
* for either parsing or printing, and none is explicitly provided in the input.
* <p>Defaults to {@link Unit#MILLIS} if unspecified.
*/
Unit defaultUnit() default Unit.MILLIS;
/**
* {@link Duration} format styles.
* Duration format styles.
*/
enum Style {
@@ -63,7 +62,7 @@ public @interface DurationFormat {
* <p>Supported unit suffixes include: {@code ns, us, ms, s, m, h, d}.
* Those correspond to nanoseconds, microseconds, milliseconds, seconds,
* minutes, hours, and days, respectively.
* <p>Note that when printing a {@link Duration}, this style can be
* <p>Note that when printing a {@code Duration}, this style can be
* lossy if the selected unit is bigger than the resolution of the
* duration. For example, {@code Duration.ofMillis(5).plusNanos(1234)}
* would get truncated to {@code "5ms"} when printing using
@@ -74,7 +73,7 @@ public @interface DurationFormat {
/**
* ISO-8601 formatting.
* <p>This is what the JDK uses in {@link Duration#parse(CharSequence)}
* <p>This is what the JDK uses in {@link java.time.Duration#parse(CharSequence)}
* and {@link Duration#toString()}.
*/
ISO8601,
@@ -91,11 +90,11 @@ public @interface DurationFormat {
}
/**
* {@link Duration} format unit, which mirrors a subset of {@link ChronoUnit} and
* Duration format unit, which mirrors a subset of {@link ChronoUnit} and
* allows conversion to and from a supported {@code ChronoUnit} as well as
* conversion from durations to longs.
*
* <p>The enum includes its corresponding suffix in the {@link Style#SIMPLE SIMPLE}
* <p>The enum includes its corresponding suffix in the {@link Style#SIMPLE simple}
* {@code Duration} format style.
*/
enum Unit {
@@ -148,24 +147,25 @@ public @interface DurationFormat {
}
/**
* Convert this {@code Unit} to its {@link ChronoUnit} equivalent.
* Convert this {@code DurationFormat.Unit} to its {@link ChronoUnit}
* equivalent.
*/
public ChronoUnit asChronoUnit() {
return this.chronoUnit;
}
/**
* Convert this {@code Unit} to a simple {@code String} suffix, suitable
* for the {@link Style#SIMPLE SIMPLE} style.
* Convert this {@code DurationFormat.Unit} to a simple {@code String}
* suffix, suitable for the {@link Style#SIMPLE SIMPLE} style.
*/
public String asSuffix() {
return this.suffix;
}
/**
* Parse a {@code long} from the given {@link String} and interpret it to be a
* {@link Duration} in the current unit.
* @param value the {@code String} representation of the long
* Parse a {@code long} from a {@code String} and interpret it to be a
* {@code Duration} in the current unit.
* @param value the String representation of the long
* @return the corresponding {@code Duration}
*/
public Duration parse(String value) {
@@ -173,11 +173,11 @@ public @interface DurationFormat {
}
/**
* Print the given {@link Duration} as a {@link String}, converting it to a long
* Print a {@code Duration} as a {@code String}, converting it to a long
* value using this unit's precision via {@link #longValue(Duration)}
* and appending this unit's simple {@link #asSuffix() suffix}.
* @param value the {@code Duration} to convert to a {@code String}
* @return the {@code String} representation of the {@code Duration} in the
* @param value the {@code Duration} to convert to a String
* @return the String representation of the {@code Duration} in the
* {@link Style#SIMPLE SIMPLE} style
*/
public String print(Duration value) {
@@ -185,12 +185,11 @@ public @interface DurationFormat {
}
/**
* Convert the given {@link Duration} to a long value in the resolution
* of this unit.
* <p>Note that this can be lossy if the current unit is bigger than the
* actual resolution of the duration. For example,
* {@code Duration.ofMillis(5).plusNanos(1234)} would get truncated to
* {@code 5} for unit {@code MILLIS}.
* Convert the given {@code Duration} to a long value in the resolution
* of this unit. Note that this can be lossy if the current unit is
* bigger than the actual resolution of the duration.
* <p>For example, {@code Duration.ofMillis(5).plusNanos(1234)} would
* get truncated to {@code 5} for unit {@code MILLIS}.
* @param value the {@code Duration} to convert to a long
* @return the long value for the {@code Duration} in this {@code Unit}
*/
@@ -199,7 +198,7 @@ public @interface DurationFormat {
}
/**
* Get the {@link Unit} corresponding to the given {@link ChronoUnit}.
* Get the {@code Unit} corresponding to the given {@code ChronoUnit}.
* @throws IllegalArgumentException if the given {@code ChronoUnit} is
* not supported
*/
@@ -216,7 +215,7 @@ public @interface DurationFormat {
}
/**
* Get the {@link Unit} corresponding to the given {@link String} suffix.
* Get the {@code Unit} corresponding to the given {@code String} suffix.
* @throws IllegalArgumentException if the given suffix is not supported
*/
public static Unit fromSuffix(String suffix) {
@@ -123,9 +123,8 @@ abstract class ScheduledAnnotationReactiveSupport {
Publisher<?> publisher = getPublisherFor(method, targetBean);
Supplier<ScheduledTaskObservationContext> contextSupplier =
() -> new ScheduledTaskObservationContext(targetBean, method);
String displayName = targetBean.getClass().getName() + "." + method.getName();
return new SubscribingRunnable(publisher, shouldBlock, scheduled.scheduler(),
subscriptionTrackerRegistry, displayName, observationRegistrySupplier, contextSupplier);
subscriptionTrackerRegistry, observationRegistrySupplier, contextSupplier);
}
/**
@@ -193,8 +192,6 @@ abstract class ScheduledAnnotationReactiveSupport {
final boolean shouldBlock;
final String displayName;
@Nullable
private final String qualifier;
@@ -205,13 +202,12 @@ abstract class ScheduledAnnotationReactiveSupport {
final Supplier<ScheduledTaskObservationContext> contextSupplier;
SubscribingRunnable(Publisher<?> publisher, boolean shouldBlock,
@Nullable String qualifier, List<Runnable> subscriptionTrackerRegistry,
String displayName, Supplier<ObservationRegistry> observationRegistrySupplier,
Supplier<ScheduledTaskObservationContext> contextSupplier) {
@Nullable String qualifier, List<Runnable> subscriptionTrackerRegistry,
Supplier<ObservationRegistry> observationRegistrySupplier,
Supplier<ScheduledTaskObservationContext> contextSupplier) {
this.publisher = publisher;
this.shouldBlock = shouldBlock;
this.displayName = displayName;
this.qualifier = qualifier;
this.subscriptionTrackerRegistry = subscriptionTrackerRegistry;
this.observationRegistrySupplier = observationRegistrySupplier;
@@ -257,11 +253,6 @@ abstract class ScheduledAnnotationReactiveSupport {
this.publisher.subscribe(subscriber);
}
}
@Override
public String toString() {
return this.displayName;
}
}
@@ -18,8 +18,6 @@ package org.springframework.scheduling.config;
import java.time.Instant;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.util.Assert;
/**
@@ -70,7 +68,7 @@ public class Task {
}
private class OutcomeTrackingRunnable implements SchedulingAwareRunnable {
private class OutcomeTrackingRunnable implements Runnable {
private final Runnable runnable;
@@ -91,23 +89,6 @@ public class Task {
}
}
@Override
public boolean isLongLived() {
if (this.runnable instanceof SchedulingAwareRunnable sar) {
return sar.isLongLived();
}
return SchedulingAwareRunnable.super.isLongLived();
}
@Nullable
@Override
public String getQualifier() {
if (this.runnable instanceof SchedulingAwareRunnable sar) {
return sar.getQualifier();
}
return SchedulingAwareRunnable.super.getQualifier();
}
@Override
public String toString() {
return this.runnable.toString();
@@ -1060,9 +1060,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0);
List<V> list = (List<V>) CollectionFactory.createCollection(paramType, size);
for (int i = 0; i < size; i++) {
list.add(null);
}
indexes.forEach(i -> list.add(null));
for (int index : indexes) {
list.set(index, (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver));
}
@@ -1082,7 +1080,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
int startIdx = paramPath.length() + 1;
int endIdx = name.indexOf(']', startIdx);
String nestedPath = ((name.length() > endIdx + 1) ? name.substring(0, endIdx + 2) : "");
String nestedPath = name.substring(0, endIdx + 2);
boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\'');
String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx));
if (map == null) {
@@ -1116,7 +1114,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
SortedSet<Integer> indexes = null;
for (String name : valueResolver.getNames()) {
if (name.startsWith(paramPath + "[")) {
int endIndex = name.indexOf(']', paramPath.length() + 1);
int endIndex = name.indexOf(']', paramPath.length() + 2);
String rawIndex = name.substring(paramPath.length() + 1, endIndex);
int index = Integer.parseInt(rawIndex);
indexes = (indexes != null ? indexes : new TreeSet<>());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -106,7 +107,7 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
Set<Class<?>> validatedClasses = new HashSet<>();
Set<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses = new HashSet<>();
processAheadOfTime(beanClass, new HashSet<>(), validatedClasses, constraintValidatorClasses);
processAheadOfTime(beanClass, validatedClasses, constraintValidatorClasses);
if (!validatedClasses.isEmpty() || !constraintValidatorClasses.isEmpty()) {
return new AotContribution(validatedClasses, constraintValidatorClasses);
@@ -114,38 +115,27 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
return null;
}
private static void processAheadOfTime(Class<?> clazz, Set<Class<?>> visitedClasses, Set<Class<?>> validatedClasses,
Set<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses) {
private static void processAheadOfTime(Class<?> clazz, Collection<Class<?>> validatedClasses,
Collection<Class<? extends ConstraintValidator<?, ?>>> constraintValidatorClasses) {
Assert.notNull(validator, "Validator cannot be null");
if (!visitedClasses.add(clazz)) {
return;
}
Assert.notNull(validator, "Validator can't be null");
BeanDescriptor descriptor;
try {
descriptor = validator.getConstraintsForClass(clazz);
}
catch (RuntimeException | LinkageError ex) {
String className = clazz.getName();
catch (RuntimeException ex) {
if (KotlinDetector.isKotlinType(clazz) && ex instanceof ArrayIndexOutOfBoundsException) {
// See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857
if (logger.isWarnEnabled()) {
logger.warn("Skipping validation constraint hint inference for class " + className +
" due to an ArrayIndexOutOfBoundsException at validator level");
}
logger.warn("Skipping validation constraint hint inference for class " + clazz +
" due to an ArrayIndexOutOfBoundsException at validator level");
}
else if (ex instanceof TypeNotPresentException || ex instanceof NoClassDefFoundError) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping validation constraint hint inference for class %s due to a %s for %s"
.formatted(className, ex.getClass().getSimpleName(), ex.getMessage()));
}
else if (ex instanceof TypeNotPresentException) {
logger.debug("Skipping validation constraint hint inference for class " +
clazz + " due to a TypeNotPresentException at validator level: " + ex.getMessage());
}
else {
if (logger.isWarnEnabled()) {
logger.warn("Skipping validation constraint hint inference for class " + className, ex);
}
logger.warn("Skipping validation constraint hint inference for class " + clazz, ex);
}
return;
}
@@ -159,12 +149,12 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
ReflectionUtils.doWithFields(clazz, field -> {
Class<?> type = field.getType();
if (Iterable.class.isAssignableFrom(type) || Optional.class.isAssignableFrom(type)) {
if (Iterable.class.isAssignableFrom(type) || List.class.isAssignableFrom(type) || Optional.class.isAssignableFrom(type)) {
ResolvableType resolvableType = ResolvableType.forField(field);
Class<?> genericType = resolvableType.getGeneric(0).toClass();
if (shouldProcess(genericType)) {
validatedClasses.add(clazz);
processAheadOfTime(genericType, visitedClasses, validatedClasses, constraintValidatorClasses);
processAheadOfTime(genericType, validatedClasses, constraintValidatorClasses);
}
}
if (Map.class.isAssignableFrom(type)) {
@@ -173,11 +163,11 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
Class<?> valueGenericType = resolvableType.getGeneric(1).toClass();
if (shouldProcess(keyGenericType)) {
validatedClasses.add(clazz);
processAheadOfTime(keyGenericType, visitedClasses, validatedClasses, constraintValidatorClasses);
processAheadOfTime(keyGenericType, validatedClasses, constraintValidatorClasses);
}
if (shouldProcess(valueGenericType)) {
validatedClasses.add(clazz);
processAheadOfTime(valueGenericType, visitedClasses, validatedClasses, constraintValidatorClasses);
processAheadOfTime(valueGenericType, validatedClasses, constraintValidatorClasses);
}
}
});
@@ -367,7 +367,7 @@ public class MethodValidationAdapter implements MethodValidator {
container = null;
}
if (node.getKind().equals(ElementKind.PROPERTY) || node.getKind().equals(ElementKind.BEAN)) {
if (node.getKind().equals(ElementKind.PROPERTY)) {
nestedViolations
.computeIfAbsent(parameterNode, k ->
new ParamErrorsBuilder(parameter, value, container, index, key))
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.validation.beanvalidation;
import jakarta.validation.ValidationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
@@ -40,13 +39,7 @@ public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean {
super.afterPropertiesSet();
}
catch (ValidationException ex) {
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Failed to set up a Bean Validation provider", ex);
}
else if (logger.isInfoEnabled()) {
logger.info("Failed to set up a Bean Validation provider: " + ex);
}
LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex);
}
}
@@ -85,7 +85,7 @@ class AspectJAutoProxyCreatorTests {
void aspectsAreApplied() {
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
ITestBean tb = bf.getBean("adrian", ITestBean.class);
ITestBean tb = (ITestBean) bf.getBean("adrian");
assertThat(tb.getAge()).isEqualTo(68);
MethodInvokingFactoryBean factoryBean = (MethodInvokingFactoryBean) bf.getBean("&factoryBean");
assertThat(AopUtils.isAopProxy(factoryBean.getTargetObject())).isTrue();
@@ -96,7 +96,7 @@ class AspectJAutoProxyCreatorTests {
void multipleAspectsWithParameterApplied() {
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
ITestBean tb = bf.getBean("adrian", ITestBean.class);
ITestBean tb = (ITestBean) bf.getBean("adrian");
tb.setAge(10);
assertThat(tb.getAge()).isEqualTo(20);
}
@@ -105,7 +105,7 @@ class AspectJAutoProxyCreatorTests {
void aspectsAreAppliedInDefinedOrder() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithOrdering.xml");
ITestBean tb = bf.getBean("adrian", ITestBean.class);
ITestBean tb = (ITestBean) bf.getBean("adrian");
assertThat(tb.getAge()).isEqualTo(71);
}
@@ -113,8 +113,8 @@ class AspectJAutoProxyCreatorTests {
void aspectsAndAdvisorAreApplied() {
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
ITestBean shouldBeWoven = ac.getBean("adrian", ITestBean.class);
assertAspectsAndAdvisorAreApplied(ac, shouldBeWoven);
ITestBean shouldBeWeaved = (ITestBean) ac.getBean("adrian");
doTestAspectsAndAdvisorAreApplied(ac, shouldBeWeaved);
}
@Test
@@ -124,22 +124,20 @@ class AspectJAutoProxyCreatorTests {
GenericApplicationContext childAc = new GenericApplicationContext(ac);
// Create a child factory with a bean that should be woven
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
bd.getPropertyValues()
.addPropertyValue(new PropertyValue("name", "Adrian"))
bd.getPropertyValues().addPropertyValue(new PropertyValue("name", "Adrian"))
.addPropertyValue(new PropertyValue("age", 34));
childAc.registerBeanDefinition("adrian2", bd);
// Register the advisor auto proxy creator with subclass
childAc.registerBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class.getName(),
new RootBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class));
childAc.registerBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class.getName(), new RootBeanDefinition(
AnnotationAwareAspectJAutoProxyCreator.class));
childAc.refresh();
ITestBean beanFromParentContextThatShouldBeWoven = ac.getBean("adrian", ITestBean.class);
ITestBean beanFromChildContextThatShouldBeWoven = childAc.getBean("adrian2", ITestBean.class);
assertAspectsAndAdvisorAreApplied(childAc, beanFromParentContextThatShouldBeWoven);
assertAspectsAndAdvisorAreApplied(childAc, beanFromChildContextThatShouldBeWoven);
ITestBean beanFromChildContextThatShouldBeWeaved = (ITestBean) childAc.getBean("adrian2");
//testAspectsAndAdvisorAreApplied(childAc, (ITestBean) ac.getBean("adrian"));
doTestAspectsAndAdvisorAreApplied(childAc, beanFromChildContextThatShouldBeWeaved);
}
protected void assertAspectsAndAdvisorAreApplied(ApplicationContext ac, ITestBean shouldBeWoven) {
protected void doTestAspectsAndAdvisorAreApplied(ApplicationContext ac, ITestBean shouldBeWeaved) {
TestBeanAdvisor tba = (TestBeanAdvisor) ac.getBean("advisor");
MultiplyReturnValue mrv = (MultiplyReturnValue) ac.getBean("aspect");
@@ -148,10 +146,10 @@ class AspectJAutoProxyCreatorTests {
tba.count = 0;
mrv.invocations = 0;
assertThat(AopUtils.isAopProxy(shouldBeWoven)).as("Autoproxying must apply from @AspectJ aspect").isTrue();
assertThat(shouldBeWoven.getName()).isEqualTo("Adrian");
assertThat(AopUtils.isAopProxy(shouldBeWeaved)).as("Autoproxying must apply from @AspectJ aspect").isTrue();
assertThat(shouldBeWeaved.getName()).isEqualTo("Adrian");
assertThat(mrv.invocations).isEqualTo(0);
assertThat(shouldBeWoven.getAge()).isEqualTo((34 * mrv.getMultiple()));
assertThat(shouldBeWeaved.getAge()).isEqualTo((34 * mrv.getMultiple()));
assertThat(tba.count).as("Spring advisor must be invoked").isEqualTo(2);
assertThat(mrv.invocations).as("Must be able to hold state in aspect").isEqualTo(1);
}
@@ -160,13 +158,13 @@ class AspectJAutoProxyCreatorTests {
void perThisAspect() {
ClassPathXmlApplicationContext bf = newContext("perthis.xml");
ITestBean adrian1 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
assertThat(AopUtils.isAopProxy(adrian1)).isTrue();
assertThat(adrian1.getAge()).isEqualTo(0);
assertThat(adrian1.getAge()).isEqualTo(1);
ITestBean adrian2 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertThat(adrian2).isNotSameAs(adrian1);
assertThat(AopUtils.isAopProxy(adrian1)).isTrue();
assertThat(adrian2.getAge()).isEqualTo(0);
@@ -180,7 +178,7 @@ class AspectJAutoProxyCreatorTests {
void perTargetAspect() throws SecurityException, NoSuchMethodException {
ClassPathXmlApplicationContext bf = newContext("pertarget.xml");
ITestBean adrian1 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
assertThat(AopUtils.isAopProxy(adrian1)).isTrue();
// Does not trigger advice or count
@@ -201,7 +199,7 @@ class AspectJAutoProxyCreatorTests {
adrian1.setName("Adrian");
//assertEquals("Any other setter does not increment", 2, adrian1.getAge());
ITestBean adrian2 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertThat(adrian2).isNotSameAs(adrian1);
assertThat(AopUtils.isAopProxy(adrian1)).isTrue();
assertThat(adrian2.getAge()).isEqualTo(34);
@@ -241,7 +239,7 @@ class AspectJAutoProxyCreatorTests {
void twoAdviceAspect() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspect.xml");
ITestBean adrian1 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testAgeAspect(adrian1, 0, 2);
}
@@ -249,9 +247,9 @@ class AspectJAutoProxyCreatorTests {
void twoAdviceAspectSingleton() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectSingleton.xml");
ITestBean adrian1 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testAgeAspect(adrian1, 0, 1);
ITestBean adrian2 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertThat(adrian2).isNotSameAs(adrian1);
testAgeAspect(adrian2, 2, 1);
}
@@ -260,9 +258,9 @@ class AspectJAutoProxyCreatorTests {
void twoAdviceAspectPrototype() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectPrototype.xml");
ITestBean adrian1 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testAgeAspect(adrian1, 0, 1);
ITestBean adrian2 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertThat(adrian2).isNotSameAs(adrian1);
testAgeAspect(adrian2, 0, 1);
}
@@ -282,7 +280,7 @@ class AspectJAutoProxyCreatorTests {
void adviceUsingJoinPoint() {
ClassPathXmlApplicationContext bf = newContext("usesJoinPointAspect.xml");
ITestBean adrian1 = bf.getBean("adrian", ITestBean.class);
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
adrian1.getAge();
AdviceUsingThisJoinPoint aspectInstance = (AdviceUsingThisJoinPoint) bf.getBean("aspect");
//(AdviceUsingThisJoinPoint) Aspects.aspectOf(AdviceUsingThisJoinPoint.class);
@@ -294,7 +292,7 @@ class AspectJAutoProxyCreatorTests {
void includeMechanism() {
ClassPathXmlApplicationContext bf = newContext("usesInclude.xml");
ITestBean adrian = bf.getBean("adrian", ITestBean.class);
ITestBean adrian = (ITestBean) bf.getBean("adrian");
assertThat(AopUtils.isAopProxy(adrian)).isTrue();
assertThat(adrian.getAge()).isEqualTo(68);
}
@@ -312,7 +310,7 @@ class AspectJAutoProxyCreatorTests {
void withAbstractFactoryBeanAreApplied() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithAbstractBean.xml");
ITestBean adrian = bf.getBean("adrian", ITestBean.class);
ITestBean adrian = (ITestBean) bf.getBean("adrian");
assertThat(AopUtils.isAopProxy(adrian)).isTrue();
assertThat(adrian.getAge()).isEqualTo(68);
}
@@ -323,7 +321,8 @@ class AspectJAutoProxyCreatorTests {
UnreliableBean bean = (UnreliableBean) bf.getBean("unreliableBean");
RetryAspect aspect = (RetryAspect) bf.getBean("retryAspect");
assertThat(bean.unreliable()).isEqualTo(2);
int attempts = bean.unreliable();
assertThat(attempts).isEqualTo(2);
assertThat(aspect.getBeginCalls()).isEqualTo(2);
assertThat(aspect.getRollbackCalls()).isEqualTo(1);
assertThat(aspect.getCommitCalls()).isEqualTo(1);
@@ -333,7 +332,7 @@ class AspectJAutoProxyCreatorTests {
void withBeanNameAutoProxyCreator() {
ClassPathXmlApplicationContext bf = newContext("withBeanNameAutoProxyCreator.xml");
ITestBean tb = bf.getBean("adrian", ITestBean.class);
ITestBean tb = (ITestBean) bf.getBean("adrian");
assertThat(tb.getAge()).isEqualTo(68);
}
@@ -1,98 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.framework.autoproxy;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.jupiter.api.Test;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.RootClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link DefaultAdvisorAutoProxyCreator}.
*
* @author Sam Brannen
* @since 6.2.1
*/
class DefaultAdvisorAutoProxyCreatorTests {
/**
* Indirectly tests behavior of {@link org.springframework.aop.framework.AdvisedSupport.MethodCacheKey}.
* @see StaticMethodMatcherPointcut#matches(Method, Class)
*/
@Test // gh-33915
void staticMethodMatcherPointcutMatchesMethodIsNotInvokedAgainForActualMethodInvocation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DemoBean.class, DemoPointcutAdvisor.class, DefaultAdvisorAutoProxyCreator.class);
DemoPointcutAdvisor demoPointcutAdvisor = context.getBean(DemoPointcutAdvisor.class);
DemoBean demoBean = context.getBean(DemoBean.class);
assertThat(demoPointcutAdvisor.matchesInvocationCount).as("matches() invocations before").isEqualTo(2);
// Invoke multiple times to ensure additional invocations don't affect the outcome.
assertThat(demoBean.sayHello()).isEqualTo("Advised: Hello!");
assertThat(demoBean.sayHello()).isEqualTo("Advised: Hello!");
assertThat(demoBean.sayHello()).isEqualTo("Advised: Hello!");
assertThat(demoPointcutAdvisor.matchesInvocationCount).as("matches() invocations after").isEqualTo(2);
context.close();
}
static class DemoBean {
public String sayHello() {
return "Hello!";
}
}
@SuppressWarnings("serial")
static class DemoPointcutAdvisor extends AbstractPointcutAdvisor {
int matchesInvocationCount = 0;
@Override
public Pointcut getPointcut() {
StaticMethodMatcherPointcut pointcut = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
if (method.getName().equals("sayHello")) {
matchesInvocationCount++;
return true;
}
return false;
}
};
pointcut.setClassFilter(new RootClassFilter(DemoBean.class));
return pointcut;
}
@Override
public Advice getAdvice() {
return (MethodInterceptor) invocation -> "Advised: " + invocation.proceed();
}
}
}
@@ -110,7 +110,7 @@ class ConfigurationClassProcessingTests {
private void aliasesAreRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) {
TestBean testBean = testBeanSupplier.get();
BeanFactory factory = initBeanFactory(false, testClass);
BeanFactory factory = initBeanFactory(testClass);
assertThat(factory.getBean(beanName)).isSameAs(testBean);
Arrays.stream(factory.getAliases(beanName)).map(factory::getBean).forEach(alias -> assertThat(alias).isSameAs(testBean));
@@ -141,30 +141,30 @@ class ConfigurationClassProcessingTests {
@Test
void finalBeanMethod() {
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
initBeanFactory(false, ConfigWithFinalBean.class));
initBeanFactory(ConfigWithFinalBean.class));
}
@Test
void finalBeanMethodWithoutProxy() {
initBeanFactory(false, ConfigWithFinalBeanWithoutProxy.class);
initBeanFactory(ConfigWithFinalBeanWithoutProxy.class);
}
@Test // gh-31007
void voidBeanMethod() {
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
initBeanFactory(false, ConfigWithVoidBean.class));
initBeanFactory(ConfigWithVoidBean.class));
}
@Test
void simplestPossibleConfig() {
BeanFactory factory = initBeanFactory(false, SimplestPossibleConfig.class);
BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class);
String stringBean = factory.getBean("stringBean", String.class);
assertThat(stringBean).isEqualTo("foo");
}
@Test
void configWithObjectReturnType() {
BeanFactory factory = initBeanFactory(false, ConfigWithNonSpecificReturnTypes.class);
BeanFactory factory = initBeanFactory(ConfigWithNonSpecificReturnTypes.class);
assertThat(factory.getType("stringBean")).isEqualTo(Object.class);
assertThat(factory.isTypeMatch("stringBean", String.class)).isFalse();
String stringBean = factory.getBean("stringBean", String.class);
@@ -173,7 +173,7 @@ class ConfigurationClassProcessingTests {
@Test
void configWithFactoryBeanReturnType() {
ListableBeanFactory factory = initBeanFactory(false, ConfigWithNonSpecificReturnTypes.class);
ListableBeanFactory factory = initBeanFactory(ConfigWithNonSpecificReturnTypes.class);
assertThat(factory.getType("factoryBean")).isEqualTo(List.class);
assertThat(factory.isTypeMatch("factoryBean", List.class)).isTrue();
assertThat(factory.getType("&factoryBean")).isEqualTo(FactoryBean.class);
@@ -201,7 +201,7 @@ class ConfigurationClassProcessingTests {
@Test
void configurationWithPrototypeScopedBeans() {
BeanFactory factory = initBeanFactory(false, ConfigWithPrototypeBean.class);
BeanFactory factory = initBeanFactory(ConfigWithPrototypeBean.class);
TestBean foo = factory.getBean("foo", TestBean.class);
ITestBean bar = factory.getBean("bar", ITestBean.class);
@@ -213,7 +213,7 @@ class ConfigurationClassProcessingTests {
@Test
void configurationWithNullReference() {
BeanFactory factory = initBeanFactory(false, ConfigWithNullReference.class);
BeanFactory factory = initBeanFactory(ConfigWithNullReference.class);
TestBean foo = factory.getBean("foo", TestBean.class);
assertThat(factory.getBean("bar")).isEqualTo(null);
@@ -223,15 +223,7 @@ class ConfigurationClassProcessingTests {
@Test // gh-33330
void configurationWithMethodNameMismatch() {
assertThatExceptionOfType(BeanDefinitionOverrideException.class)
.isThrownBy(() -> initBeanFactory(false, ConfigWithMethodNameMismatch.class));
}
@Test // gh-33920
void configurationWithMethodNameMismatchAndOverridingAllowed() {
BeanFactory factory = initBeanFactory(true, ConfigWithMethodNameMismatch.class);
SpousyTestBean foo = factory.getBean("foo", SpousyTestBean.class);
assertThat(foo.getName()).isEqualTo("foo1");
.isThrownBy(() -> initBeanFactory(ConfigWithMethodNameMismatch.class));
}
@Test
@@ -361,13 +353,13 @@ class ConfigurationClassProcessingTests {
* When complete, the factory is ready to service requests for any {@link Bean} methods
* declared by {@code configClasses}.
*/
private DefaultListableBeanFactory initBeanFactory(boolean allowOverriding, Class<?>... configClasses) {
private DefaultListableBeanFactory initBeanFactory(Class<?>... configClasses) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
for (Class<?> configClass : configClasses) {
String configBeanName = configClass.getName();
factory.registerBeanDefinition(configBeanName, new RootBeanDefinition(configClass));
}
factory.setAllowBeanDefinitionOverriding(allowOverriding);
factory.setAllowBeanDefinitionOverriding(false);
ConfigurationClassPostProcessor ccpp = new ConfigurationClassPostProcessor();
ccpp.postProcessBeanDefinitionRegistry(factory);
ccpp.postProcessBeanFactory(factory);
@@ -545,12 +537,12 @@ class ConfigurationClassProcessingTests {
@Configuration
static class ConfigWithMethodNameMismatch {
@Bean(name = "foo") public TestBean foo1() {
return new SpousyTestBean("foo1");
@Bean(name = "foo") public TestBean foo() {
return new SpousyTestBean("foo");
}
@Bean(name = "foo") public TestBean foo2() {
return new SpousyTestBean("foo2");
@Bean(name = "foo") public TestBean fooX() {
return new SpousyTestBean("fooX");
}
}
@@ -66,10 +66,8 @@ import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.context.testfixture.context.annotation.AutowiredCglibConfiguration;
import org.springframework.context.testfixture.context.annotation.AutowiredComponent;
import org.springframework.context.testfixture.context.annotation.AutowiredGenericTemplate;
import org.springframework.context.testfixture.context.annotation.AutowiredMixedCglibConfiguration;
import org.springframework.context.testfixture.context.annotation.CglibConfiguration;
import org.springframework.context.testfixture.context.annotation.ConfigurableCglibConfiguration;
import org.springframework.context.testfixture.context.annotation.GenericTemplateConfiguration;
@@ -84,7 +82,6 @@ import org.springframework.context.testfixture.context.annotation.LazyResourceMe
import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration;
import org.springframework.context.testfixture.context.annotation.QualifierConfiguration;
import org.springframework.context.testfixture.context.annotation.ResourceComponent;
import org.springframework.context.testfixture.context.annotation.ValueCglibConfiguration;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@@ -439,14 +436,12 @@ class ApplicationContextAotGeneratorTests {
@CompileWithForkedClassLoader
class ConfigurationClassCglibProxy {
private static final String CGLIB_CONFIGURATION_CLASS_SUFFIX = "$$SpringCGLIB$$0";
@Test
void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
TestGenerationContext context = processAheadOfTime(applicationContext);
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + CGLIB_CONFIGURATION_CLASS_SUFFIX);
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$0");
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$FastClass$$0");
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$FastClass$$1");
}
@@ -458,43 +453,6 @@ class ApplicationContextAotGeneratorTests {
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
}
@Test
void processAheadOfTimeExposeUserClassForCglibProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean("config", ValueCglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext).satisfies(hasBeanDefinitionOfBeanClass("config", ValueCglibConfiguration.class));
assertThat(compiled.getSourceFile(".*ValueCglibConfiguration__BeanDefinitions"))
.contains("new RootBeanDefinition(ValueCglibConfiguration.class)")
.contains("new %s(".formatted(toCglibClassSimpleName(ValueCglibConfiguration.class)));
});
}
@Test
void processAheadOfTimeUsesCglibClassForFactoryMethod() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean("config", CglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext).satisfies(hasBeanDefinitionOfBeanClass("config", CglibConfiguration.class));
assertThat(compiled.getSourceFile(".*CglibConfiguration__BeanDefinitions"))
.contains("new RootBeanDefinition(CglibConfiguration.class)")
.contains(">forFactoryMethod(%s.class,".formatted(toCglibClassSimpleName(CglibConfiguration.class)))
.doesNotContain(">forFactoryMethod(%s.class,".formatted(CglibConfiguration.class));
});
}
private Consumer<GenericApplicationContext> hasBeanDefinitionOfBeanClass(String name, Class<?> beanClass) {
return context -> {
assertThat(context.containsBean(name)).isTrue();
assertThat(context.getBeanDefinition(name)).isInstanceOfSatisfying(RootBeanDefinition.class,
rbd -> assertThat(rbd.getBeanClass()).isEqualTo(beanClass));
};
}
@Test
void processAheadOfTimeWhenHasCglibProxyUseProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
@@ -506,47 +464,6 @@ class ApplicationContextAotGeneratorTests {
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyAndAutowiring() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(AutowiredCglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> {
context.setEnvironment(new MockEnvironment().withProperty("hello", "Hi"));
initializer.initialize(context);
});
assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hi World");
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyAndMixedAutowiring() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(AutowiredMixedCglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> {
context.setEnvironment(new MockEnvironment().withProperty("hello", "Hi")
.withProperty("world", "AOT World"));
initializer.initialize(context);
});
assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hi AOT World");
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyWithAnnotationsOnTheUserClasConstructor() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean("config", ValueCglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> {
context.setEnvironment(new MockEnvironment().withProperty("name", "AOT World"));
initializer.initialize(context);
});
assertThat(freshApplicationContext.getBean(ValueCglibConfiguration.class)
.getName()).isEqualTo("AOT World");
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
@@ -570,10 +487,6 @@ class ApplicationContextAotGeneratorTests {
.accepts(generationContext.getRuntimeHints());
}
private String toCglibClassSimpleName(Class<?> configClass) {
return configClass.getSimpleName() + CGLIB_CONFIGURATION_CLASS_SUFFIX;
}
}
@Nested
@@ -213,8 +213,8 @@ class ApplicationListenerMethodAdapterTests extends AbstractApplicationEventList
@Test
void invokeListenerWithWrongGenericPayload() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericStringPayload", EntityWrapper.class);
Method method = ReflectionUtils.findMethod
(SampleEvents.class, "handleGenericStringPayload", EntityWrapper.class);
EntityWrapper<Integer> payload = new EntityWrapper<>(123);
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
verify(this.sampleEvents, times(0)).handleGenericStringPayload(any());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ public class EventCollector {
/**
* Return the events that the specified listener has received. The list of events
* is ordered according to the order in which they were received.
* is ordered according to their reception order.
*/
public List<Object> getEvents(Identifiable listener) {
return this.content.get(listener.getId());
@@ -67,7 +67,7 @@ class ConversionServiceFactoryBeanTests {
converters.add(new ConverterFactory<String, Bar>() {
@Override
public <T extends Bar> Converter<String, T> getConverter(Class<T> targetType) {
return new Converter<>() {
return new Converter<> () {
@SuppressWarnings("unchecked")
@Override
public T convert(String source) {
@@ -42,7 +42,7 @@ import static org.springframework.format.annotation.DurationFormat.Style.SIMPLE;
class DurationFormatterUtilsTests {
@ParameterizedTest
@EnumSource
@EnumSource(DurationFormat.Style.class)
void parseEmptyStringFailsWithDedicatedException(DurationFormat.Style style) {
assertThatIllegalArgumentException()
.isThrownBy(() -> DurationFormatterUtils.parse("", style))
@@ -50,7 +50,7 @@ class DurationFormatterUtilsTests {
}
@ParameterizedTest
@EnumSource
@EnumSource(DurationFormat.Style.class)
void parseNullStringFailsWithDedicatedException(DurationFormat.Style style) {
assertThatIllegalArgumentException()
.isThrownBy(() -> DurationFormatterUtils.parse(null, style))
@@ -113,30 +113,29 @@ class DurationFormatterUtilsTests {
@Test
void parseIsoNoChronoUnit() {
// These are based on the examples given in Duration.parse.
// "PT20.345S" -- parses as "20.345 seconds"
//these are based on the examples given in Duration.parse
// "PT20.345S" -- parses as "20.345 seconds"
assertThat(DurationFormatterUtils.parse("PT20.345S", ISO8601))
.hasMillis(20345);
// "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
// "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
assertThat(DurationFormatterUtils.parse("PT15M", ISO8601))
.hasSeconds(15*60);
// "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
// "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
assertThat(DurationFormatterUtils.parse("PT10H", ISO8601))
.hasHours(10);
// "P2D" -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
// "P2D" -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
assertThat(DurationFormatterUtils.parse("P2D", ISO8601))
.hasDays(2);
// "P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes"
// "P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes"
assertThat(DurationFormatterUtils.parse("P2DT3H4M", ISO8601))
.isEqualTo(Duration.ofDays(2).plusHours(3).plusMinutes(4));
// "PT-6H3M" -- parses as "-6 hours and +3 minutes"
// "PT-6H3M" -- parses as "-6 hours and +3 minutes"
assertThat(DurationFormatterUtils.parse("PT-6H3M", ISO8601))
.isEqualTo(Duration.ofHours(-6).plusMinutes(3));
// "-PT6H3M" -- parses as "-6 hours and -3 minutes"
// "-PT6H3M" -- parses as "-6 hours and -3 minutes"
assertThat(DurationFormatterUtils.parse("-PT6H3M", ISO8601))
.isEqualTo(Duration.ofHours(-6).plusMinutes(-3));
// "-PT-6H+3M" -- parses as "+6 hours and -3 minutes"
// "-PT-6H+3M" -- parses as "+6 hours and -3 minutes"
assertThat(DurationFormatterUtils.parse("-PT-6H+3M", ISO8601))
.isEqualTo(Duration.ofHours(6).plusMinutes(-3));
}
@@ -190,7 +189,7 @@ class DurationFormatterUtilsTests {
.isEqualTo(Duration.ofMinutes(34).plusSeconds(57));
}
@Test // Kotlin style compatibility
@Test //Kotlin style compatibility
void parseCompositeNegativeWithSpacesAndParenthesis() {
assertThat(DurationFormatterUtils.parse("-(34m 57s)", COMPOSITE))
.isEqualTo(Duration.ofMinutes(-34).plusSeconds(-57));
@@ -316,7 +315,7 @@ class DurationFormatterUtilsTests {
assertThat(DurationFormatterUtils.detect("-(1d 2h 34m 2ns)"))
.as("COMPOSITE")
.isEqualTo(COMPOSITE);
.isEqualTo(COMPOSITE);
assertThatIllegalArgumentException().isThrownBy(() -> DurationFormatterUtils.detect("WPT2H-4M"))
.withMessage("'WPT2H-4M' is not a valid duration, cannot detect any known style")
@@ -175,7 +175,8 @@ class ScheduledAnnotationBeanPostProcessorObservabilityTests {
}
private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatTaskObservation() {
return assertThat(this.observationRegistry).hasObservationWithNameEqualTo("tasks.scheduled.execution").that();
return TestObservationRegistryAssert.assertThat(this.observationRegistry)
.hasObservationWithNameEqualTo("tasks.scheduled.execution").that();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -173,17 +173,6 @@ class ScheduledAnnotationReactiveSupportTests {
assertThat(p).hasToString("checkpoint(\"@Scheduled 'mono()' in 'org.springframework.scheduling.annotation.ScheduledAnnotationReactiveSupportTests$ReactiveMethods'\")");
}
@Test
void shouldProvideToString() {
ReactiveMethods target = new ReactiveMethods();
Method m = ReflectionUtils.findMethod(ReactiveMethods.class, "mono");
Scheduled cron = AnnotationUtils.synthesizeAnnotation(Map.of("cron", "-"), Scheduled.class, null);
List<Runnable> tracker = new ArrayList<>();
assertThat(createSubscriptionRunnable(m, target, cron, () -> ObservationRegistry.NOOP, tracker))
.hasToString("org.springframework.scheduling.annotation.ScheduledAnnotationReactiveSupportTests$ReactiveMethods.mono");
}
static class ReactiveMethods {
@@ -125,7 +125,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableRunnable() {
TestTask task = new TestTask(this.testName, 1);
// Act
@@ -156,7 +156,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings({ "deprecation", "removal" })
void submitFailingListenableRunnable() {
TestTask task = new TestTask(this.testName, 0);
org.springframework.util.concurrent.ListenableFuture<?> future = executor.submitListenable(task);
@@ -185,7 +185,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableRunnableWithGetAfterShutdown() throws Exception {
org.springframework.util.concurrent.ListenableFuture<?> future1 = executor.submitListenable(new TestTask(this.testName, -1));
org.springframework.util.concurrent.ListenableFuture<?> future2 = executor.submitListenable(new TestTask(this.testName, -1));
@@ -209,10 +209,18 @@ abstract class AbstractSchedulingTaskExecutorTests {
CompletableFuture<?> future1 = executor.submitCompletable(new TestTask(this.testName, -1));
CompletableFuture<?> future2 = executor.submitCompletable(new TestTask(this.testName, -1));
shutdownExecutor();
assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> {
try {
future1.get(1000, TimeUnit.MILLISECONDS);
future2.get(1000, TimeUnit.MILLISECONDS);
});
}
catch (Exception ex) {
// ignore
}
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.pollInterval(10, TimeUnit.MILLISECONDS)
.untilAsserted(() -> assertThatExceptionOfType(TimeoutException.class)
.isThrownBy(() -> future2.get(1000, TimeUnit.MILLISECONDS)));
}
@Test
@@ -252,7 +260,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableCallable() {
TestCallable task = new TestCallable(this.testName, 1);
// Act
@@ -267,7 +275,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings({ "deprecation", "removal" })
void submitFailingListenableCallable() {
TestCallable task = new TestCallable(this.testName, 0);
// Act
@@ -283,7 +291,7 @@ abstract class AbstractSchedulingTaskExecutorTests {
}
@Test
@SuppressWarnings("removal")
@SuppressWarnings({ "deprecation", "removal" })
void submitListenableCallableWithGetAfterShutdown() throws Exception {
org.springframework.util.concurrent.ListenableFuture<?> future1 = executor.submitListenable(new TestCallable(this.testName, -1));
org.springframework.util.concurrent.ListenableFuture<?> future2 = executor.submitListenable(new TestCallable(this.testName, -1));
@@ -16,12 +16,8 @@
package org.springframework.scheduling.config;
import io.micrometer.observation.tck.TestObservationRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.scheduling.SchedulingAwareRunnable;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -76,18 +72,6 @@ class TaskTests {
assertThat(executionOutcome.throwable()).isInstanceOf(IllegalStateException.class);
}
@Test
void shouldDelegateToSchedulingAwareRunnable() throws Exception {
ScheduledMethodRunnable methodRunnable = new ScheduledMethodRunnable(new TestRunnable(),
TestRunnable.class.getMethod("run"), "myScheduler", TestObservationRegistry::create);
Task task = new Task(methodRunnable);
assertThat(task.getRunnable()).isInstanceOf(SchedulingAwareRunnable.class);
SchedulingAwareRunnable actual = (SchedulingAwareRunnable) task.getRunnable();
assertThat(actual.getQualifier()).isEqualTo(methodRunnable.getQualifier());
assertThat(actual.isLongLived()).isEqualTo(methodRunnable.isLongLived());
}
static class TestRunnable implements Runnable {
@@ -121,24 +121,6 @@ class DataBinderConstructTests {
assertThat(list.get(2).param1()).isEqualTo("value3");
}
@Test // gh-34145
void listBindingWithNonconsecutiveIndices() {
MapValueResolver valueResolver = new MapValueResolver(Map.of(
"dataClassList[0].param1", "value1", "dataClassList[0].param2", "true",
"dataClassList[1].param1", "value2", "dataClassList[1].param2", "true",
"dataClassList[3].param1", "value3", "dataClassList[3].param2", "true"));
DataBinder binder = initDataBinder(ListDataClass.class);
binder.construct(valueResolver);
ListDataClass dataClass = getTarget(binder);
List<DataClass> list = dataClass.dataClassList();
assertThat(list.get(0).param1()).isEqualTo("value1");
assertThat(list.get(1).param1()).isEqualTo("value2");
assertThat(list.get(3).param1()).isEqualTo("value3");
}
@Test
void mapBinding() {
MapValueResolver valueResolver = new MapValueResolver(Map.of(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
@@ -33,8 +31,6 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.Pattern;
import org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
@@ -44,7 +40,6 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.OverridingClassLoader;
import org.springframework.lang.Nullable;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
@@ -126,23 +121,6 @@ class BeanValidationBeanRegistrationAotProcessorTests {
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.generationContext.getRuntimeHints());
}
@ParameterizedTest // gh-33936
@ValueSource(classes = {BeanWithRecursiveIterable.class, BeanWithRecursiveMap.class, BeanWithRecursiveOptional.class})
void shouldProcessRecursiveGenericsWithoutInfiniteRecursion(Class<?> beanClass) {
process(beanClass);
assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(1);
assertThat(RuntimeHintsPredicates.reflection().onType(beanClass)
.withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints());
}
@Test // gh-33940
void shouldSkipConstraintWithMissingDependency() throws Exception {
MissingDependencyClassLoader classLoader = new MissingDependencyClassLoader(getClass().getClassLoader());
Class<?> beanClass = classLoader.loadClass(ConstraintWithMissingDependency.class.getName());
process(beanClass);
assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty();
}
private void process(Class<?> beanClass) {
BeanRegistrationAotContribution contribution = createContribution(beanClass);
if (contribution != null) {
@@ -266,43 +244,4 @@ class BeanValidationBeanRegistrationAotProcessorTests {
}
}
static class BeanWithRecursiveIterable {
Iterable<BeanWithRecursiveIterable> iterable;
}
static class BeanWithRecursiveMap {
Map<BeanWithRecursiveMap, BeanWithRecursiveMap> map;
}
static class BeanWithRecursiveOptional {
Optional<BeanWithRecursiveOptional> optional;
}
static class ConstraintWithMissingDependency {
MissingType missingType;
}
static class MissingType {}
static class MissingDependencyClassLoader extends OverridingClassLoader {
MissingDependencyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected boolean isEligibleForOverriding(String className) {
return className.startsWith(BeanValidationBeanRegistrationAotProcessorTests.class.getName());
}
@Override
protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException {
if (name.contains("MissingType")) {
throw new NoClassDefFoundError(name);
}
return super.loadClassForOverriding(name);
}
}
}
@@ -1,35 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class AutowiredCglibConfiguration {
@Autowired
private Environment environment;
@Bean
public String text() {
return this.environment.getProperty("hello") + " World";
}
}
@@ -1,41 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class AutowiredMixedCglibConfiguration {
@Value("${world:World}")
private String world;
private final Environment environment;
public AutowiredMixedCglibConfiguration(Environment environment) {
this.environment = environment;
}
@Bean
public String text() {
return this.environment.getProperty("hello") + " " + this.world;
}
}
@@ -1,34 +0,0 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ValueCglibConfiguration {
private final String name;
public ValueCglibConfiguration(@Value("${name:World}") String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,14 +124,6 @@ public enum MemberCategory {
* reflection for inner classes but rather makes sure they are available
* via a call to {@link Class#getDeclaredClasses}.
*/
DECLARED_CLASSES,
/**
* A category that represents the need for
* {@link sun.misc.Unsafe#allocateInstance(Class) unsafe allocation}
* for this type.
* @since 6.2.1
*/
UNSAFE_ALLOCATED
DECLARED_CLASSES
}
@@ -124,7 +124,6 @@ class ReflectionHintsWriter {
attributes.put("allDeclaredMethods", true);
case PUBLIC_CLASSES -> attributes.put("allPublicClasses", true);
case DECLARED_CLASSES -> attributes.put("allDeclaredClasses", true);
case UNSAFE_ALLOCATED -> attributes.put("unsafeAllocated", true);
}
}
);
@@ -44,6 +44,7 @@ import kotlinx.coroutines.reactor.ReactorFlowKt;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -108,7 +109,7 @@ public abstract class CoroutinesUtils {
* @throws IllegalArgumentException if {@code method} is not a suspending function
* @since 6.0
*/
@SuppressWarnings({"DataFlowIssue", "NullAway"})
@SuppressWarnings({"deprecation", "DataFlowIssue", "NullAway"})
public static Publisher<?> invokeSuspendingFunction(
CoroutineContext context, Method method, @Nullable Object target, @Nullable Object... args) {
@@ -145,7 +146,7 @@ public abstract class CoroutinesUtils {
}
return KCallables.callSuspendBy(function, argMap, continuation);
})
.filter(result -> result != Unit.INSTANCE)
.handle(CoroutinesUtils::handleResult)
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
KType returnType = function.getReturnType();
@@ -165,4 +166,22 @@ public abstract class CoroutinesUtils {
return ReactorFlowKt.asFlux(((Flow<?>) flow));
}
private static void handleResult(Object result, SynchronousSink<Object> sink) {
if (result == Unit.INSTANCE) {
sink.complete();
}
else if (KotlinDetector.isInlineClass(result.getClass())) {
try {
sink.next(result.getClass().getDeclaredMethod("unbox-impl").invoke(result));
sink.complete();
}
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
sink.error(ex);
}
}
else {
sink.next(result);
sink.complete();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -180,7 +180,7 @@ public final class GenericTypeResolver {
generics[i] = resolvedTypeArgument;
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolveType();
generics[i] = ResolvableType.forType(typeArgument);
}
}
else if (typeArgument instanceof ParameterizedType) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -334,19 +334,19 @@ public class ResolvableType implements Serializable {
// Deal with wildcard bounds
WildcardBounds ourBounds = WildcardBounds.get(this);
WildcardBounds otherBounds = WildcardBounds.get(other);
WildcardBounds typeBounds = WildcardBounds.get(other);
// In the form X is assignable to <? extends Number>
if (otherBounds != null) {
if (typeBounds != null) {
if (ourBounds != null) {
return (ourBounds.isSameKind(otherBounds) &&
ourBounds.isAssignableFrom(otherBounds.getBounds(), matchedBefore));
return (ourBounds.isSameKind(typeBounds) &&
ourBounds.isAssignableFrom(typeBounds.getBounds(), matchedBefore));
}
else if (upUntilUnresolvable) {
return otherBounds.isAssignableFrom(this, matchedBefore);
return typeBounds.isAssignableFrom(this, matchedBefore);
}
else if (!exactMatch) {
return otherBounds.isAssignableTo(this, matchedBefore);
return typeBounds.isAssignableTo(this, matchedBefore);
}
else {
return false;
@@ -400,8 +400,8 @@ public class ResolvableType implements Serializable {
if (checkGenerics) {
// Recursively check each generic
ResolvableType[] ourGenerics = getGenerics();
ResolvableType[] otherGenerics = other.as(ourResolved).getGenerics();
if (ourGenerics.length != otherGenerics.length) {
ResolvableType[] typeGenerics = other.as(ourResolved).getGenerics();
if (ourGenerics.length != typeGenerics.length) {
return false;
}
if (ourGenerics.length > 0) {
@@ -410,9 +410,7 @@ public class ResolvableType implements Serializable {
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
ResolvableType otherGeneric = otherGenerics[i];
if (!ourGenerics[i].isAssignableFrom(otherGeneric,
!otherGeneric.isUnresolvableTypeVariable(), matchedBefore, upUntilUnresolvable)) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore, upUntilUnresolvable)) {
return false;
}
}
@@ -1730,16 +1728,8 @@ public class ResolvableType implements Serializable {
* @return {@code true} if these bounds are assignable from all types
*/
public boolean isAssignableFrom(ResolvableType[] types, @Nullable Map<Type, Type> matchedBefore) {
for (ResolvableType bound : this.bounds) {
boolean matched = false;
for (ResolvableType type : types) {
if (this.kind == Kind.UPPER ? bound.isAssignableFrom(type, false, matchedBefore, false) :
type.isAssignableFrom(bound, false, matchedBefore, false)) {
matched = true;
break;
}
}
if (!matched) {
for (ResolvableType type : types) {
if (!isAssignableFrom(type, matchedBefore)) {
return false;
}
}
@@ -34,6 +34,7 @@ import org.springframework.lang.Contract;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Contextual descriptor about a type to convert from or to.
@@ -500,7 +501,16 @@ public class TypeDescriptor implements Serializable {
if (!annotationsMatch(otherDesc)) {
return false;
}
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
if (isCollection() || isArray()) {
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor());
}
else if (isMap()) {
return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) &&
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
}
else {
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
}
}
private boolean annotationsMatch(TypeDescriptor otherDesc) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 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.
@@ -40,7 +40,6 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NavigableSet;
@@ -646,15 +645,13 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
if (currentPrefix != null) {
if (checkPathWithinPackage(path.substring(currentPrefix.length()))) {
// A prefix match found, potentially to be turned into a common parent cache entry.
if (commonPrefix == null || !commonUnique || currentPrefix.length() > commonPrefix.length()) {
commonPrefix = currentPrefix;
existingPath = path;
}
else if (currentPrefix.equals(commonPrefix)) {
commonUnique = false;
}
// A prefix match found, potentially to be turned into a common parent cache entry.
if (commonPrefix == null || !commonUnique || currentPrefix.length() > commonPrefix.length()) {
commonPrefix = currentPrefix;
existingPath = path;
}
else if (currentPrefix.equals(commonPrefix)) {
commonUnique = false;
}
}
else if (actualRootPath == null || path.length() > actualRootPath.length()) {
@@ -807,7 +804,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (separatorIndex == -1) {
separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
}
if (separatorIndex >= 0) {
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars
NavigableSet<String> entriesCache = this.jarEntriesCache.get(jarFileUrl);
@@ -834,17 +831,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (con instanceof JarURLConnection jarCon) {
// Should usually be the case for traditional JAR files.
try {
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
catch (FileNotFoundException ex) {
// Happens in case of cached root directory without specific subdirectory present.
return Collections.emptySet();
}
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
else {
// No JarURLConnection -> need to resort to URL file parsing.
@@ -881,13 +872,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
Set<Resource> result = new LinkedHashSet<>(64);
NavigableSet<String> entriesCache = new TreeSet<>();
Iterator<String> entryIterator = jarFile.stream().map(JarEntry::getName).sorted().iterator();
while (entryIterator.hasNext()) {
String entryPath = entryIterator.next();
int entrySeparatorIndex = entryPath.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
if (entrySeparatorIndex >= 0) {
entryPath = entryPath.substring(entrySeparatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
}
for (String entryPath : jarFile.stream().map(JarEntry::getName).sorted().toList()) {
entriesCache.add(entryPath);
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
@@ -993,8 +978,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
if (!Files.exists(rootPath)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping search for files matching pattern [%s]: directory [%s] does not exist"
if (logger.isInfoEnabled()) {
logger.info("Skipping search for files matching pattern [%s]: directory [%s] does not exist"
.formatted(subPattern, rootPath.toAbsolutePath()));
}
return result;
@@ -1118,10 +1103,6 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
return (path.startsWith("/") ? path.substring(1) : path);
}
private static boolean checkPathWithinPackage(String path) {
return (path.contains("/") && !path.contains(ResourceUtils.JAR_URL_SEPARATOR));
}
/**
* Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2025 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,11 +51,10 @@ public class FlightRecorderApplicationStartup implements ApplicationStartup {
@Override
public StartupStep start(String name) {
Long parentId = this.currentSteps.getFirst();
long sequenceId = this.currentSequenceId.incrementAndGet();
this.currentSteps.offerFirst(sequenceId);
return new FlightRecorderStartupStep(sequenceId, name,
parentId, committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
this.currentSteps.getFirst(), committedStep -> this.currentSteps.removeFirstOccurrence(sequenceId));
}
}
@@ -26,9 +26,7 @@ import java.util.function.Consumer;
import org.springframework.lang.Nullable;
/**
* Composite map that combines two other maps.
*
* <p>This type is created via
* Composite map that combines two other maps. This type is created via
* {@link CollectionUtils#compositeMap(Map, Map, BiFunction, Consumer)}.
*
* @author Arjen Poutsma
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,11 +98,13 @@ public class FastByteArrayOutputStream extends OutputStream {
if (this.closed) {
throw new IOException("Stream closed");
}
if (this.buffers.peekLast() == null || this.buffers.getLast().length == this.index) {
addBuffer(1);
else {
if (this.buffers.peekLast() == null || this.buffers.getLast().length == this.index) {
addBuffer(1);
}
// store the byte
this.buffers.getLast()[this.index++] = (byte) datum;
}
// store the byte
this.buffers.getLast()[this.index++] = (byte) datum;
}
@Override
@@ -382,20 +384,22 @@ public class FastByteArrayOutputStream extends OutputStream {
// This stream doesn't have any data in it...
return -1;
}
if (this.nextIndexInCurrentBuffer < this.currentBufferLength) {
this.totalBytesRead++;
return this.currentBuffer[this.nextIndexInCurrentBuffer++] & 0xFF;
}
else {
if (this.buffersIterator.hasNext()) {
this.currentBuffer = this.buffersIterator.next();
updateCurrentBufferLength();
this.nextIndexInCurrentBuffer = 0;
if (this.nextIndexInCurrentBuffer < this.currentBufferLength) {
this.totalBytesRead++;
return this.currentBuffer[this.nextIndexInCurrentBuffer++] & 0xFF;
}
else {
this.currentBuffer = null;
if (this.buffersIterator.hasNext()) {
this.currentBuffer = this.buffersIterator.next();
updateCurrentBufferLength();
this.nextIndexInCurrentBuffer = 0;
}
else {
this.currentBuffer = null;
}
return read();
}
return read();
}
}
@@ -23,9 +23,8 @@ import java.util.function.Predicate;
import org.springframework.lang.Nullable;
/**
* {@link Iterator} that filters out values that do not match a predicate.
*
* <p>This type is used by {@link CompositeMap}.
* Iterator that filters out values that do not match a predicate.
* This type is used by {@link CompositeMap}.
*
* @author Arjen Poutsma
* @since 6.2
@@ -40,7 +39,7 @@ final class FilteredIterator<E> implements Iterator<E> {
@Nullable
private E next;
private boolean hasNext;
private boolean nextSet;
public FilteredIterator(Iterator<E> delegate, Predicate<E> filter) {
@@ -53,15 +52,22 @@ final class FilteredIterator<E> implements Iterator<E> {
@Override
public boolean hasNext() {
return (this.hasNext || setNext());
if (this.nextSet) {
return true;
}
else {
return setNext();
}
}
@Override
public E next() {
if (!this.hasNext && !setNext()) {
throw new NoSuchElementException();
if (!this.nextSet) {
if (!setNext()) {
throw new NoSuchElementException();
}
}
this.hasNext = false;
this.nextSet = false;
Assert.state(this.next != null, "Next should not be null");
return this.next;
}
@@ -71,7 +77,7 @@ final class FilteredIterator<E> implements Iterator<E> {
E next = this.delegate.next();
if (this.filter.test(next)) {
this.next = next;
this.hasNext = true;
this.nextSet = true;
return true;
}
}

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