mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf74ce007d | |||
| 23625ee698 | |||
| c89c4ac614 | |||
| 2155e9fb25 | |||
| 24df35c55a | |||
| 3b1fa369b4 | |||
| 221adf14a4 | |||
| ad849fb3a2 | |||
| 8041a09268 | |||
| 97b9517918 | |||
| e79d8e35a2 | |||
| 96503a2ea6 | |||
| 8803d3c5dc | |||
| 5b4728f0bf | |||
| 1fd1d8d629 | |||
| afc40d7b93 | |||
| 3eb1df0ac0 | |||
| 2aa3b0a77e | |||
| 0008106adb | |||
| 8f6d44a86b | |||
| 874498272f | |||
| 2144813bad | |||
| 67a92306f7 | |||
| bc0731891b | |||
| 3faa7cac4d | |||
| 3121daf553 | |||
| 821165488e | |||
| 3b556ba0c0 | |||
| b25f98374b | |||
| c2f7cd3401 | |||
| 22d2810ed0 | |||
| b39055f293 | |||
| 140933400d | |||
| e7a5452a14 | |||
| ab96576e67 | |||
| 19b080b73f | |||
| f9b4fba97a | |||
| 37e26e0377 | |||
| 85c47a73dd | |||
| ab93020263 | |||
| a4134663a6 | |||
| a4b1155ca6 | |||
| 1bdd8337c6 | |||
| 7a0ea14452 |
@@ -13,7 +13,9 @@ content:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
@@ -36,4 +38,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.20/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
|
||||
+2
-3
@@ -50,9 +50,8 @@ XML configuration:
|
||||
|
||||
The preceding XML is more succinct. However, typos are discovered at runtime rather than
|
||||
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
|
||||
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
|
||||
that supports automatic property completion when you create bean definitions. Such IDE
|
||||
assistance is highly recommended.
|
||||
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
|
||||
completion when you create bean definitions. Such IDE assistance is highly recommended.
|
||||
|
||||
You can also configure a `java.util.Properties` instance, as follows:
|
||||
|
||||
|
||||
+5
-5
@@ -339,11 +339,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
|
||||
modularity, but determining exactly where the autowired bean definitions are declared is
|
||||
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
|
||||
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
|
||||
explicit in the code, and this may be just fine. Remember that the
|
||||
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
|
||||
can render graphs showing how everything is wired, which may be all you need. Also,
|
||||
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
|
||||
and quickly show you the location of `@Bean` methods that return that type.
|
||||
explicit in the code, and this may be just fine. Note that the
|
||||
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
|
||||
showing how everything is wired, which may be all you need. Also, your Java IDE can
|
||||
easily find all declarations and uses of the `AccountRepository` type and quickly show
|
||||
you the location of `@Bean` methods that return that type.
|
||||
|
||||
In cases where this ambiguity is not acceptable and you wish to have direct navigation
|
||||
from within your IDE from one `@Configuration` class to another, consider autowiring the
|
||||
|
||||
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
community with a single well supported expression language that can be used across all
|
||||
the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements
|
||||
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
|
||||
That said, SpEL is based on a technology-agnostic API that lets other expression language
|
||||
implementations be integrated, should the need arise.
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and
|
||||
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
|
||||
Spring community with a single well supported expression language that can be used across
|
||||
all the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements for
|
||||
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
|
||||
is based on a technology-agnostic API that lets other expression language implementations
|
||||
be integrated, should the need arise.
|
||||
|
||||
While SpEL serves as the foundation for expression evaluation within the Spring
|
||||
portfolio, it is not directly tied to Spring and can be used independently. To
|
||||
|
||||
@@ -319,9 +319,17 @@ progresses.
|
||||
== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
The recommended testing framework is https://junit.org/junit5/[JUnit 5] along with
|
||||
The recommended testing framework is https://junit.org/[JUnit Jupiter] along with
|
||||
https://mockk.io/[Mockk] for mocking.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
|
||||
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
|
||||
in this section.
|
||||
====
|
||||
|
||||
NOTE: If you are using Spring Boot, see
|
||||
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
|
||||
|
||||
@@ -352,7 +360,6 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
||||
[[per_class-lifecycle]]
|
||||
=== `PER_CLASS` Lifecycle
|
||||
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
With JUnit Jupiter (JUnit 5), Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
|
||||
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
|
||||
|
||||
+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://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[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.
|
||||
|
||||
@@ -5,24 +5,25 @@ It is important to be able to perform some integration testing without requiring
|
||||
deployment to your application server or connecting to other enterprise infrastructure.
|
||||
Doing so lets you test things such as:
|
||||
|
||||
* The correct wiring of your Spring IoC container contexts.
|
||||
* Data access using JDBC or an ORM tool. This can include such things as the correctness
|
||||
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
|
||||
* The correct wiring of your Spring components.
|
||||
* Data access using JDBC or an ORM tool.
|
||||
** This can include such things as the correctness of SQL statements, Hibernate queries,
|
||||
JPA entity mappings, and so forth.
|
||||
|
||||
The Spring Framework provides first-class support for integration testing in the
|
||||
`spring-test` module. The name of the actual JAR file might include the release version
|
||||
and might also be in the long `org.springframework.test` form, depending on where you get
|
||||
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
|
||||
for an explanation). This library includes the `org.springframework.test` package, which
|
||||
`spring-test` module. The name of the actual JAR file might include the release version,
|
||||
depending on where you get it from (see the
|
||||
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
|
||||
for details). This library includes the `org.springframework.test` package, which
|
||||
contains valuable classes for integration testing with a Spring container. This testing
|
||||
does not rely on an application server or other deployment environment. Such tests are
|
||||
slower to run than unit tests but much faster than the equivalent Selenium tests or
|
||||
remote tests that rely on deployment to an application server.
|
||||
|
||||
Unit and integration testing support is provided in the form of the annotation-driven
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
|
||||
agnostic of the actual testing framework in use, which allows instrumentation of tests
|
||||
in various environments, including JUnit, TestNG, and others.
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
|
||||
framework is agnostic of the actual testing framework in use, which allows
|
||||
instrumentation of tests in various environments, including JUnit, TestNG, and others.
|
||||
|
||||
The following section provides an overview of the high-level goals of Spring's
|
||||
integration support, and the rest of this chapter then focuses on dedicated topics:
|
||||
|
||||
+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://junit.org/junit5/docs/current/user-guide/#extensions-conditions[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://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`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.
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
|
||||
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
|
||||
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
|
||||
`@Configuration` class. Consult the
|
||||
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
|
||||
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
|
||||
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
|
||||
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
|
||||
and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
|
||||
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
|
||||
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
|
||||
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
|
||||
requests:
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
|
||||
and, consequently, browsers reject them.
|
||||
|
||||
Each `HandlerMapping` can be
|
||||
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
|
||||
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
|
||||
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
|
||||
use the WebFlux Java configuration to declare such mappings, which results in a single,
|
||||
global map passed to all `HandlerMapping` implementations.
|
||||
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
The rules for combining global and local configuration are generally additive -- for example,
|
||||
all global and all local origins. For those attributes where only a single value can be
|
||||
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
for more details.
|
||||
|
||||
[TIP]
|
||||
|
||||
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
|
||||
| Any other argument
|
||||
| If a method argument is not matched to any of the above, it is, by default, resolved as
|
||||
a `@RequestParam` if it is a simple type, as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
or as a `@ModelAttribute`, otherwise.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
|
||||
+1
-1
@@ -74,6 +74,6 @@ When a `@RequestParam` annotation is declared on a `Map<String, String>` or
|
||||
|
||||
Note that use of `@RequestParam` is optional -- for example, to set its attributes. By
|
||||
default, any argument that is a simple value type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@RequestParam`.
|
||||
|
||||
+1
-1
@@ -87,6 +87,6 @@ Reactor provides a dedicated operator for that, `Flux#collectList()`.
|
||||
| Other return values
|
||||
| If a return value remains unresolved in any other way, it is treated as a model
|
||||
attribute, unless it is a simple type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
in which case it remains unresolved.
|
||||
|===
|
||||
|
||||
@@ -207,7 +207,7 @@ was not provided (for example, model attribute was returned) or an async return
|
||||
view resolution scenarios. Explore the options in your IDE with code completion.
|
||||
* `Model`, `Map`: Extra model attributes to be added to the model for the request.
|
||||
* Any other: Any other return value (except for simple types, as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
is treated as a model attribute to be added to the model. The attribute name is derived
|
||||
from the class name by using {spring-framework-api}/core/Conventions.html[conventions],
|
||||
unless a handler method `@ModelAttribute` annotation is present.
|
||||
|
||||
@@ -71,7 +71,7 @@ rejected. No CORS headers are added to the responses of simple and actual CORS r
|
||||
and, consequently, browsers reject them.
|
||||
|
||||
Each `HandlerMapping` can be
|
||||
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
|
||||
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
|
||||
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
|
||||
use the MVC Java configuration or the XML namespace to declare such mappings, which results
|
||||
in a single global map being passed to all `HandlerMapping` instances.
|
||||
@@ -84,7 +84,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
The rules for combining global and local configuration are generally additive -- for example,
|
||||
all global and all local origins. For those attributes where only a single value can be
|
||||
accepted, for example, `allowCredentials` and `maxAge`, the local overrides the global value. See
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
for more details.
|
||||
|
||||
[TIP]
|
||||
|
||||
@@ -175,7 +175,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
|
||||
|
||||
| `TypeMismatchException`
|
||||
| (default)
|
||||
| `+{0}+` property name, `+{1}+` property value
|
||||
| `+{0}+` property name, `+{1}+` property value, `+{2}+` simple name of required type
|
||||
|
||||
| `UnsatisfiedServletRequestParameterException`
|
||||
| (default)
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ the content negotiation during the error handling phase will decide which conten
|
||||
|
||||
| Any other return value
|
||||
| If a return value is not matched to any of the above and is not a simple type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
|
||||
by default, it is treated as a model attribute to be added to the model. If it is a simple type,
|
||||
it remains unresolved.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -135,6 +135,6 @@ and others) and is equivalent to `required=false`.
|
||||
| Any other argument
|
||||
| If a method argument is not matched to any of the earlier values in this table and it is
|
||||
a simple type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]),
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]),
|
||||
it is resolved as a `@RequestParam`. Otherwise, it is resolved as a `@ModelAttribute`.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
|
||||
+1
-1
@@ -117,6 +117,6 @@ Kotlin::
|
||||
|
||||
Note that use of `@RequestParam` is optional (for example, to set its attributes).
|
||||
By default, any argument that is a simple value type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver, is treated as if it were annotated
|
||||
with `@RequestParam`.
|
||||
|
||||
+1
-1
@@ -98,6 +98,6 @@ supported for all return values.
|
||||
| Other return values
|
||||
| If a return value remains unresolved in any other way, it is treated as a model
|
||||
attribute, unless it is a simple type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
in which case it remains unresolved.
|
||||
|===
|
||||
|
||||
@@ -221,7 +221,7 @@ When multiple patterns match a URL, the best match must be selected. This is don
|
||||
one of the following depending on whether use of parsed `PathPattern` is enabled for use or not:
|
||||
|
||||
* {spring-framework-api}/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR[`PathPattern.SPECIFICITY_COMPARATOR`]
|
||||
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator-java.lang.String-[`AntPathMatcher.getPatternComparator(String path)`]
|
||||
* {spring-framework-api}/util/AntPathMatcher.html#getPatternComparator(java.lang.String)[`AntPathMatcher.getPatternComparator(String path)`]
|
||||
|
||||
Both help to sort patterns with more specific ones on top. A pattern is more specific if
|
||||
it has a lower count of URI variables (counted as 1), single wildcards (counted as 1),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"@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.2",
|
||||
"@springio/antora-extensions": "1.14.7",
|
||||
"fast-xml-parser": "4.5.2",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.5"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.13"))
|
||||
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.12"))
|
||||
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.29"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
@@ -33,7 +33,7 @@ dependencies {
|
||||
api("com.google.code.gson:gson:2.13.2")
|
||||
api("com.google.protobuf:protobuf-java-util:4.32.1")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
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")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.14-SNAPSHOT
|
||||
version=6.2.15
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+6
-3
@@ -376,9 +376,12 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
}
|
||||
MethodParameter methodParam = descriptor.getMethodParameter();
|
||||
if (methodParam != null) {
|
||||
for (Annotation annotation : methodParam.getMethodAnnotations()) {
|
||||
if (isQualifier(annotation.annotationType())) {
|
||||
return true;
|
||||
Method method = methodParam.getMethod();
|
||||
if (method == null || void.class == method.getReturnType()) {
|
||||
for (Annotation annotation : methodParam.getMethodAnnotations()) {
|
||||
if (isQualifier(annotation.annotationType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -585,7 +585,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
public void registerContainedBean(String containedBeanName, String containingBeanName) {
|
||||
synchronized (this.containedBeanMap) {
|
||||
Set<String> containedBeans =
|
||||
this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8));
|
||||
this.containedBeanMap.computeIfAbsent(containingBeanName, key -> new LinkedHashSet<>(8));
|
||||
if (!containedBeans.add(containedBeanName)) {
|
||||
return;
|
||||
}
|
||||
@@ -604,7 +604,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
|
||||
synchronized (this.dependentBeanMap) {
|
||||
Set<String> dependentBeans =
|
||||
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
|
||||
this.dependentBeanMap.computeIfAbsent(canonicalName, key -> new LinkedHashSet<>(8));
|
||||
if (!dependentBeans.add(dependentBeanName)) {
|
||||
return;
|
||||
}
|
||||
@@ -612,7 +612,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
|
||||
synchronized (this.dependenciesForBeanMap) {
|
||||
Set<String> dependenciesForBean =
|
||||
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
|
||||
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, key -> new LinkedHashSet<>(8));
|
||||
dependenciesForBean.add(canonicalName);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -2112,8 +2112,8 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
|
||||
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
|
||||
|
||||
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
|
||||
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
|
||||
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
|
||||
@@ -2724,6 +2724,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerSingleton("nonNullBean", "Test");
|
||||
bf.registerBeanDefinition("mixedNullableInjectionBean",
|
||||
new RootBeanDefinition(MixedNullableInjectionBean.class));
|
||||
|
||||
MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class);
|
||||
assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull();
|
||||
assertThat(mixedNullableInjectionBean.nullableBean).isNull();
|
||||
@@ -2734,6 +2735,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.registerSingleton("nonNullBean", "Test");
|
||||
bf.registerBeanDefinition("mixedOptionalInjectionBean",
|
||||
new RootBeanDefinition(MixedOptionalInjectionBean.class));
|
||||
|
||||
MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class);
|
||||
assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull();
|
||||
assertThat(mixedOptionalInjectionBean.nullableBean).isNull();
|
||||
|
||||
+2
-3
@@ -444,8 +444,8 @@ class InjectAnnotationBeanPostProcessorTests {
|
||||
bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class));
|
||||
bf.registerSingleton("stringFactoryBean", new StringFactoryBean());
|
||||
|
||||
final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean");
|
||||
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean");
|
||||
|
||||
assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull();
|
||||
assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull();
|
||||
@@ -788,7 +788,6 @@ class InjectAnnotationBeanPostProcessorTests {
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
|
||||
public ConstructorResourceInjectionBean() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
Vendored
+1
-2
@@ -51,8 +51,7 @@ class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache>
|
||||
this.cacheManager.createCache(CACHE_NAME_NO_NULL, new MutableConfiguration<>());
|
||||
this.nativeCache = this.cacheManager.getCache(CACHE_NAME);
|
||||
this.cache = new JCacheCache(this.nativeCache);
|
||||
Cache<Object, Object> nativeCacheNoNull =
|
||||
this.cacheManager.getCache(CACHE_NAME_NO_NULL);
|
||||
Cache<Object, Object> nativeCacheNoNull = this.cacheManager.getCache(CACHE_NAME_NO_NULL);
|
||||
this.cacheNoNull = new JCacheCache(nativeCacheNoNull, false);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -113,7 +113,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
builder.setUnless(getAttributeValue(opElement, "unless", ""));
|
||||
builder.setSync(Boolean.parseBoolean(getAttributeValue(opElement, "sync", "false")));
|
||||
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
|
||||
col.add(builder.build());
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
builder.setBeforeInvocation(Boolean.parseBoolean(after.trim()));
|
||||
}
|
||||
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
|
||||
col.add(builder.build());
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||
parserContext.getReaderContext(), new CachePutOperation.Builder());
|
||||
builder.setUnless(getAttributeValue(opElement, "unless", ""));
|
||||
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, k -> new ArrayList<>(2));
|
||||
Collection<CacheOperation> col = cacheOpMap.computeIfAbsent(nameHolder, key -> new ArrayList<>(2));
|
||||
col.add(builder.build());
|
||||
}
|
||||
|
||||
|
||||
+16
-13
@@ -172,22 +172,25 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
|
||||
}
|
||||
}
|
||||
|
||||
boolean cacheable = true;
|
||||
for (String autowiredBeanName : autowiredBeanNames) {
|
||||
if (!this.beanFactory.containsBean(autowiredBeanName)) {
|
||||
cacheable = false;
|
||||
}
|
||||
else {
|
||||
if (this.beanName != null) {
|
||||
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
|
||||
}
|
||||
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
|
||||
boolean cacheable = false;
|
||||
if (!autowiredBeanNames.isEmpty()) {
|
||||
cacheable = true;
|
||||
for (String autowiredBeanName : autowiredBeanNames) {
|
||||
if (!this.beanFactory.containsBean(autowiredBeanName)) {
|
||||
cacheable = false;
|
||||
}
|
||||
else {
|
||||
if (this.beanName != null) {
|
||||
this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName);
|
||||
}
|
||||
if (!this.beanFactory.isSingleton(autowiredBeanName)) {
|
||||
cacheable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cacheable) {
|
||||
this.cachedTarget = target;
|
||||
}
|
||||
}
|
||||
if (cacheable) {
|
||||
this.cachedTarget = target;
|
||||
}
|
||||
|
||||
return target;
|
||||
|
||||
+1
-1
@@ -630,7 +630,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
finishRefresh();
|
||||
}
|
||||
|
||||
catch (RuntimeException | Error ex ) {
|
||||
catch (RuntimeException | Error ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Exception encountered during context initialization - " +
|
||||
"cancelling refresh attempt: " + ex);
|
||||
|
||||
+1
-1
@@ -383,7 +383,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
try {
|
||||
task = ScheduledAnnotationReactiveSupport.createSubscriptionRunnable(method, bean, scheduled,
|
||||
this.registrar::getObservationRegistry,
|
||||
this.reactiveSubscriptions.computeIfAbsent(bean, k -> new CopyOnWriteArrayList<>()));
|
||||
this.reactiveSubscriptions.computeIfAbsent(bean, key -> new CopyOnWriteArrayList<>()));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new IllegalStateException("Could not create recurring task for @Scheduled method '" +
|
||||
|
||||
+31
@@ -19,6 +19,7 @@ package org.springframework.context.annotation.configuration;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
@@ -100,6 +102,16 @@ class AutowiredConfigurationTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutowiredConfigurationMethodDependenciesWithQualifier() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
QualifiedAutowiredMethodConfig.class);
|
||||
|
||||
assertThat(context.getBeansOfType(Colour.class)).isEmpty();
|
||||
assertThat(context.getBean(TestBean.class).getName()).isEmpty();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutowiredSingleConstructorSupported() {
|
||||
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||
@@ -297,6 +309,25 @@ class AutowiredConfigurationTests {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class QualifiedAutowiredMethodConfig {
|
||||
|
||||
@Bean
|
||||
@Qualifier("testBean")
|
||||
public TestBean testBean(Optional<Colour> colour, Optional<List<Colour>> colours) {
|
||||
if (!colour.isEmpty() || !colours.isEmpty()) {
|
||||
throw new IllegalStateException("Unexpected match: " + colour + " " + colours);
|
||||
}
|
||||
return new TestBean("");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public List<?> someList() {
|
||||
return Collections.singletonList(new TestBean("shouldNotMatch"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class AutowiredConstructorConfig {
|
||||
|
||||
|
||||
+9
@@ -29,6 +29,7 @@ class ApplicationContextLifecycleTests {
|
||||
@Test
|
||||
void beansStart() {
|
||||
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
|
||||
|
||||
context.start();
|
||||
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
|
||||
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
|
||||
@@ -39,12 +40,14 @@ class ApplicationContextLifecycleTests {
|
||||
assertThat(bean2.isRunning()).as(error).isTrue();
|
||||
assertThat(bean3.isRunning()).as(error).isTrue();
|
||||
assertThat(bean4.isRunning()).as(error).isTrue();
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void beansStop() {
|
||||
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
|
||||
|
||||
context.start();
|
||||
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
|
||||
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
|
||||
@@ -55,18 +58,21 @@ class ApplicationContextLifecycleTests {
|
||||
assertThat(bean2.isRunning()).as(startError).isTrue();
|
||||
assertThat(bean3.isRunning()).as(startError).isTrue();
|
||||
assertThat(bean4.isRunning()).as(startError).isTrue();
|
||||
|
||||
context.stop();
|
||||
String stopError = "bean was not stopped";
|
||||
assertThat(bean1.isRunning()).as(stopError).isFalse();
|
||||
assertThat(bean2.isRunning()).as(stopError).isFalse();
|
||||
assertThat(bean3.isRunning()).as(stopError).isFalse();
|
||||
assertThat(bean4.isRunning()).as(stopError).isFalse();
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void startOrder() {
|
||||
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
|
||||
|
||||
context.start();
|
||||
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
|
||||
LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2");
|
||||
@@ -81,12 +87,14 @@ class ApplicationContextLifecycleTests {
|
||||
assertThat(bean2.getStartOrder()).as(orderError).isGreaterThan(bean1.getStartOrder());
|
||||
assertThat(bean3.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder());
|
||||
assertThat(bean4.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder());
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void stopOrder() {
|
||||
AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass());
|
||||
|
||||
context.start();
|
||||
context.stop();
|
||||
LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1");
|
||||
@@ -102,6 +110,7 @@ class ApplicationContextLifecycleTests {
|
||||
assertThat(bean2.getStopOrder()).as(orderError).isLessThan(bean1.getStopOrder());
|
||||
assertThat(bean3.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder());
|
||||
assertThat(bean4.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder());
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ public class CustomizerRegistry {
|
||||
Class<? extends KeyFactoryCustomizer> klass = customizer.getClass();
|
||||
for (Class type : customizerTypes) {
|
||||
if (type.isAssignableFrom(klass)) {
|
||||
List<KeyFactoryCustomizer> list = customizers.computeIfAbsent(type, k -> new ArrayList<>());
|
||||
List<KeyFactoryCustomizer> list = customizers.computeIfAbsent(type, key -> new ArrayList<>());
|
||||
list.add(customizer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
|
||||
/**
|
||||
* Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the
|
||||
@@ -114,8 +113,8 @@ public final class BridgeMethodResolver {
|
||||
if (bridgedMethod == null) {
|
||||
// Gather all methods with matching name and parameter size.
|
||||
List<Method> candidateMethods = new ArrayList<>();
|
||||
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
|
||||
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
|
||||
ReflectionUtils.doWithMethods(userClass, candidateMethods::add,
|
||||
candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
|
||||
if (!candidateMethods.isEmpty()) {
|
||||
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
|
||||
searchCandidates(candidateMethods, bridgeMethod, targetClass));
|
||||
@@ -152,9 +151,6 @@ public final class BridgeMethodResolver {
|
||||
private static Method searchCandidates(
|
||||
List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
|
||||
|
||||
if (candidateMethods.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Method previousMethod = null;
|
||||
boolean sameSig = true;
|
||||
for (Method candidateMethod : candidateMethods) {
|
||||
@@ -204,19 +200,17 @@ public final class BridgeMethodResolver {
|
||||
}
|
||||
|
||||
private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> clazz) {
|
||||
// First, compare return type.
|
||||
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(genericMethod, clazz);
|
||||
if (!ClassUtils.resolvePrimitiveIfNecessary(genericReturnType.toClass()).isAssignableFrom(
|
||||
ClassUtils.resolvePrimitiveIfNecessary(candidateMethod.getReturnType()))) {
|
||||
return false;
|
||||
}
|
||||
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
|
||||
for (int i = 0; i < candidateParameters.length; i++) {
|
||||
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz);
|
||||
Class<?> candidateParameter = candidateParameters[i];
|
||||
if (candidateParameter.isArray()) {
|
||||
// An array type: compare the component type.
|
||||
if (!candidateParameter.componentType().equals(genericParameter.getComponentType().toClass())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// A non-array type: compare the type itself.
|
||||
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
|
||||
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
|
||||
if (!ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()).equals(
|
||||
ClassUtils.resolvePrimitiveIfNecessary(candidateParameters[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -112,9 +112,7 @@ final class AnnotationTypeMapping {
|
||||
this.root = (source != null ? source.getRoot() : this);
|
||||
this.distance = (source == null ? 0 : source.getDistance() + 1);
|
||||
this.annotationType = annotationType;
|
||||
this.metaTypes = merge(
|
||||
source != null ? source.getMetaTypes() : null,
|
||||
annotationType);
|
||||
this.metaTypes = merge((source != null ? source.getMetaTypes() : null), annotationType);
|
||||
this.annotation = annotation;
|
||||
this.attributes = AttributeMethods.forAnnotationType(annotationType);
|
||||
this.mirrorSets = new MirrorSets();
|
||||
|
||||
+10
-1
@@ -76,6 +76,7 @@ final class AnnotationTypeMappings {
|
||||
|
||||
private void addAllMappings(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
|
||||
addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
|
||||
while (!queue.isEmpty()) {
|
||||
@@ -270,11 +271,19 @@ final class AnnotationTypeMappings {
|
||||
*/
|
||||
AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
|
||||
|
||||
AnnotationTypeMappings result = this.mappings.get(annotationType);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
result = createMappings(annotationType, visitedAnnotationTypes);
|
||||
AnnotationTypeMappings existing = this.mappings.putIfAbsent(annotationType, result);
|
||||
return (existing != null ? existing : result);
|
||||
}
|
||||
|
||||
private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
|
||||
Set<Class<? extends Annotation>> visitedAnnotationTypes) {
|
||||
|
||||
return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType,
|
||||
visitedAnnotationTypes);
|
||||
}
|
||||
|
||||
@@ -894,8 +894,7 @@ public abstract class AnnotationUtils {
|
||||
private static Map<String, DefaultValueHolder> getDefaultValues(
|
||||
Class<? extends Annotation> annotationType) {
|
||||
|
||||
return defaultValuesCache.computeIfAbsent(annotationType,
|
||||
AnnotationUtils::computeDefaultValues);
|
||||
return defaultValuesCache.computeIfAbsent(annotationType, AnnotationUtils::computeDefaultValues);
|
||||
}
|
||||
|
||||
private static Map<String, DefaultValueHolder> computeDefaultValues(
|
||||
|
||||
@@ -133,6 +133,14 @@ import org.springframework.util.Assert;
|
||||
* please use standard Java reflection or Spring's {@link AnnotationUtils}
|
||||
* for simple annotation retrieval purposes.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
|
||||
* its attributes references a {@link Class} or {@link Enum}
|
||||
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
|
||||
* annotation will not be accessible via the {@code MergedAnnotations} API.
|
||||
* To assist with diagnosing such scenarios, you can set the log level for
|
||||
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
|
||||
* {@code INFO}, or {@code WARN}.
|
||||
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 5.2
|
||||
|
||||
+1
-1
@@ -490,7 +490,7 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
}
|
||||
|
||||
private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
|
||||
return this.converters.computeIfAbsent(convertiblePair, k -> new ConvertersForPair());
|
||||
return this.converters.computeIfAbsent(convertiblePair, key -> new ConvertersForPair());
|
||||
}
|
||||
|
||||
public void remove(Class<?> sourceType, Class<?> targetType) {
|
||||
|
||||
+1
-1
@@ -188,7 +188,7 @@ final class SubscriberInputStream extends InputStream implements Subscriber<Data
|
||||
}
|
||||
|
||||
private void resume() {
|
||||
if (this.parkedThread != READY) {
|
||||
if (this.parkedThread.get() != READY) {
|
||||
Object old = this.parkedThread.getAndSet(READY);
|
||||
if (old != READY) {
|
||||
LockSupport.unpark((Thread)old);
|
||||
|
||||
+17
-3
@@ -634,12 +634,26 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
}
|
||||
|
||||
// Handle absolute paths correctly: do not apply parent to absolute paths.
|
||||
File pathFile = new File(path);
|
||||
File candidate = (pathFile.isAbsolute() ? pathFile : new File(parent, path));
|
||||
File pathFile = null;
|
||||
boolean absolute = false;
|
||||
if (path.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
|
||||
try {
|
||||
pathFile = new File(ResourceUtils.toURI(path));
|
||||
absolute = true;
|
||||
}
|
||||
catch (URISyntaxException | IllegalArgumentException ex) {
|
||||
// Fall back to plain String constructor below.
|
||||
}
|
||||
}
|
||||
if (pathFile == null) {
|
||||
pathFile = new File(path);
|
||||
absolute = pathFile.isAbsolute();
|
||||
}
|
||||
File candidate = (absolute ? pathFile : new File(parent, path));
|
||||
|
||||
// For relative paths, enforce security check: must be under parent.
|
||||
// For absolute paths, just verify file exists (matching JVM behavior).
|
||||
if (candidate.isFile() && (pathFile.isAbsolute() ||
|
||||
if (candidate.isFile() && (absolute ||
|
||||
candidate.getCanonicalPath().contains(parent.getCanonicalPath()))) {
|
||||
manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches));
|
||||
}
|
||||
|
||||
+2
-2
@@ -323,8 +323,8 @@ public class SpringFactoriesLoader {
|
||||
SpringFactoriesLoader.class.getClassLoader());
|
||||
Map<String, Factories> factoriesCache = cache.computeIfAbsent(
|
||||
resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
|
||||
Factories factories = factoriesCache.computeIfAbsent(resourceLocation, key ->
|
||||
new Factories(loadFactoriesResource(resourceClassLoader, resourceLocation)));
|
||||
Factories factories = factoriesCache.computeIfAbsent(resourceLocation,
|
||||
key -> new Factories(loadFactoriesResource(resourceClassLoader, resourceLocation)));
|
||||
return new SpringFactoriesLoader(classLoader, factories.byType());
|
||||
}
|
||||
|
||||
|
||||
@@ -36,11 +36,19 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
|
||||
* or {@link MethodMetadata method}), in a form that does not necessarily require
|
||||
* Defines access to the annotations on a specific {@link AnnotationMetadata class}
|
||||
* or {@link MethodMetadata method}, in a form that does not necessarily require
|
||||
* class loading of the types being inspected. Note, however, that classes for
|
||||
* encountered annotations will be loaded.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
|
||||
* its attributes references a {@link Class} or {@link Enum}
|
||||
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
|
||||
* annotation will not be accessible via the {@code AnnotatedTypeMetadata} API.
|
||||
* To assist with diagnosing such scenarios, you can set the log level for
|
||||
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
|
||||
* {@code INFO}, or {@code WARN}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Fisher
|
||||
* @author Mark Pollack
|
||||
|
||||
@@ -25,9 +25,17 @@ import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
|
||||
/**
|
||||
* Interface that defines abstract access to the annotations of a specific
|
||||
* Interface that defines abstract access to the annotations on a specific
|
||||
* class, in a form that does not require that class to be loaded yet.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
|
||||
* its attributes references a {@link Class} or {@link Enum}
|
||||
* {@linkplain TypeNotPresentException that is not present in the classpath}, that
|
||||
* annotation will not be accessible via the {@code AnnotationMetadata} API.
|
||||
* To assist with diagnosing such scenarios, you can set the log level for
|
||||
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
|
||||
* {@code INFO}, or {@code WARN}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Fisher
|
||||
* @author Phillip Webb
|
||||
|
||||
@@ -17,9 +17,17 @@
|
||||
package org.springframework.core.type;
|
||||
|
||||
/**
|
||||
* Interface that defines abstract access to the annotations of a specific
|
||||
* Interface that defines abstract access to the annotations on a specific
|
||||
* method, in a form that does not require that method's class to be loaded yet.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: If an annotation cannot be loaded because one of
|
||||
* its attributes references a {@link Class} or {@link Enum}
|
||||
* {@linkplain TypeNotPresentException that is not present in the classpath},
|
||||
* that annotation will not be accessible via the {@code MethodMetadata} API.
|
||||
* To assist with diagnosing such scenarios, you can set the log level for
|
||||
* {@code "org.springframework.core.annotation.MergedAnnotation"} to {@code DEBUG},
|
||||
* {@code INFO}, or {@code WARN}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Pollack
|
||||
* @author Chris Beams
|
||||
|
||||
@@ -125,7 +125,7 @@ public class SimpleNamespaceContext implements NamespaceContext {
|
||||
else {
|
||||
this.prefixToNamespaceUri.put(prefix, namespaceUri);
|
||||
Set<String> prefixes =
|
||||
this.namespaceUriToPrefixes.computeIfAbsent(namespaceUri, k -> new LinkedHashSet<>());
|
||||
this.namespaceUriToPrefixes.computeIfAbsent(namespaceUri, key -> new LinkedHashSet<>());
|
||||
prefixes.add(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
+100
-15
@@ -44,17 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SuppressWarnings("rawtypes")
|
||||
class BridgeMethodResolverTests {
|
||||
|
||||
private static Method findMethodWithReturnType(String name, Class<?> returnType, Class<SettingsDaoImpl> targetType) {
|
||||
Method[] methods = targetType.getMethods();
|
||||
for (Method m : methods) {
|
||||
if (m.getName().equals(name) && m.getReturnType().equals(returnType)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void findBridgedMethod() throws Exception {
|
||||
Method unbridged = MyFoo.class.getDeclaredMethod("someMethod", String.class, Object.class);
|
||||
@@ -106,6 +95,32 @@ class BridgeMethodResolverTests {
|
||||
assertThat(mostSpecificMethod).isSameAs(originalMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBridgedMethodWithDefaultMethodInInterfaceHierarchy() throws Exception {
|
||||
Method getValueDefault = DefaultMethods.class.getMethod("getValue");
|
||||
Method getValuesDefault = DefaultMethods.class.getMethod("getValues");
|
||||
Method getValueArgDefault = DefaultMethods.class.getMethod("getValue", Integer.class);
|
||||
Method getValuesArgDefault = DefaultMethods.class.getMethod("getValues", Integer[].class);
|
||||
for (Method method : ConcreteMethods.class.getMethods()) {
|
||||
if (method.getName().equals("getValue")) {
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo(
|
||||
method.getParameterCount() > 0 ? getValueArgDefault : getValueDefault);
|
||||
}
|
||||
else if (method.getName().equals("getValues")) {
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo(
|
||||
method.getParameterCount() > 0 ? getValuesArgDefault : getValuesDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBridgedMethodForCovariantReturnType() throws Exception {
|
||||
Method originalMethod = OuterSubclass.class.getDeclaredMethod("getInner");
|
||||
for (Method method: OuterSubclass.class.getDeclaredMethods()) {
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo(originalMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBridgedMethodInHierarchyWithBoundedGenerics() throws Exception {
|
||||
Method originalMethod = Bar.class.getDeclaredMethod("someMethod", Object.class, Object.class);
|
||||
@@ -161,6 +176,16 @@ class BridgeMethodResolverTests {
|
||||
assertThat(BridgeMethodResolver.findBridgedMethod(loadWithSettingsReturn)).isEqualTo(method);
|
||||
}
|
||||
|
||||
private static Method findMethodWithReturnType(String name, Class<?> returnType, Class<?> targetType) {
|
||||
Method[] methods = targetType.getMethods();
|
||||
for (Method m : methods) {
|
||||
if (m.getName().equals(name) && m.getReturnType().equals(returnType)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBridgedMethodFromParent() throws Exception {
|
||||
Method loadFromParentBridge = SettingsDaoImpl.class.getMethod("loadFromParent");
|
||||
@@ -408,6 +433,7 @@ class BridgeMethodResolverTests {
|
||||
public abstract static class SubBar<T extends StringProducer> extends InterBar<T> {
|
||||
}
|
||||
|
||||
|
||||
public interface StringProducer extends CharSequence {
|
||||
}
|
||||
|
||||
@@ -451,6 +477,69 @@ class BridgeMethodResolverTests {
|
||||
}
|
||||
|
||||
|
||||
interface InterfaceMethods<T> {
|
||||
|
||||
T getValue();
|
||||
|
||||
T[] getValues();
|
||||
|
||||
T getValue(T arg);
|
||||
|
||||
T[] getValues(T[] args);
|
||||
}
|
||||
|
||||
|
||||
interface DefaultMethods extends InterfaceMethods<Integer> {
|
||||
|
||||
default Integer getValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default Integer[] getValues() {
|
||||
return new Integer[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
default Integer getValue(Integer arg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Integer[] getValues(Integer[] args) {
|
||||
return new Integer[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ConcreteMethods implements DefaultMethods {
|
||||
}
|
||||
|
||||
|
||||
static class Outer {
|
||||
|
||||
Inner getInner() {
|
||||
return new Inner();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class OuterSubclass extends Outer {
|
||||
|
||||
@Override
|
||||
InnerSubclass getInner() {
|
||||
return new InnerSubclass();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class Inner {
|
||||
}
|
||||
|
||||
|
||||
static class InnerSubclass extends Inner {
|
||||
}
|
||||
|
||||
|
||||
public static class Enclosing<T> {
|
||||
|
||||
public class Enclosed<S> {
|
||||
@@ -844,22 +933,18 @@ class BridgeMethodResolverTests {
|
||||
|
||||
|
||||
public interface MessageBroadcaster extends Receiver<MessageEvent> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class RemovedMessageEvent extends MessageEvent {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class NewMessageEvent extends MessageEvent {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class ModifiedMessageEvent extends MessageEvent {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+9
-6
@@ -72,20 +72,22 @@ public class SortedResourcesFactoryBean extends AbstractFactoryBean<Resource[]>
|
||||
|
||||
@Override
|
||||
protected Resource[] createInstance() throws Exception {
|
||||
List<Resource> scripts = new ArrayList<>();
|
||||
List<Resource> result = new ArrayList<>();
|
||||
for (String location : this.locations) {
|
||||
Resource[] resources = this.resourcePatternResolver.getResources(location);
|
||||
|
||||
// Cache URLs to avoid repeated I/O during sorting
|
||||
Map<Resource, String> urlCache = new LinkedHashMap<>(resources.length);
|
||||
List<Resource> failingResources = new ArrayList<>();
|
||||
for (Resource resource : resources) {
|
||||
try {
|
||||
urlCache.put(resource, resource.getURL().toString());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to resolve URL for resource [" + resource +
|
||||
"] from location pattern [" + location + "]", ex);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to resolve " + resource + " for sorting purposes: " + ex);
|
||||
}
|
||||
failingResources.add(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,9 +95,10 @@ public class SortedResourcesFactoryBean extends AbstractFactoryBean<Resource[]>
|
||||
List<Resource> sortedResources = new ArrayList<>(urlCache.keySet());
|
||||
sortedResources.sort(Comparator.comparing(urlCache::get));
|
||||
|
||||
scripts.addAll(sortedResources);
|
||||
result.addAll(sortedResources);
|
||||
result.addAll(failingResources);
|
||||
}
|
||||
return scripts.toArray(new Resource[0]);
|
||||
return result.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-17
@@ -202,24 +202,10 @@ public abstract class DataSourceUtils {
|
||||
boolean debugEnabled = logger.isDebugEnabled();
|
||||
// Set read-only flag.
|
||||
if (setReadOnly) {
|
||||
try {
|
||||
if (debugEnabled) {
|
||||
logger.debug("Setting JDBC Connection [" + con + "] read-only");
|
||||
}
|
||||
con.setReadOnly(true);
|
||||
}
|
||||
catch (SQLException | RuntimeException ex) {
|
||||
Throwable exToCheck = ex;
|
||||
while (exToCheck != null) {
|
||||
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
|
||||
// Assume it's a connection timeout that would otherwise get lost: for example, from JDBC 4.0
|
||||
throw ex;
|
||||
}
|
||||
exToCheck = exToCheck.getCause();
|
||||
}
|
||||
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
|
||||
logger.debug("Could not set JDBC Connection read-only", ex);
|
||||
if (debugEnabled) {
|
||||
logger.debug("Setting JDBC Connection [" + con + "] read-only");
|
||||
}
|
||||
setReadOnlyIfPossible(con);
|
||||
}
|
||||
|
||||
// Apply specific isolation level, if any.
|
||||
@@ -238,6 +224,31 @@ public abstract class DataSourceUtils {
|
||||
return previousIsolationLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the read-only hint to the given Connection,
|
||||
* suppressing exceptions other than timeout-related ones.
|
||||
* @param con the Connection to prepare
|
||||
* @throws SQLException in case of a timeout exception
|
||||
* @since 6.2.15
|
||||
*/
|
||||
static void setReadOnlyIfPossible(Connection con) throws SQLException {
|
||||
try {
|
||||
con.setReadOnly(true);
|
||||
}
|
||||
catch (SQLException | RuntimeException ex) {
|
||||
Throwable exToCheck = ex;
|
||||
while (exToCheck != null) {
|
||||
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
|
||||
// Assume it's a connection timeout that would otherwise get lost: for example, from JDBC 4.0
|
||||
throw ex;
|
||||
}
|
||||
exToCheck = exToCheck.getCause();
|
||||
}
|
||||
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
|
||||
logger.debug("Could not set JDBC Connection read-only", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the given Connection after a transaction,
|
||||
* regarding read-only flag and isolation level.
|
||||
|
||||
+44
-36
@@ -471,48 +471,56 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
|
||||
/**
|
||||
* Return the target Connection, fetching it and initializing it if necessary.
|
||||
*/
|
||||
private Connection getTargetConnection(Method operation) throws SQLException {
|
||||
if (this.target == null) {
|
||||
// No target Connection held -> fetch one.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Connecting to database for operation '" + operation.getName() + "'");
|
||||
}
|
||||
|
||||
// Fetch physical Connection from DataSource.
|
||||
DataSource dataSource = getDataSourceToUse();
|
||||
this.target = (this.username != null ? dataSource.getConnection(this.username, this.password) :
|
||||
dataSource.getConnection());
|
||||
if (this.target == null) {
|
||||
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
|
||||
}
|
||||
|
||||
// Apply kept transaction settings, if any.
|
||||
if (this.readOnly && readOnlyDataSource == null) {
|
||||
try {
|
||||
this.target.setReadOnly(true);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// "read-only not supported" -> ignore, it's just a hint anyway
|
||||
logger.debug("Could not set JDBC Connection read-only", ex);
|
||||
}
|
||||
}
|
||||
if (this.transactionIsolation != null &&
|
||||
!this.transactionIsolation.equals(defaultTransactionIsolation())) {
|
||||
this.target.setTransactionIsolation(this.transactionIsolation);
|
||||
}
|
||||
if (this.autoCommit != null && this.autoCommit != defaultAutoCommit()) {
|
||||
this.target.setAutoCommit(this.autoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
private Connection getTargetConnection(Method operation) throws Throwable {
|
||||
Connection target = this.target;
|
||||
if (target != null) {
|
||||
// Target Connection already held -> return it.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Using existing database connection for operation '" + operation.getName() + "'");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
return this.target;
|
||||
// No target Connection held -> fetch one.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Connecting to database for operation '" + operation.getName() + "'");
|
||||
}
|
||||
|
||||
// Fetch physical Connection from DataSource.
|
||||
DataSource dataSource = getDataSourceToUse();
|
||||
target = (this.username != null ? dataSource.getConnection(this.username, this.password) :
|
||||
dataSource.getConnection());
|
||||
if (target == null) {
|
||||
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
|
||||
}
|
||||
|
||||
// Apply kept transaction settings, if any.
|
||||
try {
|
||||
if (this.readOnly && readOnlyDataSource == null) {
|
||||
DataSourceUtils.setReadOnlyIfPossible(target);
|
||||
}
|
||||
if (this.transactionIsolation != null &&
|
||||
!this.transactionIsolation.equals(defaultTransactionIsolation())) {
|
||||
target.setTransactionIsolation(this.transactionIsolation);
|
||||
}
|
||||
if (this.autoCommit != null && this.autoCommit != defaultAutoCommit()) {
|
||||
target.setAutoCommit(this.autoCommit);
|
||||
}
|
||||
}
|
||||
catch (Throwable settingsEx) {
|
||||
logger.debug("Failed to apply transaction settings to JDBC Connection", settingsEx);
|
||||
// Close Connection and do not set it as target.
|
||||
try {
|
||||
target.close();
|
||||
}
|
||||
catch (Throwable closeEx) {
|
||||
logger.debug("Could not close JDBC Connection after failed settings", closeEx);
|
||||
}
|
||||
throw settingsEx;
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
return target;
|
||||
}
|
||||
|
||||
private DataSource getDataSourceToUse() {
|
||||
|
||||
+2
-2
@@ -55,7 +55,7 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<ou
|
||||
* @since 5.0
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
// TODO Replace by the vararg variant in Spring Framework 6
|
||||
// TODO Replace by the vararg variant in Spring Framework 7
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? =
|
||||
queryForObject(sql, args, T::class.java) as T
|
||||
|
||||
@@ -89,7 +89,7 @@ inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out
|
||||
* @since 5.0
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
// TODO Replace by the vararg variant in Spring Framework 6
|
||||
// TODO Replace by the vararg variant in Spring Framework 7
|
||||
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
|
||||
queryForList(sql, args, T::class.java)
|
||||
|
||||
|
||||
+9
@@ -21,6 +21,7 @@ import javax.sql.DataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.PropertyValue;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
@@ -32,6 +33,7 @@ import org.springframework.jdbc.BadSqlGrammarException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.AbstractDriverBasedDataSource;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean;
|
||||
import org.springframework.jdbc.datasource.init.CannotReadScriptException;
|
||||
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -67,6 +69,13 @@ class JdbcNamespaceIntegrationTests {
|
||||
assertCorrectSetup("jdbc-config-pattern.xml", "dataSource");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithNonExistentResource() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> assertCorrectSetup("jdbc-config-nonexistent.xml", "dataSource"))
|
||||
.withCauseInstanceOf(CannotReadScriptException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithAnonymousDataSourceAndDefaultDatabaseName() {
|
||||
assertThat(extractDataSourceUrl("jdbc-config-db-name-default-and-anonymous-datasource.xml"))
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
|
||||
|
||||
<jdbc:embedded-database>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
</beans>
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
|
||||
|
||||
<embedded-database id="dataSource" database-name="customDbName">
|
||||
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
|
||||
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
</embedded-database>
|
||||
<jdbc:embedded-database id="dataSource" database-name="customDbName">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
</beans:beans>
|
||||
</beans>
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
|
||||
|
||||
<embedded-database id="dataSource" database-name="shouldBeOverriddenByGeneratedName" generate-name="true">
|
||||
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
|
||||
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
</embedded-database>
|
||||
<jdbc:embedded-database id="dataSource" database-name="shouldBeOverriddenByGeneratedName" generate-name="true">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
</beans:beans>
|
||||
</beans>
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/jdbc"
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
|
||||
|
||||
<embedded-database id="dataSource">
|
||||
<script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
|
||||
<script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
</embedded-database>
|
||||
<jdbc:embedded-database id="dataSource">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
</beans:beans>
|
||||
</beans>
|
||||
|
||||
+3
-2
@@ -5,7 +5,8 @@
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
|
||||
|
||||
<jdbc:embedded-database id="firstDataSource" />
|
||||
<jdbc:embedded-database id="secondDataSource" />
|
||||
<jdbc:embedded-database id="firstDataSource"/>
|
||||
|
||||
<jdbc:embedded-database id="secondDataSource"/>
|
||||
|
||||
</beans>
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/custom-data.sql"/> <!-- does not exist -->
|
||||
</jdbc:embedded-database>
|
||||
|
||||
</beans>
|
||||
@@ -5,18 +5,18 @@
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd">
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
<jdbc:embedded-database id="h2DataSource" type="H2">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
<jdbc:embedded-database id="derbyDataSource" type="DERBY">
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema-derby.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" />
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema-derby.sql"/>
|
||||
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
|
||||
</beans>
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util-3.1.xsd">
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL" />
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL"/>
|
||||
|
||||
<jdbc:initialize-database data-source="dataSource" enabled="#{properties['data.source.init']}">
|
||||
<jdbc:script location="#{properties['schema.scripts']}" />
|
||||
<jdbc:script location="#{properties['insert.scripts']}" />
|
||||
<jdbc:script location="#{properties['schema.scripts']}"/>
|
||||
<jdbc:script location="#{properties['insert.scripts']}"/>
|
||||
</jdbc:initialize-database>
|
||||
|
||||
<util:properties id="properties">
|
||||
|
||||
+4
-5
@@ -4,15 +4,14 @@
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL" />
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL"/>
|
||||
|
||||
<jdbc:initialize-database data-source="dataSource" enabled="${data.source.init}">
|
||||
<jdbc:script location="${schema.scripts}" />
|
||||
<jdbc:script location="${insert.scripts}" />
|
||||
<jdbc:script location="${schema.scripts}"/>
|
||||
<jdbc:script location="${insert.scripts}"/>
|
||||
</jdbc:initialize-database>
|
||||
|
||||
<bean
|
||||
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
|
||||
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
|
||||
<property name="properties">
|
||||
<value>
|
||||
schema.scripts=classpath:org/springframework/jdbc/config/db-schema.sql
|
||||
|
||||
+1
-1
@@ -246,7 +246,7 @@ public class CachingConnectionFactory extends SingleConnectionFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
Deque<Session> sessionList = this.cachedSessions.computeIfAbsent(mode, k -> new ArrayDeque<>());
|
||||
Deque<Session> sessionList = this.cachedSessions.computeIfAbsent(mode, key -> new ArrayDeque<>());
|
||||
Session session = null;
|
||||
synchronized (sessionList) {
|
||||
if (!sessionList.isEmpty()) {
|
||||
|
||||
@@ -156,7 +156,7 @@ public class JmsResourceHolder extends ResourceHolderSupport {
|
||||
this.sessions.add(session);
|
||||
if (connection != null) {
|
||||
Deque<Session> sessions =
|
||||
this.sessionsPerConnection.computeIfAbsent(connection, k -> new ArrayDeque<>());
|
||||
this.sessionsPerConnection.computeIfAbsent(connection, key -> new ArrayDeque<>());
|
||||
sessions.add(session);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -320,7 +320,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe
|
||||
Observation observation = createObservation(message).start();
|
||||
Observation.Scope scope = observation.openScope();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received message of type [" + message.getClass() + "] from consumer [" +
|
||||
logger.debug("Received message of type [" + message.getClass().getName() + "] from consumer [" +
|
||||
consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" +
|
||||
sessionToUse + "]");
|
||||
}
|
||||
|
||||
+12
-4
@@ -757,10 +757,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
}
|
||||
waitCount++;
|
||||
}
|
||||
// Clear remaining scheduled invokers, possibly left over as paused tasks
|
||||
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) {
|
||||
scheduledInvoker.clearResources();
|
||||
}
|
||||
// Clear remaining scheduled invokers, possibly left over as paused tasks.
|
||||
clearResourcesInScheduledInvokers();
|
||||
this.scheduledInvokers.clear();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
@@ -910,6 +908,15 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear resources in scheduled invokers, even in case of paused tasks.
|
||||
*/
|
||||
private void clearResourcesInScheduledInvokers() {
|
||||
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) {
|
||||
scheduledInvoker.clearResources();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a shared JMS Connection depending on the "cacheLevel" setting.
|
||||
* @see #setCacheLevel
|
||||
@@ -1431,6 +1438,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe
|
||||
if (activeInvokerCount == 0) {
|
||||
if (!isRunning()) {
|
||||
// Proactively release shared Connection when stopped.
|
||||
clearResourcesInScheduledInvokers();
|
||||
releaseSharedConnection();
|
||||
}
|
||||
if (stopCallback != null) {
|
||||
|
||||
@@ -102,12 +102,6 @@ public abstract class JmsAccessor implements InitializingBean {
|
||||
/**
|
||||
* Set the transaction mode that is used when creating a JMS {@link Session}.
|
||||
* Default is "false".
|
||||
* <p>Note that within a JTA transaction, the parameters passed to
|
||||
* {@code create(Queue/Topic)Session(boolean transacted, int acknowledgeMode)}
|
||||
* method are not taken into account. Depending on the Jakarta EE transaction context,
|
||||
* the container makes its own decisions on these values. Analogously, these
|
||||
* parameters are not taken into account within a locally managed transaction
|
||||
* either, since the accessor operates on an existing JMS Session in this case.
|
||||
* <p>Setting this flag to "true" will use a short local JMS transaction
|
||||
* when running outside a managed transaction, and a synchronized local
|
||||
* JMS transaction in case of a managed transaction (other than an XA
|
||||
@@ -115,6 +109,11 @@ public abstract class JmsAccessor implements InitializingBean {
|
||||
* transaction being managed alongside the main transaction (which might
|
||||
* be a native JDBC transaction), with the JMS transaction committing
|
||||
* right after the main transaction.
|
||||
* <p>Note that this flag is meant to remain at its default value "false" for
|
||||
* participating in a global JTA/XA transaction in a Jakarta EE environment.
|
||||
* While the server may leniently ignore local Session-level transaction
|
||||
* management in such a scenario, it may also throw unexpected exceptions
|
||||
* on commit/rollback in case of this Session-level flag being set to "true".
|
||||
* @see jakarta.jms.Connection#createSession(boolean, int)
|
||||
*/
|
||||
public void setSessionTransacted(boolean sessionTransacted) {
|
||||
|
||||
+2
-2
@@ -449,13 +449,13 @@ public class StompHeaders implements MultiValueMap<String, String>, Serializable
|
||||
*/
|
||||
@Override
|
||||
public void add(String headerName, @Nullable String headerValue) {
|
||||
List<String> headerValues = this.headers.computeIfAbsent(headerName, k -> new ArrayList<>(1));
|
||||
List<String> headerValues = this.headers.computeIfAbsent(headerName, key -> new ArrayList<>(1));
|
||||
headerValues.add(headerValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(String headerName, List<? extends String> headerValues) {
|
||||
List<String> currentValues = this.headers.computeIfAbsent(headerName, k -> new ArrayList<>(1));
|
||||
List<String> currentValues = this.headers.computeIfAbsent(headerName, key -> new ArrayList<>(1));
|
||||
currentValues.addAll(headerValues);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
nativeHeaders = new LinkedMultiValueMap<>(3);
|
||||
setHeader(NATIVE_HEADERS, nativeHeaders);
|
||||
}
|
||||
List<String> values = nativeHeaders.computeIfAbsent(name, k -> new ArrayList<>(1));
|
||||
List<String> values = nativeHeaders.computeIfAbsent(name, key -> new ArrayList<>(1));
|
||||
values.add(value);
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,10 +18,10 @@ package org.springframework.transaction.reactive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -196,7 +196,7 @@ public class TransactionSynchronizationManager {
|
||||
if (isSynchronizationActive()) {
|
||||
throw new IllegalStateException("Cannot activate transaction synchronization - already active");
|
||||
}
|
||||
this.transactionContext.setSynchronizations(new LinkedHashSet<>());
|
||||
this.transactionContext.setSynchronizations(new CopyOnWriteArraySet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -155,7 +155,7 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
|
||||
headers.forEach((headerName, headerValues) -> {
|
||||
if (!DISALLOWED_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
|
||||
for (String headerValue : headerValues) {
|
||||
builder.header(headerName, headerValue);
|
||||
builder.header(headerName, (headerValue != null) ? headerValue : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ class OkHttp3ClientHttpRequest extends AbstractStreamingClientHttpRequest {
|
||||
builder.method(this.method.name(), requestBody);
|
||||
headers.forEach((headerName, headerValues) -> {
|
||||
for (String headerValue : headerValues) {
|
||||
builder.addHeader(headerName, headerValue);
|
||||
builder.addHeader(headerName, (headerValue != null) ? headerValue : "");
|
||||
}
|
||||
});
|
||||
Request request = builder.build();
|
||||
|
||||
@@ -209,7 +209,7 @@ final class SubscriberInputStream<T> extends InputStream implements Flow.Subscri
|
||||
}
|
||||
|
||||
private void resume() {
|
||||
if (this.parkedThread != READY) {
|
||||
if (this.parkedThread.get() != READY) {
|
||||
Object old = this.parkedThread.getAndSet(READY);
|
||||
if (old != READY) {
|
||||
LockSupport.unpark((Thread) old);
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ public abstract class Jackson2CodecSupport {
|
||||
this.objectMapperRegistrations = new LinkedHashMap<>();
|
||||
}
|
||||
Map<MimeType, ObjectMapper> registrations =
|
||||
this.objectMapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>());
|
||||
this.objectMapperRegistrations.computeIfAbsent(clazz, key -> new LinkedHashMap<>());
|
||||
registrar.accept(registrations);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
this.objectMapperRegistrations = new LinkedHashMap<>();
|
||||
}
|
||||
Map<MediaType, ObjectMapper> registrations =
|
||||
this.objectMapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>());
|
||||
this.objectMapperRegistrations.computeIfAbsent(clazz, key -> new LinkedHashMap<>());
|
||||
registrar.accept(registrations);
|
||||
}
|
||||
|
||||
|
||||
+11
-12
@@ -50,7 +50,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpRequest} implementation that is based on a {@link HttpServletRequest}.
|
||||
@@ -76,7 +75,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
@Nullable
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
|
||||
@Nullable
|
||||
private ServerHttpAsyncRequestControl asyncRequestControl;
|
||||
|
||||
@@ -134,15 +132,15 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
if (hasQuery) {
|
||||
String requestURL = servletRequest.getRequestURL().toString();
|
||||
try {
|
||||
// Maybe malformed query, try to parse and encode it
|
||||
query = UriComponentsBuilder.fromUriString("?" + query).build().toUri().getRawQuery();
|
||||
return new URI(servletRequest.getRequestURL().toString() + "?" + query);
|
||||
// Maybe malformed query, try to encode it
|
||||
return new URI(requestURL + "?" + encodeQuery(query));
|
||||
}
|
||||
catch (URISyntaxException ex2) {
|
||||
try {
|
||||
// Try leaving it out
|
||||
return new URI(servletRequest.getRequestURL().toString());
|
||||
return new URI(requestURL);
|
||||
}
|
||||
catch (URISyntaxException ex3) {
|
||||
// ignore
|
||||
@@ -154,6 +152,11 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
private static String encodeQuery(String query) throws URISyntaxException {
|
||||
// Avoid package cycle with web.utils
|
||||
return new URI(null, null, "", query, null).getRawQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
@@ -209,6 +212,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Principal getPrincipal() {
|
||||
return this.servletRequest.getUserPrincipal();
|
||||
}
|
||||
@@ -329,7 +333,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
@Nullable
|
||||
private transient Set<Entry<String, Object>> entrySet;
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
int size = 0;
|
||||
@@ -388,7 +391,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
public Iterator<String> iterator() {
|
||||
return servletRequest.getAttributeNames().asIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return AttributesMap.this.size();
|
||||
@@ -412,7 +414,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
public boolean hasNext() {
|
||||
return e.hasMoreElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
String name = e.nextElement();
|
||||
@@ -420,7 +421,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return AttributesMap.this.size();
|
||||
@@ -444,7 +444,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
public boolean hasNext() {
|
||||
return e.hasMoreElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<String, Object> next() {
|
||||
String name = e.nextElement();
|
||||
@@ -453,7 +452,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return AttributesMap.this.size();
|
||||
@@ -464,4 +462,5 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
return entrySet;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-7
@@ -51,7 +51,6 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Adapt {@link ServerHttpRequest} to the Servlet {@link HttpServletRequest}.
|
||||
@@ -140,15 +139,15 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
if (hasQuery) {
|
||||
String requestURL = servletRequest.getRequestURL().toString();
|
||||
try {
|
||||
// Maybe malformed query, try to parse and encode it
|
||||
query = UriComponentsBuilder.fromUriString("?" + query).build().toUri().getRawQuery();
|
||||
return new URI(servletRequest.getRequestURL().toString() + "?" + query);
|
||||
// Maybe malformed query, try to encode it
|
||||
return new URI(requestURL + "?" + encodeQuery(query));
|
||||
}
|
||||
catch (URISyntaxException ex2) {
|
||||
try {
|
||||
// Try leaving it out
|
||||
return new URI(servletRequest.getRequestURL().toString());
|
||||
return new URI(requestURL);
|
||||
}
|
||||
catch (URISyntaxException ex3) {
|
||||
// ignore
|
||||
@@ -160,7 +159,12 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
private static String encodeQuery(String query) throws URISyntaxException {
|
||||
// Avoid package cycle with web.utils
|
||||
return new URI(null, null, "", query, null).getRawQuery();
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway") // Dataflow analysis limitation
|
||||
private static MultiValueMap<String, String> initHeaders(
|
||||
MultiValueMap<String, String> headerValues, HttpServletRequest request) {
|
||||
|
||||
@@ -385,7 +389,6 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
RequestBodyPublisher.this.onError(throwable);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -961,6 +961,29 @@ class RestClientIntegrationTests {
|
||||
expectRequest(request -> assertThat(request.getHeader("foo")).isEqualTo("bar"));
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedRestClientTest
|
||||
void sendNullHeaderValue(ClientHttpRequestFactory requestFactory) throws IOException {
|
||||
startServer(requestFactory);
|
||||
|
||||
prepareResponse(builder -> builder
|
||||
.setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||
|
||||
String result = this.restClient.get()
|
||||
.uri("/greeting")
|
||||
.httpRequest(request -> request.getHeaders().add("X-Test-Header", null))
|
||||
.retrieve()
|
||||
.body(String.class);
|
||||
|
||||
assertThat(result).isEqualTo("Hello Spring!");
|
||||
|
||||
expectRequestCount(1);
|
||||
expectRequest(request -> {
|
||||
assertThat(request.getHeaders().get("X-Test-Header")).isNullOrEmpty();
|
||||
assertThat(request.getPath()).isEqualTo("/greeting");
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedRestClientTest
|
||||
void defaultRequest(ClientHttpRequestFactory requestFactory) {
|
||||
startServer(requestFactory);
|
||||
|
||||
@@ -82,7 +82,7 @@ public class BindingContext {
|
||||
*/
|
||||
public BindingContext(@Nullable WebBindingInitializer initializer, ReactiveAdapterRegistry registry) {
|
||||
this.initializer = initializer;
|
||||
this.reactiveAdapterRegistry = new ReactiveAdapterRegistry();
|
||||
this.reactiveAdapterRegistry = registry;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -355,7 +355,7 @@ class ControllerMethodResolver {
|
||||
|
||||
this.initBinderMethodCache
|
||||
.computeIfAbsent(handlerType,
|
||||
clazz -> MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS))
|
||||
key -> MethodIntrospector.selectMethods(key, INIT_BINDER_METHODS))
|
||||
.forEach(method -> {
|
||||
Object bean = handlerMethod.getBean();
|
||||
result.add(getInitBinderMethod(bean, method));
|
||||
@@ -388,7 +388,7 @@ class ControllerMethodResolver {
|
||||
|
||||
this.modelAttributeMethodCache
|
||||
.computeIfAbsent(handlerType,
|
||||
clazz -> MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS))
|
||||
key -> MethodIntrospector.selectMethods(key, MODEL_ATTRIBUTE_METHODS))
|
||||
.forEach(method -> {
|
||||
Object bean = handlerMethod.getBean();
|
||||
result.add(createAttributeMethod(bean, method));
|
||||
|
||||
+13
-2
@@ -61,9 +61,20 @@ public abstract class NettyWebSocketSessionSupport<T> extends AbstractWebSocketS
|
||||
messageTypes.put(PongWebSocketFrame.class, WebSocketMessage.Type.PONG);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor that uses the hashcode of the delegate as the session id.
|
||||
*/
|
||||
protected NettyWebSocketSessionSupport(T delegate, HandshakeInfo info, NettyDataBufferFactory factory) {
|
||||
super(delegate, ObjectUtils.getIdentityHexString(delegate), info, factory);
|
||||
this(delegate, ObjectUtils.getIdentityHexString(delegate), info, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #NettyWebSocketSessionSupport(Object, HandshakeInfo, NettyDataBufferFactory)}
|
||||
* with a given WebSocket session id.
|
||||
* @since 6.2.15
|
||||
*/
|
||||
protected NettyWebSocketSessionSupport(T delegate, String id, HandshakeInfo info, NettyDataBufferFactory factory) {
|
||||
super(delegate, id, info, factory);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-3
@@ -64,14 +64,18 @@ public class ReactorNettyWebSocketSession
|
||||
* Constructor with an additional maxFramePayloadLength argument.
|
||||
* @since 5.1
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public ReactorNettyWebSocketSession(WebsocketInbound inbound, WebsocketOutbound outbound,
|
||||
HandshakeInfo info, NettyDataBufferFactory bufferFactory,
|
||||
int maxFramePayloadLength) {
|
||||
|
||||
super(new WebSocketConnection(inbound, outbound), info, bufferFactory);
|
||||
super(new WebSocketConnection(inbound, outbound), getChannelId(inbound).asLongText(), info, bufferFactory);
|
||||
this.maxFramePayloadLength = maxFramePayloadLength;
|
||||
this.channelId = ((ChannelOperations) inbound).channel().id();
|
||||
this.channelId = getChannelId(inbound);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static ChannelId getChannelId(WebsocketInbound inbound) {
|
||||
return ((ChannelOperations) inbound).channel().id();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -126,7 +126,8 @@ public class UrlFilenameViewController extends AbstractUrlViewController {
|
||||
* @see #postProcessViewName
|
||||
*/
|
||||
protected String getViewNameForUrlPath(String uri) {
|
||||
return this.viewNameCache.computeIfAbsent(uri, u -> postProcessViewName(extractViewNameFromUrlPath(u)));
|
||||
return this.viewNameCache.computeIfAbsent(uri,
|
||||
key -> postProcessViewName(extractViewNameFromUrlPath(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -484,7 +484,7 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
|
||||
/**
|
||||
* Customize the handling of {@link TypeMismatchException}.
|
||||
* <p>By default this method creates a {@link ProblemDetail} with the status
|
||||
* <p>By default, this method creates a {@link ProblemDetail} with the status
|
||||
* and a short detail message, and also looks up an override for the detail
|
||||
* via {@link MessageSource}, before delegating to
|
||||
* {@link #handleExceptionInternal}.
|
||||
@@ -499,7 +499,8 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa
|
||||
protected ResponseEntity<Object> handleTypeMismatch(
|
||||
TypeMismatchException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
|
||||
|
||||
Object[] args = {ex.getPropertyName(), ex.getValue()};
|
||||
Object[] args = {ex.getPropertyName(), ex.getValue(),
|
||||
(ex.getRequiredType() != null ? ex.getRequiredType().getSimpleName() : "")};
|
||||
String defaultDetail = "Failed to convert '" + args[0] + "' with value: '" + args[1] + "'";
|
||||
String messageCode = ErrorResponse.getDefaultDetailMessageCode(TypeMismatchException.class, null);
|
||||
ProblemDetail body = createProblemDetail(ex, status, defaultDetail, messageCode, args, request);
|
||||
|
||||
+8
-1
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.view;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -59,7 +60,7 @@ final class DefaultFragmentsRendering implements FragmentsRendering {
|
||||
|
||||
this.status = status;
|
||||
this.headers = headers;
|
||||
this.modelAndViews = new ArrayList<>(fragments);
|
||||
this.modelAndViews = Collections.unmodifiableCollection(new ArrayList<>(fragments));
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +80,12 @@ final class DefaultFragmentsRendering implements FragmentsRendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ModelAndView> fragments() {
|
||||
return this.modelAndViews;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resolveNestedViews(ViewResolver resolver, Locale locale) throws Exception {
|
||||
for (ModelAndView mv : this.modelAndViews) {
|
||||
|
||||
+6
@@ -53,6 +53,12 @@ public interface FragmentsRendering extends SmartView {
|
||||
*/
|
||||
HttpHeaders headers();
|
||||
|
||||
/**
|
||||
* Return the {@code ModelAndView} for each fragment.
|
||||
* @since 6.2.15
|
||||
*/
|
||||
Collection<ModelAndView> fragments();
|
||||
|
||||
|
||||
/**
|
||||
* Create a builder with one HTML fragment, also inheriting attributes from
|
||||
|
||||
+2
-2
@@ -245,7 +245,7 @@ class ResponseEntityExceptionHandlerTests {
|
||||
StaticMessageSource messageSource = new StaticMessageSource();
|
||||
messageSource.addMessage(
|
||||
ErrorResponse.getDefaultDetailMessageCode(TypeMismatchException.class, null), locale,
|
||||
"Failed to set {0} to value: {1}");
|
||||
"Failed to set {0} to value: {1} for type {2}");
|
||||
|
||||
this.exceptionHandler.setMessageSource(messageSource);
|
||||
|
||||
@@ -253,7 +253,7 @@ class ResponseEntityExceptionHandlerTests {
|
||||
new TypeMismatchException(new PropertyChangeEvent(this, "name", "John", "James"), String.class));
|
||||
|
||||
ProblemDetail body = (ProblemDetail) entity.getBody();
|
||||
assertThat(body.getDetail()).isEqualTo("Failed to set name to value: James");
|
||||
assertThat(body.getDetail()).isEqualTo("Failed to set name to value: James for type String");
|
||||
}
|
||||
finally {
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
|
||||
+9
-15
@@ -55,8 +55,7 @@ public class SpringConfigurator extends Configurator {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpringConfigurator.class);
|
||||
|
||||
private static final Map<String, Map<Class<?>, String>> cache =
|
||||
new ConcurrentHashMap<>();
|
||||
private static final Map<String, Map<Class<?>, String>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -101,24 +100,19 @@ public class SpringConfigurator extends Configurator {
|
||||
@Nullable
|
||||
private String getBeanNameByType(WebApplicationContext wac, Class<?> endpointClass) {
|
||||
String wacId = wac.getId();
|
||||
Map<Class<?>, String> beanNamesByType = cache.computeIfAbsent(wacId, key -> new ConcurrentHashMap<>());
|
||||
|
||||
Map<Class<?>, String> beanNamesByType = cache.computeIfAbsent(wacId, k -> new ConcurrentHashMap<>());
|
||||
|
||||
if (!beanNamesByType.containsKey(endpointClass)) {
|
||||
String beanName = beanNamesByType.get(endpointClass);
|
||||
if (beanName == null) {
|
||||
String[] names = wac.getBeanNamesForType(endpointClass);
|
||||
if (names.length == 1) {
|
||||
beanNamesByType.put(endpointClass, names[0]);
|
||||
}
|
||||
else {
|
||||
beanNamesByType.put(endpointClass, NO_VALUE);
|
||||
if (names.length > 1) {
|
||||
throw new IllegalStateException("Found multiple @ServerEndpoint's of type [" +
|
||||
endpointClass.getName() + "]: bean names " + Arrays.toString(names));
|
||||
}
|
||||
beanName = (names.length == 1 ? names[0] : NO_VALUE);
|
||||
beanNamesByType.put(endpointClass, beanName);
|
||||
if (names.length > 1) {
|
||||
throw new IllegalStateException("Found multiple @ServerEndpoint's of type [" +
|
||||
endpointClass.getName() + "]: bean names " + Arrays.toString(names));
|
||||
}
|
||||
}
|
||||
|
||||
String beanName = beanNamesByType.get(endpointClass);
|
||||
return (NO_VALUE.equals(beanName) ? null : beanName);
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -218,8 +218,8 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
|
||||
if (HttpMethod.GET != httpMethod && !CONNECT_METHOD.equals(httpMethod)) {
|
||||
response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
|
||||
response.getHeaders().setAllow(Set.of(HttpMethod.GET, CONNECT_METHOD));
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("Handshake failed due to unexpected HTTP method: " + httpMethod);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Handshake failed due to unexpected HTTP method: " + httpMethod);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -235,8 +235,8 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
|
||||
}
|
||||
String key = headers.getSecWebSocketKey();
|
||||
if (key == null) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("Missing \"Sec-WebSocket-Key\" header");
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Missing \"Sec-WebSocket-Key\" header");
|
||||
}
|
||||
response.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
return false;
|
||||
@@ -270,8 +270,8 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
|
||||
}
|
||||
|
||||
protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error(LogFormatUtils.formatValue(
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(LogFormatUtils.formatValue(
|
||||
"Handshake failed due to invalid Upgrade header: " + request.getHeaders().getUpgrade(), -1, true));
|
||||
}
|
||||
response.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
@@ -279,8 +279,8 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
|
||||
}
|
||||
|
||||
protected void handleInvalidConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error(LogFormatUtils.formatValue(
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(LogFormatUtils.formatValue(
|
||||
"Handshake failed due to invalid Connection header" + request.getHeaders().getConnection(), -1, true));
|
||||
}
|
||||
response.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
@@ -303,9 +303,9 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
|
||||
}
|
||||
|
||||
protected void handleWebSocketVersionNotSupported(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
String version = request.getHeaders().getFirst(WebSocketHttpHeaders.SEC_WEBSOCKET_VERSION);
|
||||
logger.error(LogFormatUtils.formatValue(
|
||||
logger.debug(LogFormatUtils.formatValue(
|
||||
"Handshake failed due to unsupported WebSocket version: " + version +
|
||||
". Supported versions: " + Arrays.toString(getSupportedVersions()), -1, true));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user