Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel cf74ce007d Release v6.2.15 2025-12-11 10:58:54 +01:00
197 changed files with 1194 additions and 3583 deletions
@@ -24,14 +24,14 @@ runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
@@ -17,14 +17,14 @@ runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
shell: bash
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
- name: Sync
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
uses: spring-io/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0
with:
token: ${{ inputs.central-token-password }}
token-name: ${{ inputs.central-token-username }}
+2 -2
View File
@@ -18,9 +18,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
@@ -13,7 +13,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
@@ -21,7 +21,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build
id: build
uses: ./.github/actions/build
@@ -19,7 +19,7 @@ jobs:
uses: ./.github/actions/print-jvm-thread-dumps
- name: Upload Build Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build
id: build
uses: ./.github/actions/build
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 1
ref: docs-build
+5 -5
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
@@ -20,7 +20,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
@@ -56,7 +56,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Sync to Maven Central
uses: ./.github/actions/sync-to-maven-central
with:
@@ -72,7 +72,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
@@ -85,7 +85,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v6
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
+5 -5
View File
@@ -30,23 +30,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check Out Release Verification Tests
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
ref: 'v0.0.2'
repository: spring-projects/spring-framework-release-verification
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
path: send-notification
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with:
cache-read-only: false
- name: Configure Gradle Properties
@@ -64,7 +64,7 @@ jobs:
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: build-reports
path: '**/build/reports/'
+6 -2
View File
@@ -91,10 +91,14 @@ configure([rootProject] + javaProjects) { project ->
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
//"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
// 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.14.1/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
+1 -1
View File
@@ -326,9 +326,9 @@
*** xref:testing/testcontext-framework/application-events.adoc[]
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
*** xref:testing/testcontext-framework/ctx-management.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
potential non-public interactions through proxies, it needs to be defined accordingly.
If your interception needs to include method calls or even constructors within the target
If your interception needs include method calls or even constructors within the target
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
with different characteristics, so be sure to make yourself familiar with weaving
@@ -151,17 +151,17 @@ injected into a `Set<MovieCatalog>` annotated with `@Qualifier("action")`.
[TIP]
====
Letting qualifier values select against target bean names, within the type-matching
candidates, does not require a `@Qualifier` annotation at the injection point. If there
is no other resolution indicator (such as a qualifier, a primary marker, or a fallback
marker), for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses the
same-named candidate, if any (either by bean name or by associated alias).
candidates, does not require a `@Qualifier` annotation at the injection point.
If there is no other resolution indicator (such as a qualifier or a primary marker),
for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses
the same-named candidate, if any (either by bean name or by associated alias).
Since version 6.1, this requires the `-parameters` Java compiler flag to be present. As
of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing
the full type matching algorithm when the parameter name matches the bean name and no
type, qualifier, primary, or fallback conditions override the match. It is therefore
recommendable for your parameter names to match the target bean names.
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
As of 6.2, the container applies fast shortcut resolution for bean name matches,
bypassing the full type matching algorithm when the parameter name matches the
bean name and no type, qualifier or primary conditions override the match. It is
therefore recommendable for your parameter names to match the target bean names.
====
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
@@ -308,8 +308,7 @@ set of multiple matches for the specific bean type (as returned by the factory m
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
through `@Order` values in combination with `@Primary` or `@Fallback` on a single bean
for each type.
through `@Order` values in combination with `@Primary` on a single bean for each type.
====
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
@@ -27,5 +27,4 @@ with the `CustomAutowireConfigurer`
When multiple beans qualify as autowire candidates, the determination of a "`primary`" is
as follows: If exactly one bean definition among the candidates has a `primary`
attribute set to `true`, it is selected. For annotation-based configuration, see
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
attribute set to `true`, it is selected.
@@ -67,13 +67,6 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise,
Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
initialization of other beans in the context, this early type detection is critical.
Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method,
declare the method as `static` and ideally with no dependencies. Doing so avoids eager
initialization of the configuration class and other beans, which would make them
ineligible for full post-processing (such as auto-proxying). See the
"BeanPostProcessor-returning `@Bean` methods" section in the
{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details.
[[beans-factory-programmatically-registering-beanpostprocessors]]
.Programmatically registering `BeanPostProcessor` instances
NOTE: While the recommended approach for `BeanPostProcessor` registration is through
@@ -87,7 +80,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat
are always processed before those registered through auto-detection, regardless of any
explicit ordering.
.`BeanPostProcessor` instances and early initialization
.`BeanPostProcessor` instances and AOP auto-proxying
[NOTE]
====
Classes that implement the `BeanPostProcessor` interface are special and are treated
@@ -97,23 +90,17 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe
in a sorted fashion and applied to all further beans in the container. Because AOP
auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor`
instances nor the beans they directly reference are eligible for auto-proxying and,
thus, do not have aspects woven into them. More generally, any bean that is instantiated
during this early phase is not eligible for full post-processing by all
`BeanPostProcessor` instances.
thus, do not have aspects woven into them.
For any such bean, you should see a WARN-level log message similar to the following.
For any such bean, you should see an informational log message: `Bean someBean is not
eligible for getting processed by all BeanPostProcessor interfaces (for example: not
eligible for auto-proxying)`.
[quote]
Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by
all BeanPostProcessors (for example: not eligible for auto-proxying).
To minimize the number of beans affected, register a `BeanPostProcessor` with a
`static` `@Bean` method that has no dependencies (see the note above). If you have
beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which
may fall back to autowiring), Spring might access unexpected beans when searching
for type-matching dependency candidates and, therefore, make them ineligible for
auto-proxying or other kinds of bean post-processing. For example, if you have a
dependency annotated with `@Resource` where the field or setter name does not
If you have beans wired into your `BeanPostProcessor` by using autowiring or
`@Resource` (which may fall back to autowiring), Spring might access unexpected beans
when searching for type-matching dependency candidates and, therefore, make them
ineligible for auto-proxying or other kinds of bean post-processing. For example, if you
have a dependency annotated with `@Resource` where the field or setter name does not
directly correspond to the declared name of a bean and no name attribute is used,
Spring accesses other beans for matching them by type.
====
@@ -148,7 +135,7 @@ Java::
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean);
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
@@ -177,48 +164,7 @@ Kotlin::
----
======
You can register the `InstantiationTracingBeanPostProcessor` with Java configuration
by using a `static` `@Bean` method (recommended to avoid eager initialization of the
configuration class and other beans):
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
@Configuration
public class AppConfig {
@Bean
public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() {
return new InstantiationTracingBeanPostProcessor();
}
// ... other bean definitions
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
@Configuration
class AppConfig {
@Bean
companion object {
@JvmStatic
fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor()
}
// ... other bean definitions
}
----
======
Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the
`bean` element with XML configuration:
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -246,7 +192,7 @@ Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via
----
Notice how the `InstantiationTracingBeanPostProcessor` is merely defined. It does not
even have a name, and, because it is a bean, it can be dependency-injected as with any
even have a name, and, because it is a bean, it can be dependency-injected as you would any
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
script. The Spring dynamic language support is detailed in the chapter entitled
xref:languages/dynamic.adoc[Dynamic Language Support].)
@@ -355,23 +301,6 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean
post-processors, at the appropriate time. You can deploy these post-processor beans as
you would any other bean.
When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a
`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts
with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the
configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods"
section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc
for details and an example.
For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type,
you should see an INFO-level log message similar to the following.
[quote]
@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's
BeanFactoryPostProcessor interface. This will result in a failure to process annotations
such as @Autowired, @Resource, and @PostConstruct within the method's declaring
@Configuration class. Add the 'static' modifier to this method to avoid these container
lifecycle issues; see @Bean javadoc for complete details.
NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure
``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
@@ -53,7 +53,7 @@ Kotlin::
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
// uses CustomValue:::compareTo
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java);
----
======
@@ -167,7 +167,7 @@ Kotlin::
[CAUTION]
====
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>`.
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
to `false`.
@@ -312,13 +312,13 @@ Kotlin::
// evaluates to 'a'
val ch = parser.parseExpression("'d' - 3")
.getValue(Char::class.java)
.getValue(Character::class.java);
// -- Repeat --
// evaluates to "abcabc"
val repeated = parser.parseExpression("'abc' * 2")
.getValue(String::class.java)
.getValue(String::class.java);
----
======
@@ -485,7 +485,7 @@ Kotlin::
// -- Operator precedence --
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
----
======
@@ -541,7 +541,32 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
follows.
include-code::./ListConcatenation[]
[source,java,indent=0,subs="verbatim,quotes"]
----
pubic class ListConcatenation implements OperatorOverloader {
@Override
public boolean overridesOperation(Operation operation, Object left, Object right) {
return (operation == Operation.ADD &&
left instanceof List && right instanceof List);
}
@Override
@SuppressWarnings("unchecked")
public Object operate(Operation operation, Object left, Object right) {
if (operation == Operation.ADD &&
left instanceof List list1 && right instanceof List list2) {
List result = new ArrayList(list1);
result.addAll(list2);
return result;
}
throw new UnsupportedOperationException(
"No overload for operation %s and operands [%s] and [%s]"
.formatted(operation, left, right));
}
}
----
If we register `ListConcatenation` as the `OperatorOverloader` in a
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
@@ -564,8 +589,8 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val context = StandardEvaluationContext()
context.operatorOverloader = ListConcatenation()
StandardEvaluationContext context = StandardEvaluationContext()
context.setOperatorOverloader(ListConcatenation())
// evaluates to a new list: [1, 2, 3, 4, 5]
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
@@ -6,7 +6,7 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
[NOTE]
====
Variable names must begin with a letter (as defined below), an underscore, or a dollar
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
sign.
Variable names must be composed of one or more of the following supported types of
@@ -15,7 +15,7 @@ configuration options. You should instantiate the `SimpleJdbcInsert` in the data
layer's initialization method. For this example, the initializing method is the
`setDataSource` method. You do not need to subclass the `SimpleJdbcInsert` class. Instead,
you can create a new instance and set the table name by using the `withTableName` method.
Configuration methods for this class follow the `fluent` style that returns the instance
Configuration methods for this class follow the `fluid` style that returns the instance
of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following
example uses only one configuration method (we show examples of multiple methods later):
@@ -349,11 +349,11 @@ parameters return the data read from the table.
You can declare `SimpleJdbcCall` in a manner similar to declaring `SimpleJdbcInsert`. You
should instantiate and configure the class in the initialization method of your data-access
layer. In contrast to the `StoredProcedure` class, you do not need to create a subclass,
and you do not need to declare parameters that can be looked up in the database metadata.
The following `SimpleJdbcCall` configuration example uses the preceding stored procedure.
The only configuration option (other than the `DataSource`) is the name of the stored
procedure.
layer. Compared to the `StoredProcedure` class, you need not create a subclass
and you need not to declare parameters that can be looked up in the database metadata.
The following example of a `SimpleJdbcCall` configuration uses the preceding stored
procedure (the only configuration option, in addition to the `DataSource`, is the name
of the stored procedure):
[tabs]
======
@@ -1,11 +1,16 @@
[[appendix]]
= Appendix
[[appendix.xsd-schemas]]
== XML Schemas
This part of the appendix lists XML schemas related to integration technologies.
[[appendix.xsd-schemas-jee]]
=== The `jee` Schema
@@ -167,7 +172,7 @@ different properties with `jee`:
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
without `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -179,7 +184,7 @@ without `jee`:
</bean>
----
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
with `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -195,7 +200,7 @@ with `jee`:
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
and a number of properties without `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -210,7 +215,7 @@ and a number of properties without `jee`:
</bean>
----
The following example shows how to configure a reference to a local EJB Stateless Session Bean
The following example shows how to configures a reference to a local EJB Stateless Session Bean
and a number of properties with `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -229,7 +234,7 @@ and a number of properties with `jee`:
The `<jee:remote-slsb/>` element configures a reference to a `remote` EJB Stateless Session Bean.
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
without `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -246,7 +251,7 @@ without `jee`:
</bean>
----
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
with `jee`:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -308,7 +313,7 @@ xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[Configuring Annotation-
=== The `cache` Schema
You can use the `cache` elements to enable support for Spring's `@CacheEvict`, `@CachePut`,
and `@Caching` annotations. The `cache` schema also supports declarative XML-based caching. See
and `@Caching` annotations. It it also supports declarative XML-based caching. See
xref:integration/cache/annotations.adoc#cache-annotation-enable[Enabling Caching Annotations] and
xref:integration/cache/declarative-xml.adoc[Declarative XML-based Caching] for details.
@@ -13,4 +13,4 @@ running the Kotlin compiler with its `-java-parameters` flag for standard Java p
You can declare configuration classes as
{kotlin-docs}/nested-classes.html[top level or nested but not inner],
since the latter requires a reference to the outer class.
since the later requires a reference to the outer class.
@@ -187,7 +187,7 @@ default mode may be set via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
The default mode may also be configured as a
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter].
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
If the `spring.test.constructor.autowire.mode` property is not set, test class
constructors will not be automatically autowired.
@@ -49,6 +49,7 @@ Kotlin::
<1> Injecting the `ApplicationContext`.
======
Similarly, if your test is configured to load a `WebApplicationContext`, you can inject
the web application context into your test, as follows:
@@ -86,6 +87,7 @@ Kotlin::
<2> Injecting the `WebApplicationContext`.
======
Dependency injection by using `@Autowired` is provided by the
`DependencyInjectionTestExecutionListener`, which is configured by default
(see xref:testing/testcontext-framework/fixture-di.adoc[Dependency Injection of Test Fixtures]).
@@ -94,23 +96,22 @@ Dependency injection by using `@Autowired` is provided by the
Test classes that use the TestContext framework do not need to extend any particular
class or implement a specific interface to configure their application context. Instead,
configuration is achieved by declaring the `@ContextConfiguration` annotation at the
class level. If your test class does not explicitly declare component classes or resource
locations, the configured `ContextLoader` determines how to load a context from _default_
configuration classes or a _default_ location. In addition to component classes and
context resource locations, an application context can also be configured through
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[context customizers]
or xref:testing/testcontext-framework/ctx-management/initializers.adoc[context initializers].
class level. If your test class does not explicitly declare application context resource
locations or component classes, the configured `ContextLoader` determines how to load a
context from a default location or default configuration classes. In addition to context
resource locations and component classes, an application context can also be configured
through application context initializers.
The following sections explain how to use `@ContextConfiguration` and related annotations
to configure a test `ApplicationContext` by using component classes (typically
`@Configuration` classes), XML configuration files, Groovy scripts, context customizers,
or context initializers. Alternatively, you can implement and configure your own custom
`SmartContextLoader` for advanced use cases.
The following sections explain how to use Spring's `@ContextConfiguration` annotation to
configure a test `ApplicationContext` by using XML configuration files, Groovy scripts,
component classes (typically `@Configuration` classes), or context initializers.
Alternatively, you can implement and configure your own custom `SmartContextLoader` for
advanced use cases.
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML resources]
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing Component Classes, XML, and Groovy Scripts]
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing XML, Groovy Scripts, and Component Classes]
* xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[Context Configuration with Context Customizers]
* xref:testing/testcontext-framework/ctx-management/initializers.adoc[Context Configuration with Context Initializers]
* xref:testing/testcontext-framework/ctx-management/inheritance.adoc[Context Configuration Inheritance]
@@ -1,32 +1,33 @@
[[testcontext-ctx-management-mixed-config]]
= Mixing Component Classes, XML, and Groovy Scripts
= Mixing XML, Groovy Scripts, and Component Classes
It may sometimes be desirable to mix component classes (typically `@Configuration`
classes), XML configuration files, or Groovy scripts to configure an `ApplicationContext`
for your tests. For example, if you use XML configuration in production for legacy
reasons, you may decide that you want to use `@Configuration` classes to configure
It may sometimes be desirable to mix XML configuration files, Groovy scripts, and
component classes (typically `@Configuration` classes) to configure an
`ApplicationContext` for your tests. For example, if you use XML configuration in
production, you may decide that you want to use `@Configuration` classes to configure
specific Spring-managed components for your tests, or vice versa.
Furthermore, some third-party frameworks (such as Spring Boot) provide first-class
support for loading an `ApplicationContext` from different types of resources
simultaneously (for example, `@Configuration` classes, XML configuration files, and
Groovy scripts). The Spring Framework, historically, has not supported this for standard
deployments. Consequently, most of the `SmartContextLoader` implementations that the
Spring Framework delivers in the `spring-test` module support only one resource type for
each test context. However, this does not mean that you cannot use a mixture of resource
types. One exception to the general rule is that the `GenericGroovyXmlContextLoader` and
simultaneously (for example, XML configuration files, Groovy scripts, and
`@Configuration` classes). The Spring Framework, historically, has not supported this for
standard deployments. Consequently, most of the `SmartContextLoader` implementations that
the Spring Framework delivers in the `spring-test` module support only one resource type
for each test context. However, this does not mean that you cannot use both. One
exception to the general rule is that the `GenericGroovyXmlContextLoader` and
`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy
scripts simultaneously. Furthermore, third-party frameworks may choose to support the
declaration of both `classes` and `locations` through `@ContextConfiguration`, and, with
declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with
the standard testing support in the TestContext framework, you have the following options.
If you want to use `@Configuration` classes and resource locations (for example, XML or
Groovy) to configure your tests, you must pick one as the entry point, and that one must
import or include the other. For example, in a `@Configuration` class, you can use
`@ImportResource` to import XML configuration files or Groovy scripts; whereas, in XML or
Groovy scripts, you can include `@Configuration` classes by using component scanning or
defining them as normal Spring beans. Note that this behavior is semantically equivalent
If you want to use resource locations (for example, XML or Groovy) and `@Configuration`
classes to configure your tests, you must pick one as the entry point, and that one must
include or import the other. For example, in XML or Groovy scripts, you can include
`@Configuration` classes by using component scanning or defining them as normal Spring
beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML
configuration files or Groovy scripts. Note that this behavior is semantically equivalent
to how you configure your application in production: In production configuration, you
define either a set of `@Configuration` classes or a set of XML or Groovy resource
locations from which your production `ApplicationContext` is loaded, but you still have
the freedom to import or include the other type of configuration.
define either a set of XML or Groovy resource locations or a set of `@Configuration`
classes from which your production `ApplicationContext` is loaded, but you still have the
freedom to include or import the other type of configuration.
@@ -22,7 +22,7 @@ TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://docs.junit.org/current/extensions/conditional-test-execution.html[conditional
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
@@ -160,7 +160,7 @@ for further details.
=== Dependency Injection with the `SpringExtension`
The `SpringExtension` implements the
link:https://docs.junit.org/current/extensions/parameter-resolution.html[`ParameterResolver`]
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
already set.
- The Jackson JSON and XML message converters use "application/problem+json" or
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
to ensure they are favored for content negotiation.
- For content negotiation, the Jackson `HttpMessageConverter` prefers
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
@@ -251,8 +251,7 @@ Kotlin::
======
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
mark it with `@Primary`, or mark the one declared in the MVC config with `@Fallback`, in
order to avoid conflict.
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config.
[[webflux-config-content-negotiation]]
@@ -418,23 +418,16 @@ See the section on xref:web/webflux-cors.adoc[CORS] and the xref:web/webflux-cor
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
Spring provides `UrlHandlerFilter` that removes the trailing slash from URL paths to ensure a consistent view of paths with or without a trailing slash.
This is important to avoid a mismatch between URL-based authorization decisions and web framework request mappings.
The filter can remove the trailing slash in one of a couple of ways:
Adding trailing slash variants to all mapping declarations is not the best way to handle this use case.
The `UrlHandlerFilter` web filter has been designed for this purpose. It can be configured to:
* respond with an HTTP redirect status that sends clients to the same path without a trailing slash.
* mutate the request to remove the trailing slash.
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
* mutate the request to act as if the request was sent without a trailing slash and continue the processing of the request.
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
Keep in mind the following:
- the root path `"/"` is excluded from trailing slash handling.
- `@RequestMapping("/")` adds a trailing slash to a type-level mapping, and therefore will
not map when trailing slash handling applies; use `@RequestMapping` (no path attribute) instead.
[[webflux-exception-handler]]
== Exceptions
@@ -120,26 +120,17 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors
== URL Handler
[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]#
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths
when mapping incoming requests on controller methods. This could be done by enabling the `setUseTrailingSlashMatch`
option on the `PathMatchConfigurer`. This means that sending a "GET /home/" request would be handled by a controller
method annotated with `@GetMapping("/home")`.
Spring provides `UrlHandlerFilter` that removes the trailing slash from URL paths to ensure a consistent view of paths with or without a trailing slash.
This is important to avoid a mismatch between URL-based authorization decisions and web framework request mappings.
The filter can remove the trailing slash in one of a couple of ways:
This option has been retired, but applications are still expected to handle such requests in a safe way.
The `UrlHandlerFilter` Servlet filter has been designed for this purpose. It can be configured to:
* respond with an HTTP redirect status that sends clients to the same path without a trailing slash.
* wrap the request to remove the trailing slash.
NOTE: Historically Spring MVC supported trailing slash matching of URL paths.
This capability was deprecated in 6.0 for security reasons and removed in 7.0 with
`UrlHandlerFilter` providing a safer alternative.
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request.
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
Keep in mind the following:
- the root path `"/"` is excluded from trailing slash handling.
- `@RequestMapping("/")` adds a trailing slash to a type-level mapping, and therefore will
not map when trailing slash handling applies; use `@RequestMapping` (no path attribute) instead.
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
already set.
- The Jackson JSON and XML codecs use "application/problem+json" or
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
to ensure they are favored for content negotiation.
- For content negotiation, the Jackson `HttpMessageConverter` prefers
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring MVC exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
@@ -19,5 +19,4 @@ example shows:
include-code::./MyController[tag=snippet,indent=0]
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
mark it with `@Primary`, or mark the one declared in the MVC configuration with
`@Fallback`, in order to avoid conflict.
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC configuration.
@@ -3,7 +3,7 @@
[.small]#xref:web/webflux/uri-building.adoc[See equivalent in the Reactive stack]#
This section describes various options available in the Spring Framework to work with URIs.
This section describes various options available in the Spring Framework to work with URI's.
include::partial$web/web-uris.adoc[leveloffset=+1]
@@ -2,7 +2,7 @@
= UriComponents
[.small]#Spring MVC and Spring WebFlux#
`UriComponentsBuilder` helps to build URIs from URI templates with variables, as the following example shows:
`UriComponentsBuilder` helps to build URI's from URI templates with variables, as the following example shows:
[tabs]
======
@@ -128,7 +128,7 @@ Kotlin::
= UriBuilder
[.small]#Spring MVC and Spring WebFlux#
<<uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
<<web-uricomponents, `UriComponentsBuilder`>> implements `UriBuilder`. You can create a
`UriBuilder`, in turn, with a `UriBuilderFactory`. Together, `UriBuilderFactory` and
`UriBuilder` provide a pluggable mechanism to build URIs from URI templates, based on
shared configuration, such as a base URL, encoding preferences, and other details.
@@ -247,7 +247,7 @@ and treats deviations from the syntax as illegal.
https://github.com/web-platform-tests/wpt/tree/master/url[URL parsing algorithm] in the
https://url.spec.whatwg.org[WhatWG URL Living standard]. It provides lenient handling of
a wide range of cases of unexpected input. Browsers implement this in order to handle
leniently user typed URLs. For more details, see the URL Living Standard and URL parsing
leniently user typed URL's. For more details, see the URL Living Standard and URL parsing
https://github.com/web-platform-tests/wpt/tree/master/url[test cases].
By default, `RestClient`, `WebClient`, and `RestTemplate` use the RFC parser type, and
@@ -255,10 +255,10 @@ expect applications to provide with URL templates that conform to RFC syntax. To
that you can customize the `UriBuilderFactory` on any of the clients.
Applications and frameworks may further rely on `UriComponentsBuilder` for their own needs
to parse user provided URLs in order to inspect and possibly validated URI components
to parse user provided URL's in order to inspect and possibly validated URI components
such as the scheme, host, port, path, and query. Such components can decide to use the
WhatWG parser type in order to handle URLs more leniently, and to align with the way
browsers parse URIs, in case of a redirect to the input URL or if it is included in a
WhatWG parser type in order to handle URL's more leniently, and to align with the way
browsers parse URI's, in case of a redirect to the input URL or if it is included in a
response to a browser.
@@ -373,14 +373,14 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
// Customize the RestTemplate.
// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Customize the WebClient.
// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
----
@@ -393,12 +393,12 @@ Kotlin::
encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}
// Customize the RestTemplate.
// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
uriTemplateHandler = factory
}
// Customize the WebClient.
// Customize the WebClient..
val client = WebClient.builder().uriBuilderFactory(factory).build()
----
======
+5 -5
View File
@@ -1,11 +1,11 @@
{
"dependencies": {
"antora": "3.2.0-alpha.11",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.2",
"antora": "3.2.0-alpha.4",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.7",
"fast-xml-parser": "5.3.8",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
+10 -9
View File
@@ -200,7 +200,6 @@
See the License for the specific language governing permissions and
limitations under the License.
=======================================================================
SPRING FRAMEWORK ${version} SUBCOMPONENTS:
@@ -213,7 +212,7 @@ code for these subcomponents is subject to the terms and
conditions of the following licenses.
>>> ASM 9.9.1 (org.ow2.asm:asm:9.9.1):
>>> ASM 9.1 (org.ow2.asm:asm:9.1, org.ow2.asm:asm-commons:9.1):
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
@@ -250,8 +249,10 @@ Copyright (c) 1999-2009, OW2 Consortium <https://www.ow2.org/>
>>> CGLIB 3.3 (cglib:cglib:3.3):
Per the LICENSE file in the CGLIB distribution, CGLIB 3.3 is licensed
under the Apache License, version 2.0, the text of which is included above.
Per the LICENSE file in the CGLIB JAR distribution downloaded from
https://github.com/cglib/cglib/releases/download/RELEASE_3_3_0/cglib-3.3.0.jar,
CGLIB 3.3 is licensed under the Apache License, version 2.0, the text of which
is included above.
>>> JavaPoet 1.13.0 (com.squareup:javapoet:1.13.0):
@@ -262,18 +263,18 @@ JavaPoet 1.13.0 is licensed under the Apache License, version 2.0, the text of
which is included above.
>>> Objenesis 3.5 (org.objenesis:objenesis:3.5):
>>> Objenesis 3.4 (org.objenesis:objenesis:3.4):
Per the LICENSE file in the Objenesis distribution downloaded from
http://objenesis.org/download.html, Objenesis 3.5 is licensed under the
Per the LICENSE file in the Objenesis ZIP distribution downloaded from
http://objenesis.org/download.html, Objenesis 3.4 is licensed under the
Apache License, version 2.0, the text of which is included above.
Per the NOTICE file in the Objenesis distribution downloaded from
Per the NOTICE file in the Objenesis ZIP distribution downloaded from
http://objenesis.org/download.html and corresponding to section 4d of the
Apache License, Version 2.0, in this case for Objenesis:
Objenesis
Copyright 2006-2026 Joe Walnes, Henri Tremblay, Leonardo Mesquita
Copyright 2006-2019 Joe Walnes, Henri Tremblay, Leonardo Mesquita
===============================================================================
@@ -1,47 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.core.expressions.languageref.expressionsoperatorsoverloaded;
import org.springframework.expression.Operation;
import org.springframework.expression.OperatorOverloader;
import java.util.ArrayList;
import java.util.List;
public class ListConcatenation implements OperatorOverloader {
@Override
public boolean overridesOperation(Operation operation, Object left, Object right) {
return (operation == Operation.ADD && left instanceof List && right instanceof List);
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Object operate(Operation operation, Object left, Object right) {
if (operation == Operation.ADD &&
left instanceof List list1 && right instanceof List list2) {
List result = new ArrayList(list1);
result.addAll(list2);
return result;
}
throw new UnsupportedOperationException(
"No overload for operation %s and operands [%s] and [%s]"
.formatted(operation, left, right));
}
}
@@ -1,37 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.core.expressions.languageref.expressionsoperatorsoverloaded
import org.springframework.expression.Operation
import org.springframework.expression.OperatorOverloader
class ListConcatenation: OperatorOverloader {
override fun overridesOperation(operation: Operation, left: Any?, right: Any?): Boolean {
return operation == Operation.ADD && left is List<*> && right is List<*>
}
override fun operate(operation: Operation, left: Any?, right: Any?): Any {
if (operation == Operation.ADD && left is List<*> && right is List<*>) {
return left + right
}
throw UnsupportedOperationException(
"No overload for operation $operation and operands [$left] and [$right]")
}
}
+21 -21
View File
@@ -8,20 +8,20 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
api(platform("io.micrometer:micrometer-bom:1.15.10"))
api(platform("io.netty:netty-bom:4.1.130.Final"))
api(platform("io.micrometer:micrometer-bom:1.14.14"))
api(platform("io.netty:netty-bom:4.1.128.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.16"))
api(platform("io.projectreactor:reactor-bom:2024.0.13"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.30"))
api(platform("org.apache.groovy:groovy-bom:4.0.29"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.7"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.33"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.33"))
api(platform("org.assertj:assertj-bom:3.27.6"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.30"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.30"))
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.14.3"))
api(platform("org.mockito:mockito-bom:5.22.0"))
api(platform("org.junit:junit-bom:5.14.1"))
api(platform("org.mockito:mockito-bom:5.20.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.4")
@@ -31,8 +31,8 @@ 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.13.2")
api("com.google.protobuf:protobuf-java-util:4.34.0")
api("com.h2database:h2:2.4.240")
api("com.google.protobuf:protobuf-java-util:4.32.1")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.10.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
api("com.rometools:rome:1.19.0")
@@ -50,7 +50,7 @@ dependencies {
api("io.mockk:mockk:1.13.17")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
api("io.r2dbc:r2dbc-h2:1.1.0.RELEASE")
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.12")
@@ -90,11 +90,11 @@ dependencies {
api("junit:junit:4.13.2")
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("org.apache-extras.beanshell:bsh:2.0b6")
api("org.apache.activemq:activemq-broker:5.17.7")
api("org.apache.activemq:activemq-kahadb-store:5.17.7")
api("org.apache.activemq:activemq-stomp:5.17.7")
api("org.apache.activemq:artemis-jakarta-client:2.42.0")
api("org.apache.activemq:artemis-junit-5:2.42.0")
api("org.apache.activemq:activemq-broker:5.17.7")
api("org.apache.activemq:activemq-kahadb-store:5.17.7")
api("org.apache.activemq:activemq-stomp:5.17.7")
api("org.apache.activemq:artemis-jakarta-client:2.42.0")
api("org.apache.activemq:artemis-junit-5:2.42.0")
api("org.apache.commons:commons-pool2:2.9.0")
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
@@ -129,7 +129,7 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.21.0")
api("org.htmlunit:htmlunit:4.18.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.13.0")
api("org.junit.support:testng-engine:1.0.5")
@@ -137,8 +137,8 @@ dependencies {
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.41.0")
api("org.seleniumhq.selenium:selenium-java:4.41.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.38.0")
api("org.seleniumhq.selenium:selenium-java:4.38.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.17")
api("org.testng:testng:7.11.0")
@@ -147,6 +147,6 @@ dependencies {
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.4")
api("org.xmlunit:xmlunit-matchers:2.10.4")
api("org.yaml:snakeyaml:2.6")
api("org.yaml:snakeyaml:2.5")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.17
version=6.2.15
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -489,9 +489,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
@Override
@Nullable
public Class<?> getPropertyType(String propertyName) throws BeansException {
if (this.wrappedObject == null) {
return null;
}
try {
PropertyHandler ph = getPropertyHandler(propertyName);
if (ph != null) {
@@ -192,7 +192,7 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements
@Override
@Nullable
protected PropertyHandler getLocalPropertyHandler(String propertyName) {
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler((GenericTypeAwarePropertyDescriptor) pd) : null);
}
@@ -73,7 +73,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
@Override
@Nullable
protected PropertyHandler getLocalPropertyHandler(String propertyName) {
protected FieldPropertyHandler getLocalPropertyHandler(String propertyName) {
FieldPropertyHandler propertyHandler = this.fieldMap.get(propertyName);
if (propertyHandler == null) {
Field field = ReflectionUtils.findField(getWrappedClass(), propertyName);
@@ -26,7 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -36,7 +35,6 @@ import org.springframework.util.StringUtils;
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Sam Brannen
*/
abstract class PropertyDescriptorUtils {
@@ -101,13 +99,14 @@ abstract class PropertyDescriptorUtils {
}
else {
Method readMethod = pd.getReadMethod();
if (readMethod == null || readMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
if (readMethod == null ||
(readMethod.getReturnType() == method.getReturnType() && method.getName().startsWith("is"))) {
pd.setReadMethod(method);
}
}
}
else {
pd = new BasicPropertyDescriptor(propertyName, beanClass, (!setter ? method : null), (setter ? method : null));
pd = new BasicPropertyDescriptor(propertyName, (!setter ? method : null), (setter ? method : null));
pdMap.put(propertyName, pd);
}
}
@@ -265,8 +264,6 @@ abstract class PropertyDescriptorUtils {
*/
private static class BasicPropertyDescriptor extends PropertyDescriptor {
private final Class<?> beanClass;
@Nullable
private Method readMethod;
@@ -275,11 +272,10 @@ abstract class PropertyDescriptorUtils {
private final List<Method> alternativeWriteMethods = new ArrayList<>();
public BasicPropertyDescriptor(String propertyName, Class<?> beanClass, @Nullable Method readMethod, @Nullable Method writeMethod)
public BasicPropertyDescriptor(String propertyName, @Nullable Method readMethod, @Nullable Method writeMethod)
throws IntrospectionException {
super(propertyName, readMethod, writeMethod);
this.beanClass = beanClass;
}
@Override
@@ -311,24 +307,14 @@ abstract class PropertyDescriptorUtils {
public Method getWriteMethod() {
if (this.writeMethod == null && !this.alternativeWriteMethods.isEmpty()) {
if (this.readMethod == null) {
this.writeMethod = this.alternativeWriteMethods.get(0);
return this.alternativeWriteMethods.get(0);
}
else {
for (Method method : this.alternativeWriteMethods) {
// Check subtype match first.
if (this.readMethod.getReturnType().isAssignableFrom(method.getParameterTypes()[0])) {
this.writeMethod = method;
break;
}
// Check exact match against resolved generic parameter type as a fallback.
if (!(method.getGenericParameterTypes()[0] instanceof Class<?>)) {
Class<?> resolvedParameterType =
ResolvableType.forMethodParameter(method, 0, this.beanClass).toClass();
if (this.readMethod.getReturnType().equals(resolvedParameterType)) {
this.writeMethod = method;
break;
}
}
}
}
}
@@ -336,4 +322,5 @@ abstract class PropertyDescriptorUtils {
}
}
}
@@ -40,13 +40,6 @@ import org.springframework.beans.BeansException;
* A {@code BeanFactoryPostProcessor} may also be registered programmatically
* with a {@code ConfigurableApplicationContext}.
*
* <p>When registering a {@code BeanFactoryPostProcessor} via an {@code @Bean} method
* in a {@code @Configuration} class, use a {@code static} method to avoid eager
* initialization of other beans in the configuration class. See the
* "BeanFactoryPostProcessor-returning {@code @Bean} methods" section in
* {@link org.springframework.context.annotation.Bean @Bean}'s javadoc for details
* and an example.
*
* <h3>Ordering</h3>
* <p>{@code BeanFactoryPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
@@ -34,14 +34,6 @@ import org.springframework.lang.Nullable;
* created. A plain {@code BeanFactory} allows for programmatic registration of
* post-processors, applying them to all beans created through the bean factory.
*
* <p>When registering a {@code BeanPostProcessor} via an {@code @Bean} method in
* a {@code @Configuration} class, use a {@code static} method with ideally no
* dependencies in order to avoid eager initialization that can make other beans
* ineligible for full post-processing. See the "BeanPostProcessor-returning
* {@code @Bean} methods" section in
* {@link org.springframework.context.annotation.Bean @Bean}'s javadoc for details
* and an example.
*
* <h3>Ordering</h3>
* <p>{@code BeanPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
@@ -33,12 +33,10 @@ import java.util.List;
import java.util.Locale;
import java.util.UUID;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.PropertyDescriptorUtilsPropertyResolutionTests.ServiceWithOverriddenGetterAndOverloadedSetter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.testfixture.beans.DerivedTestBean;
@@ -69,14 +67,14 @@ class BeanUtilsTests {
@Test
void instantiateClassGivenInterface() {
assertThatExceptionOfType(FatalBeanException.class)
.isThrownBy(() -> BeanUtils.instantiateClass(List.class));
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() ->
BeanUtils.instantiateClass(List.class));
}
@Test
void instantiateClassGivenClassWithoutDefaultConstructor() {
assertThatExceptionOfType(FatalBeanException.class)
.isThrownBy(() -> BeanUtils.instantiateClass(CustomDateEditor.class));
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() ->
BeanUtils.instantiateClass(CustomDateEditor.class));
}
@Test // gh-22531
@@ -93,16 +91,16 @@ class BeanUtilsTests {
void instantiateClassWithFewerArgsThanParameters() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
assertThatExceptionOfType(BeanInstantiationException.class)
.isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, "foo"));
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() ->
BeanUtils.instantiateClass(constructor, null, null, "foo"));
}
@Test // gh-22531
void instantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException {
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor();
assertThatExceptionOfType(BeanInstantiationException.class)
.isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null));
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() ->
BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null));
}
@Test // gh-22531, gh-27390
@@ -159,6 +157,267 @@ class BeanUtilsTests {
assertThat(BeanUtils.findEditorByConvention(Resource.class).getClass()).isEqualTo(ResourceEditor.class);
}
@Test
void copyProperties() throws Exception {
TestBean tb = new TestBean();
tb.setName("rod");
tb.setAge(32);
tb.setTouchy("touchy");
TestBean tb2 = new TestBean();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
@Test
void copyPropertiesWithDifferentTypes1() throws Exception {
DerivedTestBean tb = new DerivedTestBean();
tb.setName("rod");
tb.setAge(32);
tb.setTouchy("touchy");
TestBean tb2 = new TestBean();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
@Test
void copyPropertiesWithDifferentTypes2() throws Exception {
TestBean tb = new TestBean();
tb.setName("rod");
tb.setAge(32);
tb.setTouchy("touchy");
DerivedTestBean tb2 = new DerivedTestBean();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
/**
* {@code Integer} can be copied to {@code Number}.
*/
@Test
void copyPropertiesFromSubTypeToSuperType() {
IntegerHolder integerHolder = new IntegerHolder();
integerHolder.setNumber(42);
NumberHolder numberHolder = new NumberHolder();
BeanUtils.copyProperties(integerHolder, numberHolder);
assertThat(integerHolder.getNumber()).isEqualTo(42);
assertThat(numberHolder.getNumber()).isEqualTo(42);
}
/**
* {@code List<Integer>} can be copied to {@code List<Integer>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() {
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
integerListHolder1.getList().add(42);
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2();
BeanUtils.copyProperties(integerListHolder1, integerListHolder2);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(integerListHolder2.getList()).containsExactly(42);
}
/**
* {@code List<?>} can be copied to {@code List<?>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() {
List<?> list = List.of("foo", 42);
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1();
wildcardListHolder1.setList(list);
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
assertThat(wildcardListHolder2.getList()).isEmpty();
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2);
assertThat(wildcardListHolder1.getList()).isEqualTo(list);
assertThat(wildcardListHolder2.getList()).isEqualTo(list);
}
/**
* {@code List<Integer>} can be copied to {@code List<?>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() {
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
integerListHolder1.getList().add(42);
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42));
}
/**
* {@code List<Integer>} can be copied to {@code List<? extends Number>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() {
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
integerListHolder1.getList().add(42);
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder();
BeanUtils.copyProperties(integerListHolder1, numberListHolder);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(numberListHolder.getList()).isEqualTo(List.of(42));
}
/**
* {@code Number} can NOT be copied to {@code Integer}.
*/
@Test
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() {
NumberHolder numberHolder = new NumberHolder();
numberHolder.setNumber(42);
IntegerHolder integerHolder = new IntegerHolder();
BeanUtils.copyProperties(numberHolder, integerHolder);
assertThat(numberHolder.getNumber()).isEqualTo(42);
assertThat(integerHolder.getNumber()).isNull();
}
/**
* {@code List<Integer>} can NOT be copied to {@code List<Long>}.
*/
@Test
void copyPropertiesDoesNotHonorGenericTypeMismatches() {
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
integerListHolder.getList().add(42);
LongListHolder longListHolder = new LongListHolder();
BeanUtils.copyProperties(integerListHolder, longListHolder);
assertThat(integerListHolder.getList()).containsExactly(42);
assertThat(longListHolder.getList()).isEmpty();
}
/**
* {@code List<Integer>} can NOT be copied to {@code List<Number>}.
*/
@Test
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() {
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
integerListHolder.getList().add(42);
NumberListHolder numberListHolder = new NumberListHolder();
BeanUtils.copyProperties(integerListHolder, numberListHolder);
assertThat(integerListHolder.getList()).containsExactly(42);
assertThat(numberListHolder.getList()).isEmpty();
}
@Test // gh-26531
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
Order original = new Order("test", List.of("foo", "bar"));
// Create a Proxy that loses the generic type information for the getLineItems() method.
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
// Ensure that our custom Proxy works as expected.
assertThat(proxy.getId()).isEqualTo("test");
assertThat(proxy.getLineItems()).containsExactly("foo", "bar");
// Copy from proxy to target.
Order target = new Order();
BeanUtils.copyProperties(proxy, target);
assertThat(target.getId()).isEqualTo("test");
assertThat(target.getLineItems()).containsExactly("foo", "bar");
}
@Test // gh-32888
public void copyPropertiesWithGenericCglibClass() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
User user = (User) enhancer.create();
user.setId(1);
user.setName("proxy");
user.setAddress("addr");
User target = new User();
BeanUtils.copyProperties(user, target);
assertThat(target.getId()).isEqualTo(user.getId());
assertThat(target.getName()).isEqualTo(user.getName());
assertThat(target.getAddress()).isEqualTo(user.getAddress());
}
@Test
void copyPropertiesWithEditable() throws Exception {
TestBean tb = new TestBean();
assertThat(tb.getName()).as("Name empty").isNull();
tb.setAge(32);
tb.setTouchy("bla");
TestBean tb2 = new TestBean();
tb2.setName("rod");
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
// "touchy" should not be copied: it's not defined in ITestBean
BeanUtils.copyProperties(tb, tb2, ITestBean.class);
assertThat(tb2.getName()).as("Name copied").isNull();
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32);
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
}
@Test
void copyPropertiesWithIgnore() throws Exception {
TestBean tb = new TestBean();
assertThat(tb.getName()).as("Name empty").isNull();
tb.setAge(32);
tb.setTouchy("bla");
TestBean tb2 = new TestBean();
tb2.setName("rod");
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
// "spouse", "touchy", "age" should not be copied
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age");
assertThat(tb2.getName()).as("Name copied").isNull();
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
}
@Test
void copyPropertiesWithIgnoredNonExistingProperty() {
NameAndSpecialProperty source = new NameAndSpecialProperty();
source.setName("name");
TestBean target = new TestBean();
BeanUtils.copyProperties(source, target, "specialProperty");
assertThat(target.getName()).isEqualTo("name");
}
@Test
void copyPropertiesWithInvalidProperty() {
InvalidProperty source = new InvalidProperty();
source.setName("name");
source.setFlag1(true);
source.setFlag2(true);
InvalidProperty target = new InvalidProperty();
BeanUtils.copyProperties(source, target);
assertThat(target.getName()).isEqualTo("name");
assertThat((boolean) target.getFlag1()).isTrue();
assertThat(target.getFlag2()).isTrue();
}
@Test
void resolveSimpleSignature() throws Exception {
Method desiredMethod = MethodSignatureBean.class.getMethod("doSomething");
@@ -168,14 +427,14 @@ class BeanUtilsTests {
@Test
void resolveInvalidSignatureEndParen() {
assertThatIllegalArgumentException()
.isThrownBy(() -> BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class));
assertThatIllegalArgumentException().isThrownBy(() ->
BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class));
}
@Test
void resolveInvalidSignatureStartParen() {
assertThatIllegalArgumentException()
.isThrownBy(() -> BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class));
assertThatIllegalArgumentException().isThrownBy(() ->
BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class));
}
@Test
@@ -279,283 +538,6 @@ class BeanUtilsTests {
}
@Nested
class CopyPropertiesTests {
@Test
void copyProperties() throws Exception {
TestBean tb = new TestBean();
tb.setName("rod");
tb.setAge(32);
tb.setTouchy("touchy");
TestBean tb2 = new TestBean();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
@Test
void copyPropertiesWithDifferentTypes1() throws Exception {
DerivedTestBean tb = new DerivedTestBean();
tb.setName("rod");
tb.setAge(32);
tb.setTouchy("touchy");
TestBean tb2 = new TestBean();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
@Test
void copyPropertiesWithDifferentTypes2() throws Exception {
TestBean tb = new TestBean();
tb.setName("rod");
tb.setAge(32);
tb.setTouchy("touchy");
DerivedTestBean tb2 = new DerivedTestBean();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
/**
* {@code Integer} can be copied to {@code Number}.
*/
@Test
void copyPropertiesFromSubTypeToSuperType() {
IntegerHolder integerHolder = new IntegerHolder();
integerHolder.setNumber(42);
NumberHolder numberHolder = new NumberHolder();
BeanUtils.copyProperties(integerHolder, numberHolder);
assertThat(integerHolder.getNumber()).isEqualTo(42);
assertThat(numberHolder.getNumber()).isEqualTo(42);
}
/**
* {@code List<Integer>} can be copied to {@code List<Integer>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() {
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
integerListHolder1.getList().add(42);
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2();
BeanUtils.copyProperties(integerListHolder1, integerListHolder2);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(integerListHolder2.getList()).containsExactly(42);
}
/**
* {@code List<?>} can be copied to {@code List<?>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() {
List<?> list = List.of("foo", 42);
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1();
wildcardListHolder1.setList(list);
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
assertThat(wildcardListHolder2.getList()).isEmpty();
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2);
assertThat(wildcardListHolder1.getList()).isEqualTo(list);
assertThat(wildcardListHolder2.getList()).isEqualTo(list);
}
/**
* {@code List<Integer>} can be copied to {@code List<?>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() {
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
integerListHolder1.getList().add(42);
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42));
}
/**
* {@code List<Integer>} can be copied to {@code List<? extends Number>}.
*/
@Test
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() {
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
integerListHolder1.getList().add(42);
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder();
BeanUtils.copyProperties(integerListHolder1, numberListHolder);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(numberListHolder.getList()).isEqualTo(List.of(42));
}
/**
* {@code Number} can NOT be copied to {@code Integer}.
*/
@Test
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() {
NumberHolder numberHolder = new NumberHolder();
numberHolder.setNumber(42);
IntegerHolder integerHolder = new IntegerHolder();
BeanUtils.copyProperties(numberHolder, integerHolder);
assertThat(numberHolder.getNumber()).isEqualTo(42);
assertThat(integerHolder.getNumber()).isNull();
}
/**
* {@code List<Integer>} can NOT be copied to {@code List<Long>}.
*/
@Test
void copyPropertiesDoesNotHonorGenericTypeMismatches() {
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
integerListHolder.getList().add(42);
LongListHolder longListHolder = new LongListHolder();
BeanUtils.copyProperties(integerListHolder, longListHolder);
assertThat(integerListHolder.getList()).containsExactly(42);
assertThat(longListHolder.getList()).isEmpty();
}
/**
* {@code List<Integer>} can NOT be copied to {@code List<Number>}.
*/
@Test
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() {
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
integerListHolder.getList().add(42);
NumberListHolder numberListHolder = new NumberListHolder();
BeanUtils.copyProperties(integerListHolder, numberListHolder);
assertThat(integerListHolder.getList()).containsExactly(42);
assertThat(numberListHolder.getList()).isEmpty();
}
@Test // gh-26531
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
Order original = new Order("test", List.of("foo", "bar"));
// Create a Proxy that loses the generic type information for the getLineItems() method.
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
// Ensure that our custom Proxy works as expected.
assertThat(proxy.getId()).isEqualTo("test");
assertThat(proxy.getLineItems()).containsExactly("foo", "bar");
// Copy from proxy to target.
Order target = new Order();
BeanUtils.copyProperties(proxy, target);
assertThat(target.getId()).isEqualTo("test");
assertThat(target.getLineItems()).containsExactly("foo", "bar");
}
@Test // gh-32888
void copyPropertiesWithGenericCglibClass() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
User user = (User) enhancer.create();
user.setId(1);
user.setName("proxy");
user.setAddress("addr");
User target = new User();
BeanUtils.copyProperties(user, target);
assertThat(target.getId()).isEqualTo(user.getId());
assertThat(target.getName()).isEqualTo(user.getName());
assertThat(target.getAddress()).isEqualTo(user.getAddress());
}
@Test
void copyPropertiesWithEditable() throws Exception {
TestBean tb = new TestBean();
assertThat(tb.getName()).as("Name empty").isNull();
tb.setAge(32);
tb.setTouchy("bla");
TestBean tb2 = new TestBean();
tb2.setName("rod");
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
// "touchy" should not be copied: it's not defined in ITestBean
BeanUtils.copyProperties(tb, tb2, ITestBean.class);
assertThat(tb2.getName()).as("Name copied").isNull();
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32);
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
}
@Test
void copyPropertiesWithIgnore() throws Exception {
TestBean tb = new TestBean();
assertThat(tb.getName()).as("Name empty").isNull();
tb.setAge(32);
tb.setTouchy("bla");
TestBean tb2 = new TestBean();
tb2.setName("rod");
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
// "spouse", "touchy", "age" should not be copied
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age");
assertThat(tb2.getName()).as("Name copied").isNull();
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
}
@Test
void copyPropertiesWithIgnoredNonExistingProperty() {
NameAndSpecialProperty source = new NameAndSpecialProperty();
source.setName("name");
TestBean target = new TestBean();
BeanUtils.copyProperties(source, target, "specialProperty");
assertThat(target.getName()).isEqualTo("name");
}
@Test
void copyPropertiesWithInvalidProperty() {
InvalidProperty source = new InvalidProperty();
source.setName("name");
source.setFlag1(true);
source.setFlag2(true);
InvalidProperty target = new InvalidProperty();
BeanUtils.copyProperties(source, target);
assertThat(target.getName()).isEqualTo("name");
assertThat((boolean) target.getFlag1()).isTrue();
assertThat(target.getFlag2()).isTrue();
}
@Test // gh-36019
void copyPropertiesHonorsGenericsInTypeHieararchyAndIgnoresOverloadedSetterMethod() {
var source = new ServiceWithOverriddenGetterAndOverloadedSetter();
var target = new ServiceWithOverriddenGetterAndOverloadedSetter();
source.setId(1);
BeanUtils.copyProperties(source, target);
assertThat(target.getId()).isEqualTo(source.getId()).isEqualTo("1");
}
}
public record RecordWithMultiplePublicConstructors(String value, String name) {
@SuppressWarnings("unused")
public RecordWithMultiplePublicConstructors(String value) {
@@ -1,265 +0,0 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.Parameter;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.FieldSource;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.junit.jupiter.api.Named.named;
/**
* Unit tests for property descriptor resolution via
* {@link PropertyDescriptorUtils#determineBasicProperties(Class)}.
*
* <p>Results are compared to the behavior of the standard {@link Introspector}.
*
* @author Sam Brannen
* @since 6.2.16
*/
@ParameterizedClass(name = "{0}")
@FieldSource("resolvers")
class PropertyDescriptorUtilsPropertyResolutionTests {
static final List<Named<PropertiesResolver>> resolvers = List.of(
named("Basic Properties", new BasicPropertiesResolver()),
named("Standard Properties", new StandardPropertiesResolver()));
@Parameter
PropertiesResolver resolver;
@Test
void determineBasicPropertiesWithUnresolvedGenericsInInterface() {
var pdMap = resolver.resolve(GenericService.class);
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
}
@Test
void determineBasicPropertiesWithUnresolvedGenericsInSubInterface() {
// FYI: java.beans.Introspector does not resolve properties for sub-interfaces.
assumeThat(resolver).isNotInstanceOf(StandardPropertiesResolver.class);
var pdMap = resolver.resolve(SubGenericService.class);
assertThat(pdMap).containsOnlyKeys("id");
assertReadAndWriteMethodsForId(pdMap.get("id"), Object.class, Object.class);
}
@Test
void resolvePropertiesWithUnresolvedGenericsInClass() {
var pdMap = resolver.resolve(BaseService.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
}
@Test // gh-36019
void resolvePropertiesInSubclassWithOverriddenGetterAndSetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, String.class);
}
@Test // gh-36019
void resolvePropertiesWithUnresolvedGenericsInSubclassWithOverloadedSetter() {
var pdMap = resolver.resolve(ServiceWithOverloadedSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, Object.class, Object.class);
}
@Test // gh-36019
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
}
@Test // gh-36019
void resolvePropertiesWithPartiallyUnresolvedGenericsInSubclassWithOverriddenGetterAndOverloadedSetter() {
var pdMap = resolver.resolve(ServiceWithOverriddenGetterAndOverloadedSetter.class);
assertReadAndWriteMethodsForClassAndId(pdMap, String.class, Object.class);
}
private static void assertReadAndWriteMethodsForClassAndId(Map<String, List<PropertyDescriptor>> pdMap,
Class<?> readType, Class<?> writeType) {
assertThat(pdMap).containsOnlyKeys("class", "id");
assertReadAndWriteMethodsForClass(pdMap.get("class"));
assertReadAndWriteMethodsForId(pdMap.get("id"), readType, writeType);
}
private static void assertReadAndWriteMethodsForClass(List<PropertyDescriptor> pds) {
assertThat(pds).hasSize(1);
var pd = pds.get(0);
assertThat(pd.getName()).isEqualTo("class");
var readMethod = pd.getReadMethod();
assertThat(readMethod.getName()).isEqualTo("getClass");
assertThat(readMethod.getReturnType()).as("read type").isEqualTo(Class.class);
assertThat(readMethod.getParameterCount()).isZero();
assertThat(pd.getWriteMethod()).as("write method").isNull();
}
private static void assertReadAndWriteMethodsForId(List<PropertyDescriptor> pds, Class<?> readType, Class<?> writeType) {
assertThat(pds).hasSize(1);
var pd = pds.get(0);
assertThat(pd.getName()).isEqualTo("id");
var readMethod = pd.getReadMethod();
var writeMethod = pd.getWriteMethod();
assertSoftly(softly -> {
softly.assertThat(readMethod.getName()).isEqualTo("getId");
softly.assertThat(readMethod.getReturnType()).as("read type").isEqualTo(readType);
softly.assertThat(readMethod.getParameterCount()).isZero();
softly.assertThat(writeMethod).as("write method").isNotNull();
if (writeMethod != null) {
softly.assertThat(writeMethod.getName()).isEqualTo("setId");
softly.assertThat(writeMethod.getReturnType()).isEqualTo(void.class);
softly.assertThat(writeMethod.getParameterCount()).isEqualTo(1);
softly.assertThat(writeMethod.getParameterTypes()[0]).as("write type").isEqualTo(writeType);
}
});
}
private static Map<String, List<PropertyDescriptor>> toMap(Stream<? extends PropertyDescriptor> stream) {
return stream.collect(groupingBy(PropertyDescriptor::getName, toList()));
}
interface PropertiesResolver {
Map<String, List<PropertyDescriptor>> resolve(Class<?> beanClass);
}
static class BasicPropertiesResolver implements PropertiesResolver {
@Override
public Map<String, List<PropertyDescriptor>> resolve(Class<?> beanClass) {
try {
var pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
return toMap(pds.stream());
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
static class StandardPropertiesResolver implements PropertiesResolver {
@Override
public Map<String, List<PropertyDescriptor>> resolve(Class<?> beanClass) {
try {
var beanInfo = Introspector.getBeanInfo(beanClass);
return toMap(Arrays.stream(beanInfo.getPropertyDescriptors()));
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
interface GenericService<T> {
void setId(T id);
T getId();
}
interface SubGenericService<T> extends GenericService<T> {
}
static class BaseService<T> {
private T id;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
}
static class ServiceWithOverriddenGetterAndSetter extends BaseService<String>
implements SubGenericService<String> {
@Override
public String getId() {
return super.getId();
}
@Override
public void setId(String id) {
super.setId(id);
}
}
static class ServiceWithOverloadedSetter extends BaseService<String>
implements SubGenericService<String> {
public void setId(int id) {
setId(String.valueOf(id));
}
}
static class ServiceWithOverriddenGetter extends BaseService<String>
implements SubGenericService<String> {
@Override
public String getId() {
return super.getId();
}
}
static class ServiceWithOverriddenGetterAndOverloadedSetter extends BaseService<String>
implements SubGenericService<String> {
@Override
public String getId() {
return super.getId();
}
public void setId(int id) {
setId(String.valueOf(id));
}
}
}
@@ -380,7 +380,7 @@ class UtilNamespaceHandlerTests {
// For DependencyDescriptor resolution
Map<String, TestBean> mapWithRef;
Map<String, TestBean> mapWithTypes;
private Map<String, TestBean> mapWithRef;
private Map<String, TestBean> mapWithTypes;
}
@@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
class CaffeineReactiveCachingTests {
@ParameterizedTest
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeWithoutNullValuesConfig.class})
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeConfig.class})
void cacheHitDetermination(Class<?> configClass) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
@@ -106,8 +106,9 @@ class CaffeineReactiveCachingTests {
ctx.close();
}
@ParameterizedTest
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeWithoutNullValuesConfig.class})
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeConfig.class})
void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
@@ -39,12 +39,10 @@ class CacheEvaluationContextFactory {
@Nullable
private Supplier<ParameterNameDiscoverer> parameterNameDiscoverer;
CacheEvaluationContextFactory(StandardEvaluationContext originalContext) {
this.originalContext = originalContext;
}
public void setParameterNameDiscoverer(Supplier<ParameterNameDiscoverer> parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
@@ -56,7 +54,6 @@ class CacheEvaluationContextFactory {
return this.parameterNameDiscoverer.get();
}
/**
* Creates a {@link CacheEvaluationContext} for the specified operation.
* @param rootObject the {@code root} object to use for the context
@@ -35,11 +35,12 @@ import org.springframework.core.annotation.AliasFor;
* example:
*
* <pre class="code">
* &#064;Bean
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }</pre>
* &#064;Bean
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }
* </pre>
*
* <h3>Bean Names</h3>
*
@@ -51,29 +52,31 @@ import org.springframework.core.annotation.AliasFor;
* (i.e. a primary bean name plus one or more aliases) for a single bean.
*
* <pre class="code">
* &#064;Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }</pre>
* &#064;Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }
* </pre>
*
* <h3>Profile, Scope, Lazy, DependsOn, Primary, Fallback, Order</h3>
* <h3>Profile, Scope, Lazy, DependsOn, Primary, Order</h3>
*
* <p>Note that the {@code @Bean} annotation does not provide attributes for profile,
* scope, lazy, depends-on, or primary. Rather, it should be used in conjunction with
* {@link Scope @Scope}, {@link Lazy @Lazy}, {@link DependsOn @DependsOn}, and
* scope, lazy, depends-on or primary. Rather, it should be used in conjunction with
* {@link Scope @Scope}, {@link Lazy @Lazy}, {@link DependsOn @DependsOn} and
* {@link Primary @Primary} annotations to declare those semantics. For example:
*
* <pre class="code">
* &#064;Bean
* &#064;Profile("production")
* &#064;Scope("prototype")
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }</pre>
* &#064;Bean
* &#064;Profile("production")
* &#064;Scope("prototype")
* public MyBean myBean() {
* // instantiate and configure MyBean obj
* return obj;
* }
* </pre>
*
* The semantics of the aforementioned annotations match their use at the component
* The semantics of the above-mentioned annotations match their use at the component
* class level: {@code @Profile} allows for selective inclusion of certain beans.
* {@code @Scope} changes the bean's scope from singleton to the specified scope.
* {@code @Lazy} only has an actual effect in case of the default singleton scope.
@@ -82,9 +85,6 @@ import org.springframework.core.annotation.AliasFor;
* through direct references, which is typically helpful for singleton startup.
* {@code @Primary} is a mechanism to resolve ambiguity at the injection point level
* if a single target component needs to be injected but several beans match by type.
* {@link Fallback @Fallback} marks a bean as a fallback candidate in such scenarios;
* if all beans but one among multiple matching candidates are marked as fallback, the
* remaining bean will be selected.
*
* <p>Additionally, {@code @Bean} methods may also declare qualifier annotations
* and {@link org.springframework.core.annotation.Order @Order} values, to be
@@ -100,8 +100,8 @@ import org.springframework.core.annotation.AliasFor;
* orthogonal concern determined by dependency relationships and {@code @DependsOn}
* declarations as mentioned above. Also, {@link jakarta.annotation.Priority} is not
* available at this level since it cannot be declared on methods; its semantics can
* be modeled through {@code @Order} values in combination with {@code @Primary} or
* {@code @Fallback} on a single bean per type.
* be modeled through {@code @Order} values in combination with {@code @Primary} on
* a single bean per type.
*
* <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3>
*
@@ -119,17 +119,17 @@ import org.springframework.core.annotation.AliasFor;
* &#064;Configuration
* public class AppConfig {
*
* &#064;Bean
* public FooService fooService() {
* return new FooService(fooRepository());
* }
* &#064;Bean
* public FooService fooService() {
* return new FooService(fooRepository());
* }
*
* &#064;Bean
* public FooRepository fooRepository() {
* return new JdbcFooRepository(dataSource());
* }
* &#064;Bean
* public FooRepository fooRepository() {
* return new JdbcFooRepository(dataSource());
* }
*
* // ...
* // ...
* }</pre>
*
* <h3>{@code @Bean} <em>Lite</em> Mode</h3>
@@ -158,21 +158,20 @@ import org.springframework.core.annotation.AliasFor;
* <pre class="code">
* &#064;Component
* public class Calculator {
* public int sum(int a, int b) {
* return a+b;
* }
* public int sum(int a, int b) {
* return a+b;
* }
*
* &#064;Bean
* public MyBean myBean() {
* return new MyBean();
* }
* &#064;Bean
* public MyBean myBean() {
* return new MyBean();
* }
* }</pre>
*
* <h3>Bootstrapping</h3>
*
* <p>See the {@link Configuration @Configuration} javadoc for further details
* including how to bootstrap the container using
* {@link AnnotationConfigApplicationContext} and friends.
* <p>See the @{@link Configuration} javadoc for further details including how to bootstrap
* the container using {@link AnnotationConfigApplicationContext} and friends.
*
* <h3>{@code BeanFactoryPostProcessor}-returning {@code @Bean} methods</h3>
*
@@ -184,44 +183,20 @@ import org.springframework.core.annotation.AliasFor;
* lifecycle issues, mark {@code BFPP}-returning {@code @Bean} methods as {@code static}. For example:
*
* <pre class="code">
* &#064;Bean
* public static PropertySourcesPlaceholderConfigurer pspc() {
* // instantiate, configure and return pspc ...
* }</pre>
* &#064;Bean
* public static PropertySourcesPlaceholderConfigurer pspc() {
* // instantiate, configure and return pspc ...
* }
* </pre>
*
* By marking this method as {@code static}, it can be invoked without causing instantiation of its
* declaring {@code @Configuration} class, thus avoiding the aforementioned lifecycle conflicts.
* declaring {@code @Configuration} class, thus avoiding the above-mentioned lifecycle conflicts.
* Note however that {@code static} {@code @Bean} methods will not be enhanced for scoping and AOP
* semantics as mentioned above. This works out in {@code BFPP} cases, as they are not typically
* referenced by other {@code @Bean} methods. As a reminder, an INFO-level log message will be
* issued for any non-static {@code @Bean} methods having a return type assignable to
* {@code BeanFactoryPostProcessor}.
*
* <h3>{@code BeanPostProcessor}-returning {@code @Bean} methods</h3>
*
* <p>Similarly, special consideration must be taken for {@code @Bean} methods that return Spring
* {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}
* ({@code BPP}) types. Because {@code BPP} objects must be instantiated early in the container
* lifecycle, a non-static {@code @Bean} method that returns a {@code BPP} will cause eager
* initialization of its declaring {@code @Configuration} class, which can make other beans in the
* {@code @Configuration} class (as well as depencencies of those beans) ineligible for full
* post-processing. To avoid these lifecycle issues, mark {@code BPP}-returning {@code @Bean}
* methods as {@code static}. For example:
*
* <pre class="code">
* &#064;Bean
* public static MyBeanPostProcessor myBeanPostProcessor() {
* return new MyBeanPostProcessor();
* }</pre>
*
* By marking this method as {@code static}, it can be invoked without causing instantiation of its
* declaring {@code @Configuration} class. Furthermore, the method should ideally not declare any
* dependencies so that the container does not need to instantiate other beans to create the
* post-processor, which would make those beans ineligible for post-processing as well. For any such
* bean, you should see a WARN-level log message similar to the following: "Bean 'someBean' of type
* [org.example.SomeType] is not eligible for getting processed by all BeanPostProcessors (for example:
* not eligible for auto-proxying)".
*
* @author Rod Johnson
* @author Costin Leau
* @author Chris Beams
@@ -233,7 +208,6 @@ import org.springframework.core.annotation.AliasFor;
* @see DependsOn
* @see Lazy
* @see Primary
* @see Fallback
* @see org.springframework.stereotype.Component
* @see org.springframework.beans.factory.annotation.Autowired
* @see org.springframework.beans.factory.annotation.Value
@@ -299,8 +299,8 @@ class ConfigurationClassBeanDefinitionReader {
}
if (logger.isTraceEnabled()) {
logger.trace("Registering bean definition for @Bean method %s.%s() with bean name '%s'"
.formatted(configClass.getMetadata().getClassName(), methodName, beanName));
logger.trace("Registering bean definition for @Bean method %s.%s()"
.formatted(configClass.getMetadata().getClassName(), beanName));
}
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
@@ -357,8 +357,8 @@ class ConfigurationClassBeanDefinitionReader {
"@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
}
if (logger.isDebugEnabled()) {
logger.debug(("Skipping bean definition for %s: a definition for bean '%s' already exists. " +
"This top-level bean definition is considered as an override.").formatted(beanMethod, beanName));
logger.debug("Skipping bean definition for %s: a definition for bean '%s' already exists. " +
"This top-level bean definition is considered as an override.".formatted(beanMethod, beanName));
}
return true;
}
@@ -389,13 +389,13 @@ class ConfigurationClassEnhancer {
// create the bean instance.
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
logger.info("""
@Bean method %s.%s is non-static and returns an object assignable to Spring's \
BeanFactoryPostProcessor interface. This will result in a failure to process \
annotations such as @Autowired, @Resource, and @PostConstruct within the method's \
declaring @Configuration class. Add the 'static' modifier to this method to avoid \
these container lifecycle issues; see @Bean javadoc for complete details."""
.formatted(beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
"result in a failure to process annotations such as @Autowired, " +
"@Resource and @PostConstruct within the method's declaring " +
"@Configuration class. Add the 'static' modifier to this method to avoid " +
"these container lifecycle issues; see @Bean javadoc for complete details.",
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
}
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
@@ -27,7 +27,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -1068,9 +1067,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
if (!tryLockForShutdown()) {
if (isStartupShutdownThreadStuck()) {
active.set(false);
return;
}
startupShutdownLock.lock();
try {
doClose();
}
@@ -1083,30 +1084,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
}
/**
* Try to acquire the common startup/shutdown lock, backing out if
* the main startup/shutdown thread is stuck or on interruption.
* @see #isStartupShutdownThreadStuck()
*/
private boolean tryLockForShutdown() {
try {
while (!this.startupShutdownLock.tryLock(100, TimeUnit.MILLISECONDS)) {
if (!this.active.get() || this.closed.get()) {
return false;
}
if (isStartupShutdownThreadStuck()) {
this.active.set(false);
return false;
}
}
return true;
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* Determine whether an active startup/shutdown thread is currently stuck,
* for example, through a {@code System.exit} call in a user component.
@@ -1118,7 +1095,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
activeThread.interrupt();
try {
// Leave just a little bit of time for the interruption to show effect
Thread.sleep(10);
Thread.sleep(1);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
@@ -1140,10 +1117,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
@Override
public void close() {
if (!tryLockForShutdown()) {
if (isStartupShutdownThreadStuck()) {
this.active.set(false);
return;
}
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
@@ -54,7 +54,7 @@ public abstract class AbstractResourceBasedMessageSource extends AbstractMessage
* Set a single basename, following the basic ResourceBundle convention
* of not specifying file extension or language codes. The resource location
* format is up to the specific {@code MessageSource} implementation.
* <p>Regular and XML properties files are supported: for example, "messages" will find
* <p>Regular and XMl properties files are supported: for example, "messages" will find
* a "messages.properties", "messages_en.properties" etc arrangement as well
* as "messages.xml", "messages_en.xml" etc.
* @param basename the single basename
@@ -23,8 +23,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -100,8 +98,7 @@ class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor,
try {
ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();
multicaster.removeApplicationListener(applicationListener);
multicaster.removeApplicationListenerBean(
bean instanceof FactoryBean ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
multicaster.removeApplicationListenerBean(beanName);
}
catch (IllegalStateException ex) {
// ApplicationEventMulticaster not initialized yet - no need to remove a listener
@@ -92,12 +92,10 @@ import org.springframework.util.StringUtils;
public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource
implements ResourceLoaderAware {
private static final String PROPERTIES_EXTENSION = ".properties";
private static final String XML_EXTENSION = ".xml";
private List<String> fileExtensions = List.of(PROPERTIES_EXTENSION, XML_EXTENSION);
private List<String> fileExtensions = List.of(".properties", XML_EXTENSION);
@Nullable
private Properties fileEncodings;
@@ -383,18 +381,18 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
StringBuilder temp = new StringBuilder(basename);
temp.append('_');
if (!language.isEmpty()) {
if (language.length() > 0) {
temp.append(language);
result.add(0, temp.toString());
}
temp.append('_');
if (!country.isEmpty()) {
if (country.length() > 0) {
temp.append(country);
result.add(0, temp.toString());
}
if (!variant.isEmpty() && (!language.isEmpty() || !country.isEmpty())) {
if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) {
temp.append('_').append(variant);
result.add(0, temp.toString());
}
@@ -564,13 +562,13 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
*/
protected Properties loadProperties(Resource resource, String filename) throws IOException {
Properties props = newProperties();
try (InputStream inputStream = resource.getInputStream()) {
try (InputStream is = resource.getInputStream()) {
String resourceFilename = resource.getFilename();
if (resourceFilename != null && resourceFilename.endsWith(XML_EXTENSION)) {
if (logger.isDebugEnabled()) {
logger.debug("Loading properties [" + resource.getFilename() + "]");
}
this.propertiesPersister.loadFromXml(props, inputStream);
this.propertiesPersister.loadFromXml(props, is);
}
else {
String encoding = null;
@@ -584,13 +582,13 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
if (logger.isDebugEnabled()) {
logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'");
}
this.propertiesPersister.load(props, new InputStreamReader(inputStream, encoding));
this.propertiesPersister.load(props, new InputStreamReader(is, encoding));
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Loading properties [" + resource.getFilename() + "]");
}
this.propertiesPersister.load(props, inputStream);
this.propertiesPersister.load(props, is);
}
}
return props;
@@ -242,8 +242,8 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
* @since 6.1.4
* @see #initiateShutdown()
*/
public void setStrictEarlyShutdown(boolean strictEarlyShutdown) {
this.strictEarlyShutdown = strictEarlyShutdown;
public void setStrictEarlyShutdown(boolean defaultEarlyShutdown) {
this.strictEarlyShutdown = defaultEarlyShutdown;
}
/**
@@ -377,45 +377,47 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
protected void postProcessConfiguration(Configuration<?> configuration) {
}
private ValidatorFactory obtainValidatorFactory() {
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory;
}
@Override
public Validator getValidator() {
return obtainValidatorFactory().getValidator();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getValidator();
}
@Override
public ValidatorContext usingContext() {
return obtainValidatorFactory().usingContext();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.usingContext();
}
@Override
public MessageInterpolator getMessageInterpolator() {
return obtainValidatorFactory().getMessageInterpolator();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getMessageInterpolator();
}
@Override
public TraversableResolver getTraversableResolver() {
return obtainValidatorFactory().getTraversableResolver();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getTraversableResolver();
}
@Override
public ConstraintValidatorFactory getConstraintValidatorFactory() {
return obtainValidatorFactory().getConstraintValidatorFactory();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getConstraintValidatorFactory();
}
@Override
public ParameterNameProvider getParameterNameProvider() {
return obtainValidatorFactory().getParameterNameProvider();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getParameterNameProvider();
}
@Override
public ClockProvider getClockProvider() {
return obtainValidatorFactory().getClockProvider();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getClockProvider();
}
@Override
@@ -425,8 +427,8 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
try {
return super.unwrap(type);
}
catch (ValidationException ignored) {
// Trying ValidatorFactory unwrapping next
catch (ValidationException ex) {
// Ignore - we'll try ValidatorFactory unwrapping next
}
}
if (this.validatorFactory != null) {
@@ -26,7 +26,7 @@ import org.springframework.context.support.MessageSourceResourceBundle;
import org.springframework.util.Assert;
/**
* Implementation of Hibernate Validator's {@link ResourceBundleLocator} interface,
* Implementation of Hibernate Validator 4.3/5.x's {@link ResourceBundleLocator} interface,
* exposing a Spring {@link MessageSource} as localized {@link MessageSourceResourceBundle}.
*
* @author Juergen Hoeller
@@ -39,7 +39,6 @@ public class MessageSourceResourceBundleLocator implements ResourceBundleLocator
private final MessageSource messageSource;
/**
* Build a MessageSourceResourceBundleLocator for the given MessageSource.
* @param messageSource the Spring MessageSource to wrap
@@ -49,7 +48,6 @@ public class MessageSourceResourceBundleLocator implements ResourceBundleLocator
this.messageSource = messageSource;
}
@Override
public ResourceBundle getResourceBundle(Locale locale) {
return new MessageSourceResourceBundle(this.messageSource, locale);
@@ -134,15 +134,20 @@ class ResourceElementResolverMethodTests {
private String one;
private String test;
private Integer count;
public void setOne(String one) {
this.one = one;
}
public void setTest(String test) {
this.test = test;
}
public void setCount(Integer count) {
this.count = count;
}
}
@@ -29,7 +29,6 @@ import org.mockito.ArgumentCaptor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
@@ -380,15 +379,12 @@ class ApplicationContextEventTests extends AbstractApplicationEventListenerTests
RootBeanDefinition listener1Def = new RootBeanDefinition(MyOrderedListener1.class);
listener1Def.setDependsOn("nestedChild");
context.registerBeanDefinition("listener1", listener1Def);
context.registerBeanDefinition("listenerFb", new RootBeanDefinition(MyFactoryBeanListener.class));
context.refresh();
MyOrderedListener1 listener1 = context.getBean("listener1", MyOrderedListener1.class);
MyFactoryBeanListener listenerFb = context.getBean("&listenerFb", MyFactoryBeanListener.class);
MyEvent event1 = new MyEvent(context);
context.publishEvent(event1);
assertThat(listener1.seenEvents).contains(event1);
assertThat(listenerFb.seenEvents).contains(event1);
SimpleApplicationEventMulticaster multicaster = context.getBean(SimpleApplicationEventMulticaster.class);
assertThat(multicaster.getApplicationListeners()).isNotEmpty();
@@ -730,27 +726,6 @@ class ApplicationContextEventTests extends AbstractApplicationEventListenerTests
}
public static class MyFactoryBeanListener implements FactoryBean<String>, ApplicationListener<ApplicationEvent> {
public final List<ApplicationEvent> seenEvents = new ArrayList<>();
@Override
public void onApplicationEvent(ApplicationEvent event) {
this.seenEvents.add(event);
}
@Override
public String getObject() {
return "";
}
@Override
public Class<?> getObjectType() {
return String.class;
}
}
public static class EventPublishingBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@@ -184,7 +184,7 @@ class PeriodicTriggerTests {
void equalsVerification() {
PeriodicTrigger trigger1 = new PeriodicTrigger(Duration.ofMillis(3000));
PeriodicTrigger trigger2 = new PeriodicTrigger(Duration.ofMillis(3000));
assertThat(trigger1).isNotEqualTo(new String("not a trigger"));
assertThat(trigger1.equals(new String("not a trigger"))).isFalse();
assertThat(trigger1).isNotEqualTo(null);
assertThat(trigger1).isEqualTo(trigger1);
assertThat(trigger2).isEqualTo(trigger2);
@@ -60,13 +60,18 @@ class BshScriptFactoryTests {
Calculator calc = (Calculator) ctx.getBean("calculator");
Messenger messenger = (Messenger) ctx.getBean("messenger");
assertThat(calc).as("Scripted object should not be instance of Refreshable").isNotInstanceOf(Refreshable.class);
assertThat(messenger).as("Scripted object should not be instance of Refreshable").isNotInstanceOf(Refreshable.class);
boolean condition3 = calc instanceof Refreshable;
assertThat(condition3).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition2 = messenger instanceof Refreshable;
assertThat(condition2).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(calc).isEqualTo(calc);
assertThat(messenger).isEqualTo(messenger);
boolean condition1 = !messenger.equals(calc);
assertThat(condition1).isTrue();
assertThat(messenger.hashCode()).isNotEqualTo(calc.hashCode());
assertThat(messenger.toString()).isNotEqualTo(calc.toString());
boolean condition = !messenger.toString().equals(calc.toString());
assertThat(condition).isTrue();
assertThat(calc.add(2, 3)).isEqualTo(5);
@@ -139,7 +144,8 @@ class BshScriptFactoryTests {
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger).as("Scripted object should not be instance of Refreshable").isNotInstanceOf(Refreshable.class);
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(messenger2).isNotSameAs(messenger);
assertThat(messenger2.getClass()).isSameAs(messenger.getClass());
@@ -158,7 +164,8 @@ class BshScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messenger");
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
assertThat(messenger).as("Should be an instance of Refreshable").isInstanceOf(Refreshable.class);
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -178,7 +185,8 @@ class BshScriptFactoryTests {
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
assertThat(messenger).as("Should be an instance of Refreshable").isInstanceOf(Refreshable.class);
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(messenger2.getMessage()).isEqualTo("Hello World!");
@@ -198,9 +206,9 @@ class BshScriptFactoryTests {
@Test
void scriptCompilationException() {
assertThatExceptionOfType(NestedRuntimeException.class)
.isThrownBy(() -> new ClassPathXmlApplicationContext("org/springframework/scripting/bsh/bshBrokenContext.xml"))
.matches(ex -> ex.contains(ScriptCompilationException.class));
assertThatExceptionOfType(NestedRuntimeException.class).isThrownBy(() ->
new ClassPathXmlApplicationContext("org/springframework/scripting/bsh/bshBrokenContext.xml"))
.matches(ex -> ex.contains(ScriptCompilationException.class));
}
@Test
@@ -219,17 +227,20 @@ class BshScriptFactoryTests {
@Test
void ctorWithNullScriptSourceLocator() {
assertThatIllegalArgumentException().isThrownBy(() -> new BshScriptFactory(null, Messenger.class));
assertThatIllegalArgumentException().isThrownBy(() ->
new BshScriptFactory(null, Messenger.class));
}
@Test
void ctorWithEmptyScriptSourceLocator() {
assertThatIllegalArgumentException().isThrownBy(() -> new BshScriptFactory("", Messenger.class));
assertThatIllegalArgumentException().isThrownBy(() ->
new BshScriptFactory("", Messenger.class));
}
@Test
void ctorWithWhitespacedScriptSourceLocator() {
assertThatIllegalArgumentException().isThrownBy(() -> new BshScriptFactory("\n ", Messenger.class));
assertThatIllegalArgumentException().isThrownBy(() ->
new BshScriptFactory("\n ", Messenger.class));
}
@Test
@@ -244,7 +255,8 @@ class BshScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messenger");
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(messenger).isNotInstanceOf(Refreshable.class);
boolean condition = messenger instanceof Refreshable;
assertThat(condition).isFalse();
Messenger messengerImpl = (Messenger) ctx.getBean("messengerImpl");
assertThat(messengerImpl.getMessage()).isEqualTo("Hello World!");
@@ -293,7 +305,8 @@ class BshScriptFactoryTests {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bsh-with-xsd.xml", getClass());
Calculator calculator = (Calculator) ctx.getBean("calculator");
assertThat(calculator).isNotNull();
assertThat(calculator).isNotInstanceOf(Refreshable.class);
boolean condition = calculator instanceof Refreshable;
assertThat(condition).isFalse();
ctx.close();
}
@@ -302,7 +315,8 @@ class BshScriptFactoryTests {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bsh-with-xsd.xml", getClass());
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(messenger).as("Messenger should be Refreshable").isInstanceOf(Refreshable.class);
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Messenger should be Refreshable").isTrue();
ctx.close();
}
@@ -78,14 +78,18 @@ public class GroovyScriptFactoryTests {
assertThat(AopUtils.isAopProxy(calc)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(calc instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition3 = calc instanceof Refreshable;
assertThat(condition3).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition2 = messenger instanceof Refreshable;
assertThat(condition2).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(calc).isEqualTo(calc);
assertThat(messenger).isEqualTo(messenger);
assertThat(messenger).isNotEqualTo(calc);
boolean condition1 = !messenger.equals(calc);
assertThat(condition1).isTrue();
assertThat(messenger.hashCode()).isNotEqualTo(calc.hashCode());
assertThat(messenger.toString()).isNotEqualTo(calc.toString());
boolean condition = !messenger.toString().equals(calc.toString());
assertThat(condition).isTrue();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -107,14 +111,18 @@ public class GroovyScriptFactoryTests {
assertThat(AopUtils.isAopProxy(calc)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(calc instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition3 = calc instanceof Refreshable;
assertThat(condition3).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition2 = messenger instanceof Refreshable;
assertThat(condition2).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(calc).isEqualTo(calc);
assertThat(messenger).isEqualTo(messenger);
assertThat(messenger).isNotEqualTo(calc);
boolean condition1 = !messenger.equals(calc);
assertThat(condition1).isTrue();
assertThat(messenger.hashCode()).isNotEqualTo(calc.hashCode());
assertThat(messenger.toString()).isNotEqualTo(calc.toString());
boolean condition = !messenger.toString().equals(calc.toString());
assertThat(condition).isTrue();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -130,7 +138,8 @@ public class GroovyScriptFactoryTests {
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(messenger2).isNotSameAs(messenger);
assertThat(messenger2.getClass()).isSameAs(messenger.getClass());
@@ -149,7 +158,8 @@ public class GroovyScriptFactoryTests {
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
assertThat(messenger2).isNotSameAs(messenger);
assertThat(messenger2.getClass()).isSameAs(messenger.getClass());
@@ -168,7 +178,8 @@ public class GroovyScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messengerInstance");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -182,7 +193,8 @@ public class GroovyScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messengerInstance");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -196,7 +208,8 @@ public class GroovyScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messengerInstanceInline");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -210,7 +223,8 @@ public class GroovyScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messengerInstanceInline");
assertThat(AopUtils.isAopProxy(messenger)).as("Shouldn't get proxy when refresh is disabled").isFalse();
assertThat(messenger instanceof Refreshable).as("Scripted object should not be instance of Refreshable").isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Scripted object should not be instance of Refreshable").isFalse();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -223,7 +237,8 @@ public class GroovyScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("messenger");
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
assertThat(messenger instanceof Refreshable).as("Should be an instance of Refreshable").isTrue();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
String desiredMessage = "Hello World!";
assertThat(messenger.getMessage()).as("Message is incorrect").isEqualTo(desiredMessage);
@@ -242,7 +257,8 @@ public class GroovyScriptFactoryTests {
ConfigurableMessenger messenger2 = (ConfigurableMessenger) ctx.getBean("messengerPrototype");
assertThat(AopUtils.isAopProxy(messenger)).as("Should be a proxy for refreshable scripts").isTrue();
assertThat(messenger instanceof Refreshable).as("Should be an instance of Refreshable").isTrue();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).as("Should be an instance of Refreshable").isTrue();
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(messenger2.getMessage()).isEqualTo("Hello World!");
@@ -355,7 +371,8 @@ public class GroovyScriptFactoryTests {
CallCounter countingAspect = (CallCounter) ctx.getBean("getMessageAspect");
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
assertThat(messenger instanceof Refreshable).isFalse();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).isFalse();
assertThat(countingAspect.getCalls()).isEqualTo(0);
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(countingAspect.getCalls()).isEqualTo(1);
@@ -387,7 +404,8 @@ public class GroovyScriptFactoryTests {
assertThat(ObjectUtils.containsElement(bd.getDependsOn(), "messenger")).isTrue();
Calculator calculator = (Calculator) ctx.getBean("calculator");
assertThat(calculator).isNotNull();
assertThat(calculator instanceof Refreshable).isFalse();
boolean condition = calculator instanceof Refreshable;
assertThat(condition).isFalse();
}
@Test
@@ -399,7 +417,8 @@ public class GroovyScriptFactoryTests {
CallCounter countingAspect = (CallCounter) ctx.getBean("getMessageAspect");
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
assertThat(messenger instanceof Refreshable).isTrue();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).isTrue();
assertThat(countingAspect.getCalls()).isEqualTo(0);
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(countingAspect.getCalls()).isEqualTo(1);
@@ -416,7 +435,8 @@ public class GroovyScriptFactoryTests {
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
assertThat(messenger instanceof Refreshable).isTrue();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).isTrue();
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
assertThat(ctx.getBeansOfType(ConcreteMessenger.class)).containsValue((ConcreteMessenger) messenger);
@@ -467,7 +487,8 @@ public class GroovyScriptFactoryTests {
assertThat(Arrays.asList(ctx.getBeanNamesForType(Messenger.class))).contains("refreshableMessenger");
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
assertThat(AopUtils.isAopProxy(messenger)).isTrue();
assertThat(messenger instanceof Refreshable).isTrue();
boolean condition = messenger instanceof Refreshable;
assertThat(condition).isTrue();
assertThat(messenger.getMessage()).isEqualTo("Hello World!");
}
@@ -521,17 +542,20 @@ public class GroovyScriptFactoryTests {
// expect the exception we threw in the custom metaclass to show it got invoked
ApplicationContext ctx = new ClassPathXmlApplicationContext(xmlFile);
Calculator calc = (Calculator) ctx.getBean("delegatingCalculator");
assertThatIllegalStateException()
.isThrownBy(() -> calc.add(1, 2))
.withMessage("Gotcha");
assertThatIllegalStateException().isThrownBy(() ->
calc.add(1, 2))
.withMessage("Gotcha");
}
@Test
void testFactoryBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("groovyContext.xml", getClass());
Object factory = context.getBean("&factory");
assertThat(factory instanceof FactoryBean).isTrue();
boolean condition1 = factory instanceof FactoryBean;
assertThat(condition1).isTrue();
Object result = context.getBean("factory");
boolean condition = result instanceof String;
assertThat(condition).isTrue();
assertThat(result).isEqualTo("test");
}
@@ -539,8 +563,11 @@ public class GroovyScriptFactoryTests {
void testRefreshableFactoryBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("groovyContext.xml", getClass());
Object factory = context.getBean("&refreshableFactory");
assertThat(factory instanceof FactoryBean).isTrue();
boolean condition1 = factory instanceof FactoryBean;
assertThat(condition1).isTrue();
Object result = context.getBean("refreshableFactory");
boolean condition = result instanceof String;
assertThat(condition).isTrue();
assertThat(result).isEqualTo("test");
}
@@ -43,7 +43,6 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
private final String[] ignoredPackages;
public InvocationsRecorderClassTransformer(String[] instrumentedPackages, String[] ignoredPackages) {
Assert.notNull(instrumentedPackages, "instrumentedPackages must not be null");
Assert.notNull(ignoredPackages, "ignoredPackages must not be null");
@@ -56,7 +55,6 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
.toArray(String[]::new);
}
@Override
public byte[] transform(@Nullable ClassLoader classLoader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
@@ -103,7 +101,7 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
fileReader.accept(classVisitor, 0);
}
catch (Exception ex) {
System.err.println("Failed to transform class: " + ex);
ex.printStackTrace();
return classfileBuffer;
}
if (classVisitor.isTransformed()) {
@@ -111,5 +109,4 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
}
return classfileBuffer;
}
}
@@ -45,6 +45,7 @@ public final class RuntimeHintsAgent {
private static boolean loaded = false;
private RuntimeHintsAgent() {
}
public static void premain(@Nullable String agentArgs, Instrumentation inst) {
@@ -63,7 +64,6 @@ public final class RuntimeHintsAgent {
return loaded;
}
private static final class ParsedArguments {
List<String> instrumentedPackages;
@@ -104,6 +104,6 @@ public final class RuntimeHintsAgent {
}
return new ParsedArguments(included, excluded);
}
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ multiRelease {
}
def javapoetVersion = "1.13.0"
def objenesisVersion = "3.5"
def objenesisVersion = "3.4"
configurations {
java21Api.extendsFrom(api)
@@ -95,7 +95,7 @@ public class Attribute {
* a Code attribute that contains labels.
* @deprecated no longer used by ASM.
*/
@Deprecated(forRemoval = false)
@Deprecated
protected Label[] getLabels() {
return new Label[0];
}
@@ -174,7 +174,6 @@ public class Attribute {
* ClassReader overrides {@link ClassReader#readLabel}. Hence {@link #read(ClassReader, int, int,
* char[], int, Label[])} must not manually create {@link Label} instances.
*
* @param classReader the class that contains the attribute to be read.
* @param bytecodeOffset a bytecode offset in a method.
* @param labels the already created labels, indexed by their offset. If a label already exists
* for bytecodeOffset this method does not create a new one. Otherwise it stores the new label
@@ -100,7 +100,7 @@ public class ClassReader {
* @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will
* eventually be deleted.
*/
@Deprecated(forRemoval = false)
@Deprecated
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
public final byte[] b;
@@ -197,7 +197,7 @@ public class ClassReader {
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
// SPRING PATCH: leniently try to parse newer class files as well
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V27) {
// if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V26) {
// throw new IllegalArgumentException(
// "Unsupported class file major version " + readShort(classFileOffset + 6));
// }
@@ -3550,9 +3550,6 @@ public class ClassReader {
final char[] charBuffer,
final int codeAttributeOffset,
final Label[] labels) {
if (length > classFileBuffer.length - offset) {
throw new IllegalArgumentException();
}
for (Attribute attributePrototype : attributePrototypes) {
if (attributePrototype.type.equals(type)) {
return attributePrototype.read(
@@ -897,7 +897,7 @@ public class ClassWriter extends ClassVisitor {
* @deprecated this method is superseded by {@link #newHandle(int, String, String, String,
* boolean)}.
*/
@Deprecated(forRemoval = false)
@Deprecated
public int newHandle(
final int tag, final String owner, final String name, final String descriptor) {
return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
@@ -71,7 +71,7 @@ public final class Handle {
* @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String,
* boolean)}.
*/
@Deprecated(forRemoval = false)
@Deprecated
public Handle(final int tag, final String owner, final String name, final String descriptor) {
this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
}
@@ -414,7 +414,7 @@ public abstract class MethodVisitor {
* @param descriptor the method's descriptor (see {@link Type}).
* @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
*/
@Deprecated(forRemoval = false)
@Deprecated
public void visitMethodInsn(
final int opcode, final String owner, final String name, final String descriptor) {
int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0);
@@ -291,7 +291,6 @@ public interface Opcodes {
int V24 = 0 << 16 | 68;
int V25 = 0 << 16 | 69;
int V26 = 0 << 16 | 70;
int V27 = 0 << 16 | 71;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -128,16 +128,16 @@ public class ResolvableType implements Serializable {
private Class<?> resolved;
@Nullable
private transient volatile ResolvableType superType;
private volatile ResolvableType superType;
@Nullable
private transient volatile ResolvableType[] interfaces;
private volatile ResolvableType[] interfaces;
@Nullable
private transient volatile ResolvableType[] generics;
private volatile ResolvableType[] generics;
@Nullable
private transient volatile Boolean unresolvableGenerics;
private volatile Boolean unresolvableGenerics;
/**
@@ -53,10 +53,8 @@ final class AnnotationTypeMapping {
private static final Log logger = LogFactory.getLog(AnnotationTypeMapping.class);
private static final Predicate<? super Annotation> isBeanValidationConstraint = annotation -> {
String name = annotation.annotationType().getName();
return (name.equals("jakarta.validation.Constraint") || name.equals("javax.validation.Constraint"));
};
private static final Predicate<? super Annotation> isBeanValidationConstraint = annotation ->
annotation.annotationType().getName().equals("jakarta.validation.Constraint");
/**
* Set used to track which convention-based annotation attribute overrides
@@ -168,7 +168,6 @@ public interface Resource extends InputStreamSource {
/**
* Determine the content length for this resource.
* @return the content length (or -1 if undetermined)
* @throws IOException if the resource cannot be resolved
* (in the file system or as some other known physical resource type)
*/
@@ -176,7 +175,6 @@ public interface Resource extends InputStreamSource {
/**
* Determine the last-modified timestamp for this resource.
* @return the last-modified timestamp (or 0 if not known)
* @throws IOException if the resource cannot be resolved
* (in the file system or as some other known physical resource type)
*/
@@ -1205,13 +1205,7 @@ public abstract class DataBufferUtils {
long pos = this.position.get();
Attachment attachment = new Attachment(byteBuffer, dataBuffer, iterator);
this.writing.set(true);
try {
this.channel.write(byteBuffer, pos, attachment, this);
}
catch (RuntimeException ex) {
// If the exception escapes, route it to the failure handler
failed(ex, attachment);
}
this.channel.write(byteBuffer, pos, attachment, this);
}
}
@@ -25,10 +25,14 @@ import org.springframework.util.concurrent.FutureUtils;
/**
* Extended interface for asynchronous {@link TaskExecutor} implementations,
* offering support for {@link Future}/{@link CompletableFuture} handles.
* offering support for {@link java.util.concurrent.Callable}.
*
* <p>Note: The {@link java.util.concurrent.Executors} class includes a set of
* methods that can convert some other common closure-like objects, for example,
* {@link java.security.PrivilegedAction} to {@link Callable} before executing them.
*
* <p>Implementing this interface also indicates that the {@link #execute(Runnable)}
* method will not execute the given Runnable in the caller's thread but rather
* method will not execute its Runnable in the caller's thread but rather
* asynchronously in some other thread.
*
* @author Juergen Hoeller
@@ -121,7 +125,8 @@ public interface AsyncTaskExecutor extends TaskExecutor {
/**
* Submit a {@code Callable} task for execution, receiving a {@code CompletableFuture}
* representing that task. The Future will return the Callable's result upon completion.
* representing that task. The Future will return the Callable's result upon
* completion.
* @param task the {@code Callable} to execute (never {@code null})
* @return a {@code CompletableFuture} representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
@@ -19,12 +19,10 @@ package org.springframework.core.task;
import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -34,13 +32,13 @@ import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
/**
* {@link TaskExecutor} implementation that fires up a new Thread for each task.
* Provides a {@link #setVirtualThreads virtual threads} option on JDK 21+.
* {@link TaskExecutor} implementation that fires up a new Thread for each task,
* executing it asynchronously. Provides a virtual thread option on JDK 21.
*
* <p>Supports a graceful shutdown through {@link #setTaskTerminationTimeout},
* at the expense of task tracking overhead per execution thread at runtime.
* Supports limiting concurrent threads through {@link #setConcurrencyLimit};
* by default, the number of concurrent task executions is unlimited.
* Supports limiting concurrent threads through {@link #setConcurrencyLimit}.
* By default, the number of concurrent task executions is unlimited.
*
* <p><b>NOTE: This implementation does not reuse threads!</b> Consider a
* thread-pooling TaskExecutor implementation instead, in particular for
@@ -98,9 +96,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
private boolean rejectTasksWhenLimitReached = false;
private final AtomicBoolean closed = new AtomicBoolean();
private boolean cancelled = false; // within activeThreads synchronization
private volatile boolean active = true;
/**
@@ -221,13 +217,10 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
/**
* Specify whether to reject tasks when the concurrency limit has been reached,
* throwing {@link TaskRejectedException} (which extends the common
* {@link java.util.concurrent.RejectedExecutionException})
* on any further execution attempts.
* throwing {@link TaskRejectedException} on any further submission attempts.
* <p>The default is {@code false}, blocking the caller until the submission can
* be accepted. Switch this to {@code true} for immediate rejection instead.
* @since 6.2.6
* @see #setConcurrencyLimit
*/
public void setRejectTasksWhenLimitReached(boolean rejectTasksWhenLimitReached) {
this.rejectTasksWhenLimitReached = rejectTasksWhenLimitReached;
@@ -276,7 +269,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
* @see #close()
*/
public boolean isActive() {
return !this.closed.get();
return this.active;
}
/**
@@ -309,20 +302,19 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
* @see #TIMEOUT_IMMEDIATE
* @see #doExecute(Runnable)
*/
@Deprecated(since = "5.3.16")
@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
Assert.notNull(task, "Runnable must not be null");
if (!isActive()) {
throw new TaskRejectedException(getClass().getSimpleName() + " is not active");
throw new TaskRejectedException(getClass().getSimpleName() + " has been closed already");
}
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
Future<?> future = (task instanceof Future<?> f ? f : null);
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
this.concurrencyThrottle.beforeAccess();
try {
doExecute(new TaskTrackingRunnable(taskToUse, future));
doExecute(new TaskTrackingRunnable(taskToUse));
}
catch (Throwable ex) {
// Release concurrency permit if thread creation fails
@@ -332,7 +324,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
}
}
else if (this.activeThreads != null) {
doExecute(new TaskTrackingRunnable(taskToUse, future));
doExecute(new TaskTrackingRunnable(taskToUse));
}
else {
doExecute(taskToUse);
@@ -408,13 +400,11 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
*/
@Override
public void close() {
if (this.closed.compareAndSet(false, true)) {
if (this.active) {
this.active = false;
Set<Thread> threads = this.activeThreads;
if (threads != null) {
if (this.cancelRemainingTasksOnClose) {
synchronized (threads) {
this.cancelled = true;
}
// Early interrupt for remaining tasks on close
threads.forEach(Thread::interrupt);
}
@@ -428,7 +418,6 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
this.cancelled = true;
}
if (!this.cancelRemainingTasksOnClose) {
// Late interrupt for remaining tasks after timeout
@@ -439,15 +428,6 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
}
}
private void checkCancelled(@Nullable Future<?> future) {
if (this.cancelled) { // within synchronization from TaskTrackingRunnable
if (future != null) {
future.cancel(false);
}
throw new CancellationException(getClass().getSimpleName() + " has cancelled all remaining tasks");
}
}
/**
* Subclass of the general ConcurrencyThrottleSupport class,
@@ -484,12 +464,9 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
private final Runnable task;
private final @Nullable Future<?> future;
public TaskTrackingRunnable(Runnable task, @Nullable Future<?> future) {
public TaskTrackingRunnable(Runnable task) {
Assert.notNull(task, "Task must not be null");
this.task = task;
this.future = future;
}
@Override
@@ -498,10 +475,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
Thread thread = null;
if (threads != null) {
thread = Thread.currentThread();
synchronized (threads) {
checkCancelled(this.future);
threads.add(thread);
}
threads.add(thread);
}
try {
this.task.run();
@@ -509,7 +483,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
finally {
if (threads != null) {
threads.remove(thread);
if (closed.get()) {
if (!isActive()) {
synchronized (threads) {
if (threads.isEmpty()) {
threads.notify();
@@ -120,7 +120,6 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
return new MergedAnnotationReadingVisitor<>(this.classLoader, this.source, type, consumer);
}
@SuppressWarnings("unchecked")
@Nullable
static <A extends Annotation> AnnotationVisitor get(@Nullable ClassLoader classLoader,
@@ -1,6 +1,6 @@
/**
* Spring's repackaging of
* <a href="http://objenesis.org">Objenesis 3.5</a>
* <a href="http://objenesis.org">Objenesis 3.4</a>
* (with SpringObjenesis entry point; for internal use only).
*
* <p>This repackaging technique avoids any potential conflicts with
@@ -386,13 +386,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V computeIfAbsent(@Nullable K key, Function<? super K, ? extends V> mappingFunction) {
// Avoid locking if entry is present
Reference<K, V> ref = getReference(key, Restructure.NEVER);
Entry<K, V> entry = (ref != null ? ref.get() : null);
if (entry != null) {
return entry.getValue();
}
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
@@ -413,13 +406,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
public V computeIfPresent(@Nullable K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
// Avoid locking if entry is absent
Reference<K, V> ref = getReference(key, Restructure.NEVER);
Entry<K, V> entry = (ref != null ? ref.get() : null);
if (entry == null) {
return null;
}
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
@Override
protected @Nullable V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
@@ -16,6 +16,8 @@
package org.springframework.util;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -28,11 +30,9 @@ import java.nio.file.Files;
import org.springframework.lang.Nullable;
/**
* Simple utility methods for file and stream copying.
*
* <p>All copy methods use a block size of {@value #BUFFER_SIZE} bytes and
* close all affected streams when done. A variation of the copy methods from
* this class that leave streams open can be found in {@link StreamUtils}.
* Simple utility methods for file and stream copying. All copy methods use a block size
* of 4096 bytes, and close all affected streams when done. A variation of the copy
* methods from this class that leave streams open can be found in {@link StreamUtils}.
*
* <p>Mainly for use within the framework, but also useful for application code.
*
@@ -76,7 +76,7 @@ public abstract class FileCopyUtils {
public static void copy(byte[] in, File out) throws IOException {
Assert.notNull(in, "No input byte array specified");
Assert.notNull(out, "No output File specified");
Files.write(out.toPath(), in);
copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath()));
}
/**
@@ -87,7 +87,7 @@ public abstract class FileCopyUtils {
*/
public static byte[] copyToByteArray(File in) throws IOException {
Assert.notNull(in, "No input File specified");
return Files.readAllBytes(in.toPath());
return copyToByteArray(Files.newInputStream(in.toPath()));
}
@@ -125,9 +125,12 @@ public abstract class FileCopyUtils {
Assert.notNull(in, "No input byte array specified");
Assert.notNull(out, "No OutputStream specified");
try (out) {
try {
out.write(in);
}
finally {
close(out);
}
}
/**
@@ -164,11 +167,21 @@ public abstract class FileCopyUtils {
Assert.notNull(in, "No Reader specified");
Assert.notNull(out, "No Writer specified");
try (in; out) {
int charCount = (int) in.transferTo(out);
try {
int charCount = 0;
char[] buffer = new char[BUFFER_SIZE];
int charsRead;
while ((charsRead = in.read(buffer)) != -1) {
out.write(buffer, 0, charsRead);
charCount += charsRead;
}
out.flush();
return charCount;
}
finally {
close(in);
close(out);
}
}
/**
@@ -182,9 +195,12 @@ public abstract class FileCopyUtils {
Assert.notNull(in, "No input String specified");
Assert.notNull(out, "No Writer specified");
try (out) {
try {
out.write(in);
}
finally {
close(out);
}
}
/**
@@ -204,4 +220,18 @@ public abstract class FileCopyUtils {
return out.toString();
}
/**
* Attempt to close the supplied {@link Closeable}, silently swallowing any
* exceptions.
* @param closeable the {@code Closeable} to close
*/
private static void close(Closeable closeable) {
try {
closeable.close();
}
catch (IOException ex) {
// ignore
}
}
}
@@ -142,6 +142,31 @@ public class MimeType implements Comparable<MimeType>, Serializable {
*/
public MimeType(String type, String subtype, Charset charset) {
this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charset.name()));
this.resolvedCharset = charset;
}
/**
* Copy-constructor that copies the type, subtype, parameters of the given {@code MimeType},
* and allows to set the specified character set.
* @param other the other MimeType
* @param charset the character set
* @throws IllegalArgumentException if any of the parameters contains illegal characters
* @since 4.3
*/
public MimeType(MimeType other, Charset charset) {
this(other.getType(), other.getSubtype(), addCharsetParameter(charset, other.getParameters()));
this.resolvedCharset = charset;
}
/**
* Copy-constructor that copies the type and subtype of the given {@code MimeType},
* and allows for different parameter.
* @param other the other MimeType
* @param parameters the parameters (may be {@code null})
* @throws IllegalArgumentException if any of the parameters contains illegal characters
*/
public MimeType(MimeType other, @Nullable Map<String, String> parameters) {
this(other.getType(), other.getSubtype(), parameters);
}
/**
@@ -158,43 +183,16 @@ public class MimeType implements Comparable<MimeType>, Serializable {
checkToken(subtype);
this.type = type.toLowerCase(Locale.ROOT);
this.subtype = subtype.toLowerCase(Locale.ROOT);
this.parameters = createParametersMap(parameters);
if (this.parameters.containsKey(PARAM_CHARSET)) {
this.resolvedCharset = Charset.forName(unquote(this.parameters.get(PARAM_CHARSET)));
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ROOT);
parameters.forEach((parameter, value) -> {
checkParameters(parameter, value);
map.put(parameter, value);
});
this.parameters = Collections.unmodifiableMap(map);
}
}
/**
* Copy-constructor that copies the type, subtype, parameters of the given {@code MimeType},
* and allows to set the specified character set.
* @param other the other MimeType
* @param charset the character set
* @throws IllegalArgumentException if any of the parameters contains illegal characters
* @since 4.3
*/
public MimeType(MimeType other, Charset charset) {
this.type = other.type;
this.subtype = other.subtype;
Map<String, String> map = new LinkedCaseInsensitiveMap<>(other.parameters.size() + 1, Locale.ROOT);
map.putAll(other.parameters);
map.put(PARAM_CHARSET, charset.name());
this.parameters = Collections.unmodifiableMap(map);
this.resolvedCharset = charset;
}
/**
* Copy-constructor that copies the type and subtype of the given {@code MimeType},
* and allows for different parameter.
* @param other the other MimeType
* @param parameters the parameters (may be {@code null})
* @throws IllegalArgumentException if any of the parameters contains illegal characters
*/
public MimeType(MimeType other, @Nullable Map<String, String> parameters) {
this.type = other.type;
this.subtype = other.subtype;
this.parameters = createParametersMap(parameters);
if (this.parameters.containsKey(PARAM_CHARSET)) {
this.resolvedCharset = Charset.forName(unquote(this.parameters.get(PARAM_CHARSET)));
else {
this.parameters = Collections.emptyMap();
}
}
@@ -227,25 +225,16 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
}
private Map<String, String> createParametersMap(@Nullable Map<String, String> parameters) {
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ROOT);
parameters.forEach((parameter, value) -> {
checkParameters(parameter, value);
map.put(parameter, value);
});
return Collections.unmodifiableMap(map);
}
else {
return Collections.emptyMap();
}
}
protected void checkParameters(String parameter, String value) {
Assert.hasLength(parameter, "'parameter' must not be empty");
Assert.hasLength(value, "'value' must not be empty");
checkToken(parameter);
if (!isQuotedString(value)) {
if (PARAM_CHARSET.equals(parameter)) {
if (this.resolvedCharset == null) {
this.resolvedCharset = Charset.forName(unquote(value));
}
}
else if (!isQuotedString(value)) {
checkToken(value);
}
}
@@ -237,8 +237,7 @@ class MethodParameterTests {
assertThat(m3.getTypeIndexForCurrentLevel()).isEqualTo(3);
}
public int method(String str, long lng) {
public int method(String p1, long p2) {
return 42;
}
@@ -1335,22 +1335,6 @@ class ResolvableTypeTests {
assertThat(deserializedNone).isSameAs(ResolvableType.NONE);
}
@Test
void serializeWithCachedState() throws Exception {
ResolvableType type = ResolvableType.forClass(List.class);
testSerialization(type);
type.getSuperType();
type.getInterfaces();
type.getGenerics();
type.hasUnresolvableGenerics();
testSerialization(type);
type.getSuperType();
type.getInterfaces();
type.getGenerics();
type.hasUnresolvableGenerics();
testSerialization(type);
}
@Test
void canResolveVoid() {
ResolvableType type = ResolvableType.forClass(void.class);
@@ -41,13 +41,11 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Test cases for {@link ResourceRegionEncoder} class.
*
* @author Brian Clozel
*/
class ResourceRegionEncoderTests extends AbstractLeakCheckingTests {
private final ResourceRegionEncoder encoder = new ResourceRegionEncoder();
private ResourceRegionEncoder encoder = new ResourceRegionEncoder();
@Test
void canEncode() {
@@ -118,7 +116,7 @@ class ResourceRegionEncoderTests extends AbstractLeakCheckingTests {
.verify();
}
@Test // gh-22107
@Test // gh-22107
void cancelWithoutDemandForMultipleResourceRegions() {
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
Flux<ResourceRegion> regions = Flux.just(
@@ -140,7 +138,7 @@ class ResourceRegionEncoderTests extends AbstractLeakCheckingTests {
subscriber.cancel();
}
@Test // gh-22107
@Test // gh-22107
void cancelWithoutDemandForSingleResourceRegion() {
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
Mono<ResourceRegion> regions = Mono.just(new ResourceRegion(resource, 0, 6));
@@ -16,11 +16,8 @@
package org.springframework.core.task;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
@@ -30,7 +27,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
@@ -90,11 +86,11 @@ class SimpleAsyncTaskExecutorTests {
* <p>This test reproduces a critical bug where OutOfMemoryError from
* Thread.start() causes the executor to permanently deadlock:
* <ol>
* <li>beforeAccess() increments concurrencyCount
* <li>doExecute() throws Error before thread starts
* <li>TaskTrackingRunnable.run() never executes
* <li>afterAccess() in finally block never called
* <li>Subsequent tasks block forever in onLimitReached()
* <li>beforeAccess() increments concurrencyCount
* <li>doExecute() throws Error before thread starts
* <li>TaskTrackingRunnable.run() never executes
* <li>afterAccess() in finally block never called
* <li>Subsequent tasks block forever in onLimitReached()
* </ol>
*
* <p>Test approach: The first execute() should fail with some exception
@@ -135,105 +131,6 @@ class SimpleAsyncTaskExecutorTests {
.isTrue();
}
@Test
void taskTerminationTimeout() throws InterruptedException{
Future<?> future;
try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) {
executor.setTaskTerminationTimeout(500);
future = executor.submit(() -> {
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalStateException();
}
});
Thread.sleep(100);
}
assertThatNoException().isThrownBy(future::get);
}
@Test
void taskTerminationTimeoutWithImmediateCancel() {
AtomicBoolean finished = new AtomicBoolean();
Future<?> future;
try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) {
executor.setTaskTerminationTimeout(100);
future = executor.submit(() -> {
if (finished.get()) {
throw new IllegalStateException();
}
});
}
finished.set(true);
assertThatExceptionOfType(CancellationException.class).isThrownBy(future::get);
}
@Test
void taskTerminationTimeoutWithLateInterrupt() throws InterruptedException {
AtomicBoolean interrupted = new AtomicBoolean();
Future<?> future;
try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) {
executor.setTaskTerminationTimeout(200);
future = executor.submit(() -> {
try {
Thread.sleep(500);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
interrupted.set(true);
}
});
Thread.sleep(100);
}
assertThatNoException().isThrownBy(future::get);
assertThat(interrupted).isTrue();
}
@Test
void taskTerminationTimeoutWithEarlyInterrupt() throws InterruptedException {
AtomicBoolean interrupted = new AtomicBoolean();
Future<?> future;
try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) {
executor.setTaskTerminationTimeout(500);
executor.setCancelRemainingTasksOnClose(true);
future = executor.submit(() -> {
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
interrupted.set(true);
}
});
Thread.sleep(100);
}
assertThatNoException().isThrownBy(future::get);
assertThat(interrupted).isTrue();
}
@Test
void cancelRemainingTasksOnClose() throws InterruptedException {
AtomicBoolean interrupted = new AtomicBoolean();
Future<?> future;
try (SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor()) {
executor.setCancelRemainingTasksOnClose(true);
future = executor.submit(() -> {
try {
Thread.sleep(200);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
interrupted.set(true);
}
});
Thread.sleep(100);
}
assertThatNoException().isThrownBy(future::get);
assertThat(interrupted).isTrue();
}
@Test
void threadNameGetsSetCorrectly() {
String customPrefix = "chankPop#";
@@ -362,12 +362,12 @@ public abstract class NamedParameterUtils {
for (int i = 0; i < paramNames.size(); i++) {
String paramName = paramNames.get(i);
try {
SqlParameter param = findParameter(declaredParams, paramName, i);
Object paramValue = paramSource.getValue(paramName);
if (paramValue instanceof SqlParameterValue) {
paramArray[i] = paramValue;
}
else {
SqlParameter param = findParameter(declaredParams, paramName, i);
paramArray[i] = (param != null ? new SqlParameterValue(param, paramValue) :
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
}
@@ -78,7 +78,7 @@ final class DefaultJdbcClient implements JdbcClient {
}
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate) {
Assert.notNull(jdbcTemplate, "NamedParameterJdbcTemplate must not be null");
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
this.classicOps = jdbcTemplate.getJdbcOperations();
this.namedParamOps = jdbcTemplate;
}
@@ -29,7 +29,7 @@ import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.lang.Nullable;
/**
* Object to represent a binary parameter value for an SQL statement, for example,
* Object to represent a binary parameter value for a SQL statement, for example,
* a binary stream for a BLOB or a LONGVARBINARY or PostgreSQL BYTEA column.
*
* <p>Designed for use with {@link org.springframework.jdbc.core.JdbcTemplate}
@@ -37,7 +37,7 @@ import org.springframework.lang.Nullable;
* passed in as a parameter value wrapping the target content value.
*
* <p>Can be combined with {@link org.springframework.jdbc.core.SqlParameterValue}
* for specifying an SQL type, for example,
* for specifying a SQL type, for example,
* {@code new SqlParameterValue(Types.BLOB, new SqlBinaryValue(myContent))}.
* With most database drivers, the type hint is not actually necessary.
*
@@ -70,7 +70,7 @@ public class SqlBinaryValue implements SqlTypeValue {
/**
* Create a new {@code SqlBinaryValue} for the given content.
* @param stream the content stream
* @param length the length of the content (or -1 if undetermined)
* @param length the length of the content
*/
public SqlBinaryValue(InputStream stream, long length) {
this.content = stream;
@@ -82,7 +82,7 @@ public class SqlBinaryValue implements SqlTypeValue {
* <p>Consider specifying a {@link Resource} with content length support
* when available: {@link SqlBinaryValue#SqlBinaryValue(Resource)}.
* @param resource the resource to obtain a content stream from
* @param length the length of the content (or -1 if undetermined)
* @param length the length of the content
*/
public SqlBinaryValue(InputStreamSource resource, long length) {
this.content = resource;
@@ -146,20 +146,10 @@ public class SqlBinaryValue implements SqlTypeValue {
throws SQLException {
if (sqlType == Types.BLOB) {
if (length >= 0) {
ps.setBlob(paramIndex, is, length);
}
else {
ps.setBlob(paramIndex, is);
}
ps.setBlob(paramIndex, is, length);
}
else {
if (length >= 0) {
ps.setBinaryStream(paramIndex, is, length);
}
else {
ps.setBinaryStream(paramIndex, is);
}
ps.setBinaryStream(paramIndex, is, length);
}
}
@@ -28,7 +28,7 @@ import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.lang.Nullable;
/**
* Object to represent a character-based parameter value for an SQL statement,
* Object to represent a character-based parameter value for a SQL statement,
* for example, a character stream for a CLOB/NCLOB or a LONGVARCHAR column.
*
* <p>Designed for use with {@link org.springframework.jdbc.core.JdbcTemplate}
@@ -36,7 +36,7 @@ import org.springframework.lang.Nullable;
* passed in as a parameter value wrapping the target content value.
*
* <p>Can be combined with {@link org.springframework.jdbc.core.SqlParameterValue}
* for specifying an SQL type, for example,
* for specifying a SQL type, for example,
* {@code new SqlParameterValue(Types.CLOB, new SqlCharacterValue(myContent))}.
* With most database drivers, the type hint is not actually necessary.
*
@@ -77,7 +77,7 @@ public class SqlCharacterValue implements SqlTypeValue {
/**
* Create a new {@code SqlCharacterValue} for the given content.
* @param reader the content reader
* @param length the length of the content (or -1 if undetermined)
* @param length the length of the content
*/
public SqlCharacterValue(Reader reader, long length) {
this.content = reader;
@@ -87,7 +87,7 @@ public class SqlCharacterValue implements SqlTypeValue {
/**
* Create a new {@code SqlCharacterValue} for the given content.
* @param asciiStream the content as ASCII stream
* @param length the length of the content (or -1 if undetermined)
* @param length the length of the content
*/
public SqlCharacterValue(InputStream asciiStream, long length) {
this.content = asciiStream;
@@ -108,8 +108,8 @@ public class SqlCharacterValue implements SqlTypeValue {
else if (this.content instanceof Reader reader) {
setReader(ps, paramIndex, sqlType, reader, this.length);
}
else if (this.content instanceof InputStream inputStream) {
setInputStream(ps, paramIndex, inputStream, this.length);
else if (this.content instanceof InputStream asciiStream) {
ps.setAsciiStream(paramIndex, asciiStream, this.length);
}
else {
throw new IllegalArgumentException("Illegal content type: " + this.content.getClass().getName());
@@ -134,39 +134,13 @@ public class SqlCharacterValue implements SqlTypeValue {
throws SQLException {
if (sqlType == Types.CLOB) {
if (length >= 0) {
ps.setClob(paramIndex, reader, length);
}
else {
ps.setClob(paramIndex, reader);
}
ps.setClob(paramIndex, reader, length);
}
else if (sqlType == Types.NCLOB) {
if (length >= 0) {
ps.setNClob(paramIndex, reader, length);
}
else {
ps.setNClob(paramIndex, reader);
}
ps.setNClob(paramIndex, reader, length);
}
else {
if (length >= 0) {
ps.setCharacterStream(paramIndex, reader, length);
}
else {
ps.setCharacterStream(paramIndex, reader);
}
}
}
private void setInputStream(PreparedStatement ps, int paramIndex, InputStream is, long length)
throws SQLException {
if (length >= 0) {
ps.setAsciiStream(paramIndex, is, length);
}
else {
ps.setAsciiStream(paramIndex, is);
ps.setCharacterStream(paramIndex, reader, length);
}
}
@@ -65,15 +65,6 @@ class SqlBinaryValueTests {
verify(ps).setBinaryStream(1, content, 3L);
}
@Test
void withInputStreamUndeterminedLength() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
SqlBinaryValue value = new SqlBinaryValue(content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
verify(ps).setBinaryStream(1, content);
}
@Test
void withInputStreamForBlob() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
@@ -83,15 +74,6 @@ class SqlBinaryValueTests {
verify(ps).setBlob(1, content, 3L);
}
@Test
void withInputStreamForBlobUndeterminedLength() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
SqlBinaryValue value = new SqlBinaryValue(content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, Types.BLOB, null);
verify(ps).setBlob(1, content);
}
@Test
void withInputStreamSource() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
@@ -101,15 +83,6 @@ class SqlBinaryValueTests {
verify(ps).setBinaryStream(1, content, 3L);
}
@Test
void withInputStreamSourceUndeterminedLength() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
SqlBinaryValue value = new SqlBinaryValue(() -> content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
verify(ps).setBinaryStream(1, content);
}
@Test
void withInputStreamSourceForBlob() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
@@ -119,15 +92,6 @@ class SqlBinaryValueTests {
verify(ps).setBlob(1, content, 3L);
}
@Test
void withInputStreamSourceForBlobUndeterminedLength() throws SQLException {
InputStream content = new ByteArrayInputStream(new byte[] {0, 1, 2});
SqlBinaryValue value = new SqlBinaryValue(() -> content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, Types.BLOB, null);
verify(ps).setBlob(1, content);
}
@Test
void withResource() throws SQLException {
byte[] content = new byte[] {0, 1, 2};
@@ -104,15 +104,6 @@ class SqlCharacterValueTests {
verify(ps).setCharacterStream(1, content, 3L);
}
@Test
void withReaderUndeterminedLength() throws SQLException {
Reader content = new StringReader("abc");
SqlCharacterValue value = new SqlCharacterValue(content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
verify(ps).setCharacterStream(1, content);
}
@Test
void withReaderForClob() throws SQLException {
Reader content = new StringReader("abc");
@@ -122,15 +113,6 @@ class SqlCharacterValueTests {
verify(ps).setClob(1, content, 3L);
}
@Test
void withReaderForClobUndeterminedLength() throws SQLException {
Reader content = new StringReader("abc");
SqlCharacterValue value = new SqlCharacterValue(content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, Types.CLOB, null);
verify(ps).setClob(1, content);
}
@Test
void withReaderForNClob() throws SQLException {
Reader content = new StringReader("abc");
@@ -140,15 +122,6 @@ class SqlCharacterValueTests {
verify(ps).setNClob(1, content, 3L);
}
@Test
void withReaderForNClobUndeterminedLength() throws SQLException {
Reader content = new StringReader("abc");
SqlCharacterValue value = new SqlCharacterValue(content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, Types.NCLOB, null);
verify(ps).setNClob(1, content);
}
@Test
void withAsciiStream() throws SQLException {
InputStream content = new ByteArrayInputStream("abc".getBytes(StandardCharsets.US_ASCII));
@@ -158,13 +131,4 @@ class SqlCharacterValueTests {
verify(ps).setAsciiStream(1, content, 3L);
}
@Test
void withAsciiStreamUndeterminedLength() throws SQLException {
InputStream content = new ByteArrayInputStream("abc".getBytes(StandardCharsets.US_ASCII));
SqlCharacterValue value = new SqlCharacterValue(content, -1);
PreparedStatement ps = mock();
value.setTypeValue(ps, 1, JdbcUtils.TYPE_UNKNOWN, null);
verify(ps).setAsciiStream(1, content);
}
}
@@ -26,15 +26,15 @@ import org.springframework.lang.Nullable;
/**
* Specifies a basic set of JMS operations.
*
* <p>Implemented by {@link JmsTemplate}. Not often used directly, but a useful
* option to enhance testability, as it can easily be mocked or stubbed.
* <p>Implemented by {@link JmsTemplate}. Not often used but a useful option
* to enhance testability, as it can easily be mocked or stubbed.
*
* <p>Provides {@code JmsTemplate's} {@code send(..)} and
* {@code receive(..)} methods that mirror various JMS API methods.
* See the JMS specification and javadocs for details on those methods.
*
* <p>Provides also basic request-reply operations using a temporary queue to
* receive the reply.
* <p>Provides also basic request reply operation using a temporary
* queue to collect the reply.
*
* @author Mark Pollack
* @author Juergen Hoeller
@@ -59,7 +59,7 @@ public interface JmsOperations {
/**
* Send messages to the default JMS destination (or one specified
* for each send operation). The callback provides access to the JMS Session
* for each send operation). The callback gives access to the JMS Session
* and MessageProducer in order to perform complex send operations.
* @param action callback object that exposes the session/producer pair
* @return the result object from working with the session
@@ -69,7 +69,7 @@ public interface JmsOperations {
<T> T execute(ProducerCallback<T> action) throws JmsException;
/**
* Send messages to a JMS destination. The callback provides access to the JMS Session
* Send messages to a JMS destination. The callback gives access to the JMS Session
* and MessageProducer in order to perform complex send operations.
* @param destination the destination to send messages to
* @param action callback object that exposes the session/producer pair
@@ -80,7 +80,7 @@ public interface JmsOperations {
<T> T execute(Destination destination, ProducerCallback<T> action) throws JmsException;
/**
* Send messages to a JMS destination. The callback provides access to the JMS Session
* Send messages to a JMS destination. The callback gives access to the JMS Session
* and MessageProducer in order to perform complex send operations.
* @param destinationName the name of the destination to send messages to
* (to be resolved to an actual destination by a DestinationResolver)
@@ -377,7 +377,7 @@ public interface JmsOperations {
/**
* Send a request message and receive the reply from a default destination. The
* {@link MessageCreator} callback creates the message given a Session. A temporary
* queue is created as part of this operation and is set in the {@code JMSReplyTo}
* queue is created as part of this operation and is set in the {@code JMSReplyTO}
* header of the message.
* <p>This will only work with a default destination specified!
* @param messageCreator callback to create a request message
@@ -390,10 +390,10 @@ public interface JmsOperations {
Message sendAndReceive(MessageCreator messageCreator) throws JmsException;
/**
* Send a message and receive the reply from the specified destination.
* <p>The {@link MessageCreator} callback creates the message given a Session.
* A temporary queue is created as part of this operation and is set in the
* {@code JMSReplyTo} header of the message.
* Send a message and receive the reply from the specified destination. The
* {@link MessageCreator} callback creates the message given a Session. A temporary
* queue is created as part of this operation and is set in the {@code JMSReplyTO}
* header of the message.
* @param destination the destination to send this message to
* @param messageCreator callback to create a message
* @return the reply, possibly {@code null} if the message could not be received,
@@ -405,10 +405,10 @@ public interface JmsOperations {
Message sendAndReceive(Destination destination, MessageCreator messageCreator) throws JmsException;
/**
* Send a message and receive the reply from the specified destination.
* <p>The {@link MessageCreator} callback creates the message given a Session.
* A temporary queue is created as part of this operation and is set in the
* {@code JMSReplyTo} header of the message.
* Send a message and receive the reply from the specified destination. The
* {@link MessageCreator} callback creates the message given a Session. A temporary
* queue is created as part of this operation and is set in the {@code JMSReplyTO}
* header of the message.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageCreator callback to create a message
@@ -426,7 +426,7 @@ public interface JmsOperations {
//---------------------------------------------------------------------------------------
/**
* Browse messages in the default JMS queue. The callback provides access to the JMS
* Browse messages in the default JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
@@ -436,7 +436,7 @@ public interface JmsOperations {
<T> T browse(BrowserCallback<T> action) throws JmsException;
/**
* Browse messages in a JMS queue. The callback provides access to the JMS Session
* Browse messages in a JMS queue. The callback gives access to the JMS Session
* and QueueBrowser in order to browse the queue and react to the contents.
* @param queue the queue to browse
* @param action callback object that exposes the session/browser pair
@@ -447,7 +447,7 @@ public interface JmsOperations {
<T> T browse(Queue queue, BrowserCallback<T> action) throws JmsException;
/**
* Browse messages in a JMS queue. The callback provides access to the JMS Session
* Browse messages in a JMS queue. The callback gives access to the JMS Session
* and QueueBrowser in order to browse the queue and react to the contents.
* @param queueName the name of the queue to browse
* (to be resolved to an actual destination by a DestinationResolver)
@@ -459,7 +459,7 @@ public interface JmsOperations {
<T> T browse(String queueName, BrowserCallback<T> action) throws JmsException;
/**
* Browse selected messages in a JMS queue. The callback provides access to the JMS
* Browse selected messages in a JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
@@ -471,7 +471,7 @@ public interface JmsOperations {
<T> T browseSelected(@Nullable String messageSelector, BrowserCallback<T> action) throws JmsException;
/**
* Browse selected messages in a JMS queue. The callback provides access to the JMS
* Browse selected messages in a JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param queue the queue to browse
* @param messageSelector the JMS message selector expression (or {@code null} if none).
@@ -484,7 +484,7 @@ public interface JmsOperations {
<T> T browseSelected(Queue queue, @Nullable String messageSelector, BrowserCallback<T> action) throws JmsException;
/**
* Browse selected messages in a JMS queue. The callback provides access to the JMS
* Browse selected messages in a JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param queueName the name of the queue to browse
* (to be resolved to an actual destination by a DestinationResolver)
@@ -197,7 +197,9 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
/**
* Initialize this container.
* <p>Marks the container as active and calls {@link #doInitialize()}.
* <p>Creates a JMS Connection, starts the {@link jakarta.jms.Connection}
* (if {@link #setAutoStartup(boolean) "autoStartup"} hasn't been turned off),
* and calls {@link #doInitialize()}.
* @throws org.springframework.jms.JmsException if startup failed
*/
public void initialize() throws JmsException {
@@ -487,7 +489,7 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
/**
* Stop the shared Connection.
* @throws JMSException if thrown by JMS API methods
* @see jakarta.jms.Connection#stop()
* @see jakarta.jms.Connection#start()
*/
protected void stopSharedConnection() throws JMSException {
this.sharedConnectionLock.lock();
@@ -1026,14 +1026,13 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
private static class MessageRejectedWhileStoppingException extends RuntimeException {
}
private abstract static class MicrometerInstrumentation {
static Session instrumentSession(Session session, ObservationRegistry registry) {
return JmsInstrumentation.instrumentSession(session, registry);
}
}
}
private abstract static class ObservationFactory {
@@ -280,8 +280,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
* between recovery attempts. If the {@link BackOffExecution} implementation
* returns {@link BackOffExecution#STOP}, this listener container will not further
* attempt to recover.
* <p>Note that setting the {@linkplain #setRecoveryInterval(long) recovery
* interval} overrides this property.
* <p>The {@link #setRecoveryInterval(long) recovery interval} is ignored
* when this property is set.
* @since 4.1
*/
public void setBackOff(BackOff backOff) {
@@ -290,12 +290,10 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
/**
* Specify the interval between recovery attempts, in <b>milliseconds</b>.
* <p>The default is 5000 ms, that is, 5 seconds.
* <p>This is a convenience method to create a {@link FixedBackOff} with the
* specified interval. For more recovery options, consider specifying a
* {@link #setBackOff(BackOff) BackOff} instance instead. Note, however, that
* explicitly setting the {@link #setBackOff(BackOff) BackOff} overrides this
* property.
* The default is 5000 ms, that is, 5 seconds. This is a convenience method
* to create a {@link FixedBackOff} with the specified interval.
* <p>For more recovery options, consider specifying a {@link BackOff}
* instance instead.
* @see #setBackOff(BackOff)
* @see #handleListenerSetupFailure
*/
@@ -1087,7 +1085,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
}
}
else {
// Recovery during active operation...
// Recovery during active operation..
if (alreadyRecovered) {
logger.debug("Setup of JMS message listener invoker failed - already recovered by other invoker", ex);
}
@@ -1521,7 +1519,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
/**
* Apply the back-off time once. In a regular scenario, the back-off is only applied if we
* failed to recover with the broker. This additional wait period avoids a burst retry
* scenario when the broker is actually up but something else is failing (i.e. listener
* scenario when the broker is actually up but something else if failing (i.e. listener
* specific).
*/
private void waitBeforeRecoveryAttempt() {
@@ -242,7 +242,7 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter
return !isStrictContentTypeMatch();
}
for (MimeType current : getSupportedMimeTypes()) {
if (current.includes(mimeType)) {
if (current.getType().equals(mimeType.getType()) && current.getSubtype().equals(mimeType.getSubtype())) {
return true;
}
}

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