mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf74ce007d |
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-10
@@ -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`.
|
||||
|
||||
+1
-2
@@ -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.
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+14
-13
@@ -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]
|
||||
|
||||
+22
-21
@@ -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.
|
||||
|
||||
|
||||
+2
-2
@@ -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()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
===============================================================================
|
||||
|
||||
-47
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
-37
@@ -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]")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
version=6.2.17
|
||||
version=6.2.15
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
-3
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
-7
@@ -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
|
||||
|
||||
-8
@@ -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) {
|
||||
|
||||
-265
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
-3
@@ -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">
|
||||
* @Bean
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }</pre>
|
||||
* @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">
|
||||
* @Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }</pre>
|
||||
* @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">
|
||||
* @Bean
|
||||
* @Profile("production")
|
||||
* @Scope("prototype")
|
||||
* public MyBean myBean() {
|
||||
* // instantiate and configure MyBean obj
|
||||
* return obj;
|
||||
* }</pre>
|
||||
* @Bean
|
||||
* @Profile("production")
|
||||
* @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;
|
||||
* @Configuration
|
||||
* public class AppConfig {
|
||||
*
|
||||
* @Bean
|
||||
* public FooService fooService() {
|
||||
* return new FooService(fooRepository());
|
||||
* }
|
||||
* @Bean
|
||||
* public FooService fooService() {
|
||||
* return new FooService(fooRepository());
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public FooRepository fooRepository() {
|
||||
* return new JdbcFooRepository(dataSource());
|
||||
* }
|
||||
* @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">
|
||||
* @Component
|
||||
* public class Calculator {
|
||||
* public int sum(int a, int b) {
|
||||
* return a+b;
|
||||
* }
|
||||
* public int sum(int a, int b) {
|
||||
* return a+b;
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public MyBean myBean() {
|
||||
* return new MyBean();
|
||||
* }
|
||||
* @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">
|
||||
* @Bean
|
||||
* public static PropertySourcesPlaceholderConfigurer pspc() {
|
||||
* // instantiate, configure and return pspc ...
|
||||
* }</pre>
|
||||
* @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">
|
||||
* @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
|
||||
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
|
||||
+7
-7
@@ -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);
|
||||
}
|
||||
|
||||
+7
-28
@@ -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();
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-4
@@ -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
|
||||
|
||||
+8
-10
@@ -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;
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+16
-14
@@ -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) {
|
||||
|
||||
+1
-3
@@ -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);
|
||||
|
||||
+5
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-25
@@ -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;
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+29
-15
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+53
-26
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+2
-4
@@ -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
|
||||
|
||||
+16
-42
@@ -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();
|
||||
|
||||
-1
@@ -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);
|
||||
|
||||
+3
-5
@@ -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));
|
||||
|
||||
+5
-108
@@ -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#";
|
||||
|
||||
+1
-1
@@ -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));
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+6
-16
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-35
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-36
@@ -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};
|
||||
|
||||
-36
@@ -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)
|
||||
|
||||
+4
-2
@@ -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();
|
||||
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
|
||||
+8
-10
@@ -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() {
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user