mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1526ceec1d |
@@ -27,7 +27,7 @@ jobs:
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-7.1.x'
|
||||
build-name: 'spring-framework-7.0.x'
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
|
||||
@@ -2,8 +2,8 @@ name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.1.0-M[1-9]
|
||||
- v7.1.0-RC[1-9]
|
||||
- v7.0.0-M[1-9]
|
||||
- v7.0.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.1.[0-9]+
|
||||
- v7.0.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [ '6.2.x', '7.0.x', 'main' ]
|
||||
branch: [ '6.2.x', 'main' ]
|
||||
steps:
|
||||
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@5a57bcc6a0da2a1474136cf29571b277850432bc
|
||||
name: Update
|
||||
|
||||
@@ -9,7 +9,6 @@ ivy-cache
|
||||
argfile*
|
||||
activemq-data/
|
||||
classes/
|
||||
.cursor/
|
||||
|
||||
# Log files
|
||||
jxl.log
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# Contributing to the Spring Framework
|
||||
# Contributing to the Spring Framework
|
||||
|
||||
First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
|
||||
+3
-5
@@ -61,9 +61,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
testImplementation("org.junit.platform:junit-platform-suite")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("io.mockk:mockk") {
|
||||
exclude group: 'junit', module: 'junit'
|
||||
}
|
||||
testImplementation("io.mockk:mockk")
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
@@ -74,11 +72,11 @@ configure([rootProject] + javaProjects) { project ->
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/5.6/httpclient5/apidocs/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.1.0/api/",
|
||||
"https://docs.junit.org/6.0.3/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
|
||||
@@ -20,7 +20,7 @@ ext {
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.2.0"
|
||||
implementation "org.jetbrains.dokka:dokka-gradle-plugin:2.1.0"
|
||||
implementation "com.tngtech.archunit:archunit:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.6.2"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("13.4.2");
|
||||
checkstyle.setToolVersion("13.3.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -15,8 +15,8 @@ content:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
# 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: include all releases from 6.2.0 to 9.*.*.
|
||||
tags: ['v6.2.+({0..9})', 'v{7..9}.+({0..9}).+({0..9})?(-{RC,M}*)']
|
||||
# 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:
|
||||
extensions:
|
||||
@@ -38,4 +38,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.26/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
|
||||
@@ -20,10 +20,6 @@ antora {
|
||||
]
|
||||
}
|
||||
|
||||
node {
|
||||
version = '24.15.0'
|
||||
}
|
||||
|
||||
tasks.named("generateAntoraYml") {
|
||||
asciidocAttributes = project.provider( {
|
||||
return ["spring-version": project.version ]
|
||||
|
||||
@@ -198,7 +198,6 @@
|
||||
*** xref:web/webmvc/mvc-uri-building.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-async.adoc[]
|
||||
*** xref:web/webmvc/mvc-range.adoc[]
|
||||
*** xref:web/webmvc/mvc-data-binding.adoc[]
|
||||
*** xref:web/webmvc-cors.adoc[]
|
||||
*** xref:web/webmvc-versioning.adoc[]
|
||||
*** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[]
|
||||
@@ -296,7 +295,6 @@
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
*** xref:web/webflux/uri-building.adoc[]
|
||||
*** xref:web/webflux/range.adoc[]
|
||||
*** xref:web/webflux/data-binding.adoc[]
|
||||
*** xref:web/webflux-cors.adoc[]
|
||||
*** xref:web/webflux-versioning.adoc[]
|
||||
*** xref:web/webflux/ann-rest-exceptions.adoc[]
|
||||
|
||||
@@ -74,11 +74,6 @@ expressions used in XML bean definitions, `@Value`, etc.
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
| `spring.expression.maxOperations`
|
||||
| The default maximum number of operations permitted during
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expression evaluation.
|
||||
|
||||
| `spring.getenv.ignore`
|
||||
| Instructs Spring to ignore operating system environment variables if a Spring
|
||||
`Environment` property -- for example, a placeholder in a configuration String -- isn't
|
||||
@@ -143,11 +138,6 @@ xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context P
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.extension.context.scope`
|
||||
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
|
||||
class hierarchies. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
|
||||
@@ -378,7 +378,7 @@ The container also supports creating a bean with {spring-framework-api}++/beans/
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. Bypassing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
+1
-1
@@ -274,7 +274,7 @@ Kotlin::
|
||||
Next, you can provide the information for the candidate bean definitions. You can add
|
||||
`<qualifier/>` tags as sub-elements of the `<bean/>` tag and then specify the `type` and
|
||||
`value` to match your custom qualifier annotations. The type is matched against the
|
||||
fully-qualified class name of the annotation. Alternatively, as a convenience if no risk of
|
||||
fully-qualified class name of the annotation. Alternately, as a convenience if no risk of
|
||||
conflicting names exists, you can use the short class name. The following example
|
||||
demonstrates both approaches:
|
||||
|
||||
|
||||
@@ -575,7 +575,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honor that and will not register the method to listen to events.
|
||||
NOTE: Do not define such beans to be lazy as the `ApplicationContext` will honour that and will not register the method to listen to events.
|
||||
|
||||
The method signature once again declares the event type to which it listens,
|
||||
but, this time, with a flexible name and without implementing a specific listener interface.
|
||||
|
||||
-13
@@ -610,19 +610,6 @@ Kotlin::
|
||||
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
|
||||
javadoc for more detail.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `@Conditional` annotation declared on an enclosing `@Configuration` class is only
|
||||
applied to the registration of a nested `@Configuration` class if the nested class is
|
||||
reached through the parser's recursion from its enclosing class, or via `@Import`. If a
|
||||
nested class is discovered independently of its enclosing class — for example, via
|
||||
`@ComponentScan` or by directly registering it against the application context — it is
|
||||
processed using only its own `@Conditional` annotations. Thus, if you wish to ensure that
|
||||
the same `@Conditional` annotations apply in such scenarios, you must redeclare the
|
||||
relevant annotations on the nested class, or extract them into a composed annotation
|
||||
which you apply to both the enclosing class and the nested class.
|
||||
====
|
||||
|
||||
|
||||
[[beans-java-combining]]
|
||||
== Combining Java and XML Configuration
|
||||
|
||||
@@ -395,16 +395,6 @@ set a JVM system property or Spring property named `spring.context.expression.ma
|
||||
to the maximum expression length needed by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
Similarly, the number of operations performed during the evaluation of a SpEL expression
|
||||
cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If
|
||||
you create a `SpelExpressionParser` programmatically (the recommend approach), you can
|
||||
specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that
|
||||
you provide to the `SpelExpressionParser`. If you are not able to configure an explicit
|
||||
value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system
|
||||
property or Spring property named `spring.expression.maxOperations` to the maximum number
|
||||
of operations required by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
|
||||
[[expressions-spel-compilation]]
|
||||
== SpEL Compilation
|
||||
@@ -541,6 +531,7 @@ following kinds of expressions cannot be compiled.
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using `Optional` with the null-safe or Elvis operator
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
* Expressions using bean references
|
||||
|
||||
@@ -2,12 +2,4 @@
|
||||
= Language Reference
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Expression Language (SpEL) expressions are composed of a sequence of tokens such
|
||||
as literals, operators, method invocations, and so forth.
|
||||
|
||||
Whitespace can be used freely between tokens to format and improve the readability of
|
||||
expressions. Specifically, the `\s` (space), `\t` (tab), `\r` (carriage return), and `\n`
|
||||
(newline) characters are all valid separators between tokens. However, whitespace is
|
||||
ignored by the expression parser unless it is part of a string literal.
|
||||
|
||||
The following sections describe the features and syntax of SpEL.
|
||||
This section describes how the Spring Expression Language works.
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ is applicable for typical implementations of indexed structures.
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invocable via a `public` class or `public` interface for compilation to succeed.
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
|
||||
@@ -25,8 +25,7 @@ public void sendNotification() {
|
||||
|
||||
By default, the method invocation will be retried for any exception thrown: with at most
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts. If all attempts have failed and the retry policy has been exhausted,
|
||||
the last original exception from the target method will be propagated to the caller.
|
||||
between attempts.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -70,12 +69,12 @@ Or for 4 retry attempts and an exponential back-off strategy with a bit of jitte
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Retryable(
|
||||
includes = MessageDeliveryException.class,
|
||||
maxRetries = 4,
|
||||
delay = 100,
|
||||
jitter = 10,
|
||||
multiplier = 2,
|
||||
maxDelay = 1000)
|
||||
includes = MessageDeliveryException.class,
|
||||
maxRetries = 4,
|
||||
delay = 100,
|
||||
jitter = 10,
|
||||
multiplier = 2,
|
||||
maxDelay = 1000)
|
||||
public void sendNotification() {
|
||||
this.jmsClient.destination("notifications").send(...);
|
||||
}
|
||||
@@ -100,13 +99,6 @@ TIP: Several attributes in `@Retryable` have `String` variants that provide prop
|
||||
placeholder and SpEL support, as an alternative to the specifically typed annotation
|
||||
attributes used in the above examples.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
During `@Retryable` processing, Spring publishes a `MethodRetryEvent` for every exception
|
||||
coming out of the target method. This can be used to track/log all original exceptions
|
||||
whereas the caller of the `@Retryable` method will only ever see the last exception.
|
||||
====
|
||||
|
||||
|
||||
[[resilience-annotations-concurrencylimit]]
|
||||
== `@ConcurrencyLimit`
|
||||
@@ -182,7 +174,7 @@ configured {spring-framework-api}/core/retry/RetryPolicy.html[`RetryPolicy`].
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(); // <1>
|
||||
|
||||
retryTemplate.invoke(
|
||||
retryTemplate.execute(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Implicitly uses `RetryPolicy.withDefaults()`.
|
||||
@@ -208,7 +200,7 @@ If you only need to customize the number of retry attempts, you can use the
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
|
||||
|
||||
retryTemplate.invoke(
|
||||
retryTemplate.execute(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Explicitly uses `RetryPolicy.withMaxRetries(4)`.
|
||||
@@ -226,7 +218,7 @@ matched against an exception thrown by a failed operation as well as nested caus
|
||||
|
||||
var retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
retryTemplate.invoke(
|
||||
retryTemplate.execute(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
<1> Specify one or more exception types to include.
|
||||
@@ -259,101 +251,21 @@ and an exponential back-off strategy with a bit of jitter.
|
||||
|
||||
var retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
retryTemplate.invoke(
|
||||
retryTemplate.execute(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
|
||||
with a `RetryTemplate` to react to events published during key retry phases (before a
|
||||
retry attempt, after a retry attempt, etc.), and you can compose multiple listeners via a
|
||||
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
|
||||
====
|
||||
|
||||
Although the factory methods and builder API for `RetryPolicy` cover most common
|
||||
configuration scenarios, you can implement a custom `RetryPolicy` for complete control
|
||||
over the types of exceptions that should trigger a retry as well as the
|
||||
{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that
|
||||
you can also configure a customized `BackOff` strategy via the `backOff()` method in
|
||||
the `RetryPolicy.Builder`.
|
||||
====
|
||||
|
||||
Note that the examples above apply a pattern similar to `@Retryable` method invocations
|
||||
where the last original exception will be propagated to the caller, using the `invoke`
|
||||
variants on `RetryTemplate` which are available with and without a return value.
|
||||
The callback may throw unchecked exceptions, the last one of which is exposed for
|
||||
direct handling on the caller side:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
var result = retryTemplate.invoke(() -> {
|
||||
jmsClient.destination("notifications").send(...);
|
||||
return "result";
|
||||
});
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
`RetryTemplate` instances are very light and can be created on the fly,
|
||||
potentially with a specific retry policy to use for a given invocation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
new RetryTemplate(RetryPolicy.withMaxRetries(4)).invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// coming out of the original JmsClient send method
|
||||
}
|
||||
----
|
||||
|
||||
For deeper interaction, you may use RetryTemplate's `execute` method. The caller will
|
||||
have to handle the checked `RetryException` thrown by `RetryTemplate`, exposing the
|
||||
outcome of all attempts:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
try {
|
||||
var result = retryTemplate.execute(() -> {
|
||||
jmsClient.destination("notifications").send(...);
|
||||
return "result";
|
||||
});
|
||||
}
|
||||
catch (RetryException ex) {
|
||||
// ex.getExceptions() / ex.getLastException() ...
|
||||
}
|
||||
----
|
||||
|
||||
A {spring-framework-api}/core/retry/RetryListener.html[`RetryListener`] can be registered
|
||||
with a `RetryTemplate` to react to key retry steps (before or after a retry attempt etc.)
|
||||
or simply to every invocation attempt, being able to track all exceptions coming out of
|
||||
the callback and all retry outcomes (exhaustion, interruption, timeout). This is
|
||||
particularly useful when using `invoke` where no retry state other than the last
|
||||
original exception is exposed otherwise:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryListener(new RetryListener() {
|
||||
@Override
|
||||
public void onRetryableExecution(RetryPolicy retryPolicy, Retryable<?> retryable, RetryState retryState) {
|
||||
...
|
||||
}
|
||||
});
|
||||
|
||||
retryTemplate.invoke(
|
||||
() -> jmsClient.destination("notifications").send(...));
|
||||
----
|
||||
|
||||
You can also compose multiple listeners via a
|
||||
{spring-framework-api}/core/retry/support/CompositeRetryListener.html[`CompositeRetryListener`].
|
||||
{spring-framework-api}/util/backoff/BackOff.html[`BackOff`] strategy to use. Note that you
|
||||
can also configure a customized `BackOff` strategy via the `backOff()` method in the
|
||||
`RetryPolicy.Builder`.
|
||||
|
||||
@@ -347,10 +347,10 @@ recognized and used as the `PropertyEditor` for `Something`-typed properties.
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com
|
||||
└── example
|
||||
└── things
|
||||
├── *Something*
|
||||
└── *SomethingEditor* // the PropertyEditor for the Something class
|
||||
chank
|
||||
pop
|
||||
Something
|
||||
SomethingEditor // the PropertyEditor for the Something class
|
||||
----
|
||||
|
||||
Note that you can also use the standard `BeanInfo` JavaBeans mechanism here as well
|
||||
@@ -362,10 +362,10 @@ following example uses the `BeanInfo` mechanism to explicitly register one or mo
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
com
|
||||
└── example
|
||||
└── things
|
||||
├── *Something*
|
||||
└── *SomethingBeanInfo* // the BeanInfo for the Something class
|
||||
chank
|
||||
pop
|
||||
Something
|
||||
SomethingBeanInfo // the BeanInfo for the Something class
|
||||
----
|
||||
|
||||
The following Java source code for the referenced `SomethingBeanInfo` class
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ where all the underlying resources have to participate in the service-level tran
|
||||
|
||||
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
|
||||
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
|
||||
Consider switching the `validateExistingTransaction` flag to `true` on your transaction
|
||||
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
|
||||
manager if you want isolation level declarations to be rejected when participating in
|
||||
an existing transaction with a different isolation level. This non-lenient mode also
|
||||
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
[[observability]]
|
||||
= Observability Support
|
||||
|
||||
With https://docs.micrometer.io/micrometer/reference/concepts.html[Micrometer], developers can instrument libraries and applications for metrics (timers, gauges, counters)
|
||||
that collect statistics about their runtime behavior. Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
https://docs.micrometer.io/tracing/reference/[Micrometer can also produce traces], giving you a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Micrometer defines an {micrometer-docs}/observation.html[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Each observation will produce:
|
||||
|
||||
* https://docs.micrometer.io/micrometer/reference/observation/components.html#micrometer-observation-default-meter-handler[several metrics - a timer, a long task timer and many counters]
|
||||
* a https://docs.micrometer.io/tracing/reference/glossary.html[span for the current trace]
|
||||
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
You can learn more about {spring-boot-docs-ref}/actuator/observability.html[configuring the observability infrastructure in Spring Boot].
|
||||
|
||||
@@ -402,27 +402,6 @@ To serialize only a subset of the object properties, you can specify a {baeldung
|
||||
.toBodilessEntity();
|
||||
----
|
||||
|
||||
==== URL encoded Forms
|
||||
|
||||
URL encoded forms, using the `"application/x-www-form-urlencoded"` media type, are useful for sending String key/values over the wire.
|
||||
This is supported by the `FormHttpMessageConverter`, if the application uses a `MultiValueMap<String, String>` as source instance
|
||||
or a target type.
|
||||
|
||||
For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
||||
form.add("project", "Spring Framework");
|
||||
form.add("module", "spring-web");
|
||||
ResponseEntity<Void> response = this.restClient.post()
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(form)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
----
|
||||
|
||||
|
||||
==== Multipart
|
||||
|
||||
To send multipart data, you need to provide a `MultiValueMap<String, Object>` whose values may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for part content with headers.
|
||||
@@ -440,70 +419,18 @@ For example:
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
|
||||
ResponseEntity<Void> response = this.restClient.post()
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
.body(parts)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
// send using RestClient.post or RestTemplate.postForEntity
|
||||
----
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part.
|
||||
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource`, based on the file extension.
|
||||
If necessary, you can explicitly provide the `MediaType` with an `HttpEntity` wrapper.
|
||||
|
||||
The `Content-Type` is set to `multipart/form-data` by the `MultipartHttpMessageConverter`.
|
||||
As seen in the previous section, `MultiValueMap` types can also be used for URL encoded forms.
|
||||
It is preferable to explicitly set the media type in the `Content-Type` or `Accept` HTTP request headers to ensure that the expected
|
||||
message converter is used.
|
||||
|
||||
`RestClient` can also receive multipart responses.
|
||||
To decode a multipart response body, use a `ParameterizedTypeReference<MultiValueMap<String, Part>>`.
|
||||
The decoded map contains `Part` instances where `FormFieldPart` represents form field values
|
||||
and `FilePart` represents file parts with a `filename()` and a `transferTo()` method.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
MultiValueMap<String, Part> result = this.restClient.get()
|
||||
.uri("https://example.com/upload")
|
||||
.accept(MediaType.MULTIPART_FORM_DATA)
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<>() {});
|
||||
|
||||
Part field = result.getFirst("fieldPart");
|
||||
if (field instanceof FormFieldPart formField) {
|
||||
String fieldValue = formField.value();
|
||||
}
|
||||
Part file = result.getFirst("filePart");
|
||||
if (file instanceof FilePart filePart) {
|
||||
filePart.transferTo(Path.of("/tmp/" + filePart.filename()));
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
val result = this.restClient.get()
|
||||
.uri("https://example.com/upload")
|
||||
.accept(MediaType.MULTIPART_FORM_DATA)
|
||||
.retrieve()
|
||||
.body(object : ParameterizedTypeReference<MultiValueMap<String, Part>>() {})
|
||||
|
||||
val field = result?.getFirst("fieldPart")
|
||||
if (field is FormFieldPart) {
|
||||
val fieldValue = field.value()
|
||||
}
|
||||
val file = result?.getFirst("filePart")
|
||||
if (file is FilePart) {
|
||||
file.transferTo(Path.of("/tmp/" + file.filename()))
|
||||
}
|
||||
----
|
||||
======
|
||||
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set to `multipart/form-data` by the `FormHttpMessageConverter`.
|
||||
If the `MultiValueMap` has `String` values, the `Content-Type` defaults to `application/x-www-form-urlencoded`.
|
||||
If necessary the `Content-Type` may also be set explicitly.
|
||||
|
||||
[[rest-request-factories]]
|
||||
=== Client Request Factories
|
||||
@@ -1252,7 +1179,7 @@ built-in decorators to suppress 404 exceptions and return a `ResponseEntity` wit
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// For RestClient
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restClientAdapter)
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
|
||||
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
|
||||
.build();
|
||||
|
||||
|
||||
+13
-22
@@ -3,7 +3,7 @@
|
||||
|
||||
The following annotations are supported when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and the JUnit Jupiter testing framework:
|
||||
and JUnit Jupiter (that is, the programming model in JUnit):
|
||||
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
|
||||
@@ -30,39 +30,30 @@ developer wishes to switch to test-class scoped semantics — the `SpringExtensi
|
||||
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
|
||||
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
|
||||
|
||||
Alternatively, you can change the global default by setting the
|
||||
`spring.test.extension.context.scope` property to `test_class`. The property is resolved
|
||||
first via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism
|
||||
which also supports JVM system properties — for example,
|
||||
`-Dspring.test.extension.context.scope=test_class`. If the Spring property has not been
|
||||
set, the `SpringExtension` will attempt to resolve the property as a
|
||||
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter]
|
||||
as a fallback mechanism. If the property has not been set via either of those mechanisms,
|
||||
the `SpringExtension` will use a test-method scoped extension context by default. Note,
|
||||
however, that a `@SpringExtensionConfig` declaration always takes precedence over this
|
||||
property.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If a test class uses JUnit Jupiter's `@TestInstance(Lifecycle.PER_CLASS)` semantics, the
|
||||
`SpringExtension` will always use a test-class scoped `ExtensionContext`, and
|
||||
configuration via `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)` or
|
||||
the `spring.test.extension.context.scope` property will have no effect for that test
|
||||
class.
|
||||
If your top-level test class is configured to use JUnit Jupiter's
|
||||
`@TestInstance(Lifecycle.PER_CLASS)` semantics, the `SpringExtension` will always use a
|
||||
test-class scoped `ExtensionContext`, and there is no need to declare
|
||||
`@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This annotation is currently only applicable to `@Nested` test class hierarchies and
|
||||
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
|
||||
|
||||
Consequently, there is no need to declare this annotation on a test class that does not
|
||||
contain `@Nested` test classes.
|
||||
====
|
||||
|
||||
In addition,
|
||||
[NOTE]
|
||||
====
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
does not apply to this annotation. `@SpringExtensionConfig` will always be detected
|
||||
within a `@Nested` test class hierarchy, effectively disregarding any
|
||||
`@NestedTestConfiguration(OVERRIDE)` declarations.
|
||||
does not apply to this annotation.
|
||||
|
||||
`@SpringExtensionConfig` will always be detected within a `@Nested` test class hierarchy,
|
||||
effectively disregarding any `@NestedTestConfiguration(OVERRIDE)` declarations.
|
||||
====
|
||||
|
||||
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
|
||||
|
||||
-7
@@ -72,13 +72,6 @@ bean definition profiles programmatically by implementing a custom
|
||||
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc#testcontext-ctx-management-env-profiles-ActiveProfilesResolver[`ActiveProfilesResolver`]
|
||||
and registering it by using the `resolver` attribute of `@ActiveProfiles`.
|
||||
|
||||
NOTE: When `@ActiveProfiles` is declared on a test class, the `spring.profiles.active`
|
||||
property (whether configured as a JVM system property or environment variable) is not
|
||||
taken into account by the TestContext Framework when determining active profiles. If
|
||||
you need to allow `spring.profiles.active` to override the profiles configured via
|
||||
`@ActiveProfiles`, you can implement a custom `ActiveProfilesResolver` as described in
|
||||
xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles].
|
||||
|
||||
See xref:testing/testcontext-framework/ctx-management/env-profiles.adoc[Context Configuration with Environment Profiles],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-nested-test-configuration[`@Nested` test class configuration], and the
|
||||
{spring-framework-api}/test/context/ActiveProfiles.html[`@ActiveProfiles`] javadoc for
|
||||
|
||||
+6
-301
@@ -12,20 +12,18 @@ The annotations can be applied in the following ways.
|
||||
* On a non-static field in a test class or any of its superclasses.
|
||||
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
|
||||
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
|
||||
* On a parameter in the constructor for a test class.
|
||||
* At the type level on a test class or any superclass or implemented interface in the
|
||||
type hierarchy above the test class.
|
||||
* At the type level on an enclosing class for a `@Nested` test class or on any class or
|
||||
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
|
||||
class.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field or constructor parameter,
|
||||
the bean to mock or spy is inferred from the type of the annotated field or parameter. If
|
||||
multiple candidates exist in the `ApplicationContext`, a `@Qualifier` annotation can be
|
||||
declared on the field or parameter to help disambiguate. In the absence of a `@Qualifier`
|
||||
annotation, the name of the annotated field or parameter will be used as a _fallback
|
||||
qualifier_. Alternatively, you can explicitly specify a bean name to mock or spy by
|
||||
setting the `value` or `name` attribute in the annotation.
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
|
||||
is inferred from the type of the annotated field. If multiple candidates exist in the
|
||||
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
|
||||
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
|
||||
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
|
||||
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
|
||||
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
|
||||
@@ -142,21 +140,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoBean // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||
======
|
||||
|
||||
In the example above, we are creating a mock for `CustomService`. If more than one bean
|
||||
@@ -185,98 +168,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoBean("service") // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoBean` on a constructor parameter for a
|
||||
by-type lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoBean CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
|
||||
the constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoBean val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
|
||||
the constructor.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoBean` on a constructor parameter for a
|
||||
by-name lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoBean("service") CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock and inject it into the
|
||||
constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoBean("service") val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito mock and inject it into the
|
||||
constructor.
|
||||
======
|
||||
|
||||
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
|
||||
@@ -296,19 +187,6 @@ Java::
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` mocks by-type.
|
||||
<2> Register `PrintingService` mock by-name.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MockitoBean(types = [OrderService::class, UserService::class]) // <1>
|
||||
@MockitoBean(name = "ps1", types = [PrintingService::class]) // <2>
|
||||
annotation class SharedMocks
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` mocks by-type.
|
||||
<2> Register `PrintingService` mock by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedMocks` can be used on a test class.
|
||||
@@ -339,34 +217,6 @@ Java::
|
||||
----
|
||||
<1> Register common mocks via the custom `@SharedMocks` annotation.
|
||||
<2> Optionally inject mocks to _stub_ or _verify_ them.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@SharedMocks // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var orderService: OrderService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var userService: UserService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var ps1: PrintingService // <2>
|
||||
|
||||
// Inject other components that rely on the mocks.
|
||||
|
||||
@Test
|
||||
fun testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common mocks via the custom `@SharedMocks` annotation.
|
||||
<2> Optionally inject mocks to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The mocks can also be injected into `@Configuration` classes or other test-related
|
||||
@@ -396,21 +246,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoSpyBean // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||
======
|
||||
|
||||
In the example above, we are wrapping the bean with type `CustomService`. If more than
|
||||
@@ -436,95 +271,6 @@ Java::
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
@MockitoSpyBean("service") // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
|
||||
a by-type lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoSpyBean CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
|
||||
constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoSpyBean val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
|
||||
constructor.
|
||||
======
|
||||
|
||||
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
|
||||
a by-name lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
class BeanOverrideTests {
|
||||
|
||||
private final CustomService customService;
|
||||
|
||||
BeanOverrideTests(@MockitoSpyBean("service") CustomService customService) { // <1>
|
||||
this.customService = customService;
|
||||
}
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
class BeanOverrideTests(@MockitoSpyBean("service") val customService: CustomService) { // <1>
|
||||
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
|
||||
======
|
||||
|
||||
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
|
||||
@@ -544,19 +290,6 @@ Java::
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` spies by-type.
|
||||
<2> Register `PrintingService` spy by-name.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MockitoSpyBean(types = [OrderService::class, UserService::class]) // <1>
|
||||
@MockitoSpyBean(name = "ps1", types = [PrintingService::class]) // <2>
|
||||
annotation class SharedSpies
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` spies by-type.
|
||||
<2> Register `PrintingService` spy by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedSpies` can be used on a test class.
|
||||
@@ -587,34 +320,6 @@ Java::
|
||||
----
|
||||
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@SharedSpies // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var orderService: OrderService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var userService: UserService // <2>
|
||||
|
||||
@Autowired
|
||||
lateinit var ps1: PrintingService // <2>
|
||||
|
||||
// Inject other components that rely on the spies.
|
||||
|
||||
@Test
|
||||
fun testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The spies can also be injected into `@Configuration` classes or other test-related
|
||||
|
||||
-43
@@ -73,27 +73,6 @@ Java::
|
||||
----
|
||||
<1> Mark a field for overriding the bean with type `CustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// test case body...
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun customService(): CustomService { // <2>
|
||||
return MyFakeCustomService()
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for overriding the bean with type `CustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
======
|
||||
|
||||
In the example above, we are overriding the bean with type `CustomService`. If more than
|
||||
@@ -123,28 +102,6 @@ Java::
|
||||
<1> Mark a field for overriding the bean with name `service`, and specify that the
|
||||
factory method is named `createCustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean(name = "service", methodName = "createCustomService") // <1>
|
||||
lateinit var customService: CustomService
|
||||
|
||||
// test case body...
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createCustomService(): CustomService { // <2>
|
||||
return MyFakeCustomService()
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for overriding the bean with name `service`, and specify that the
|
||||
factory method is named `createCustomService`.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
======
|
||||
|
||||
[TIP]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
In the previous sections, we have seen how to use MockMvc in conjunction with the raw
|
||||
HtmlUnit APIs. In this section, we use additional abstractions within the Selenium
|
||||
https://www.selenium.dev/documentation/webdriver/[WebDriver] to make things even easier.
|
||||
https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
|
||||
|
||||
[[mockmvc-server-htmlunit-webdriver-why]]
|
||||
== Why WebDriver and MockMvc?
|
||||
@@ -12,8 +12,8 @@ We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver?
|
||||
Selenium WebDriver provides a very elegant API that lets us easily organize our code. To
|
||||
better show how it works, we explore an example in this section.
|
||||
|
||||
NOTE: Despite being a part of https://www.selenium.dev/documentation/[Selenium],
|
||||
WebDriver does not require a Selenium Server to run your tests.
|
||||
NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not
|
||||
require a Selenium Server to run your tests.
|
||||
|
||||
Suppose we need to ensure that a message is created properly. The tests involve finding
|
||||
the HTML form input elements, filling them out, and making various assertions.
|
||||
@@ -308,7 +308,7 @@ interested. These are of type `WebElement`. WebDriver's
|
||||
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
|
||||
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
|
||||
each `WebElement`. The
|
||||
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
|
||||
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
|
||||
method automatically resolves each `WebElement` by using the field name and looking it up
|
||||
by the `id` or `name` of the element within the HTML page.
|
||||
<3> We can use the
|
||||
@@ -352,7 +352,7 @@ interested. These are of type `WebElement`. WebDriver's
|
||||
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
|
||||
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
|
||||
each `WebElement`. The
|
||||
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
|
||||
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
|
||||
method automatically resolves each `WebElement` by using the field name and looking it up
|
||||
by the `id` or `name` of the element within the HTML page.
|
||||
<3> We can use the
|
||||
|
||||
@@ -142,9 +142,6 @@ provides two alternative ways to verify the response:
|
||||
1. xref:resttestclient-workflow[Built-in Assertions] extend the request workflow with a chain of expectations
|
||||
2. xref:resttestclient-assertj[AssertJ Integration] to verify the response via `assertThat()` statements
|
||||
|
||||
TIP: See the xref:integration/rest-clients.adoc#rest-message-conversion[HTTP Message Conversion]
|
||||
section for examples on how to prepare a request with any content, including form data and multipart data.
|
||||
|
||||
|
||||
|
||||
[[resttestclient.workflow]]
|
||||
@@ -216,16 +213,6 @@ To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
|
||||
include-code::./JsonTests[tag=jsonPath,indent=0]
|
||||
|
||||
|
||||
[[resttestclient.multipart]]
|
||||
==== Multipart Content
|
||||
|
||||
When testing endpoints that return multipart responses, you can decode the body to a
|
||||
`MultiValueMap<String, Part>` and assert individual parts using the `FormFieldPart`
|
||||
and `FilePart` subtypes.
|
||||
|
||||
include-code::./MultipartTests[tag=multipart,indent=0]
|
||||
|
||||
|
||||
[[resttestclient.assertj]]
|
||||
=== AssertJ Integration
|
||||
|
||||
|
||||
+5
-6
@@ -2,9 +2,8 @@
|
||||
= Bean Overriding in Tests
|
||||
|
||||
Bean overriding in tests refers to the ability to override specific beans in the
|
||||
`ApplicationContext` for a test class, by annotating the test class, one or more
|
||||
non-static fields in the test class, or one or more parameters in the constructor for the
|
||||
test class.
|
||||
`ApplicationContext` for a test class, by annotating the test class or one or more
|
||||
non-static fields in the test class.
|
||||
|
||||
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
||||
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
||||
@@ -43,9 +42,9 @@ The `spring-test` module registers implementations of the latter two
|
||||
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||
properties file].
|
||||
|
||||
The bean overriding infrastructure searches for annotations on test classes, non-static
|
||||
fields in test classes, and parameters in test class constructors that are meta-annotated
|
||||
with `@BeanOverride`, and instantiates the corresponding `BeanOverrideProcessor` which is
|
||||
The bean overriding infrastructure searches for annotations on test classes as well as
|
||||
annotations on non-static fields in test classes that are meta-annotated with
|
||||
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
|
||||
responsible for creating an appropriate `BeanOverrideHandler`.
|
||||
|
||||
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
||||
|
||||
@@ -19,6 +19,6 @@ meta-annotation. If a bootstrapper is not explicitly configured by using
|
||||
`WebTestContextBootstrapper` is used, depending on the presence of `@WebAppConfiguration`.
|
||||
|
||||
Since the `TestContextBootstrapper` SPI is likely to change in the future (to accommodate
|
||||
new requirements), we strongly encourage implementors not to implement this interface
|
||||
new requirements), we strongly encourage implementers not to implement this interface
|
||||
directly but rather to extend `AbstractTestContextBootstrapper` or one of its concrete
|
||||
subclasses instead.
|
||||
|
||||
-82
@@ -543,85 +543,3 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The following example demonstrates how to implement and register a custom
|
||||
`SystemPropertyOverrideActiveProfilesResolver` that allows the `spring.profiles.active`
|
||||
property (when configured as a JVM system property) to override profiles configured via
|
||||
`@ActiveProfiles`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// profiles resolved programmatically via a custom resolver that
|
||||
// allows "spring.profiles.active" to override @ActiveProfiles
|
||||
@ActiveProfiles(
|
||||
resolver = SystemPropertyOverrideActiveProfilesResolver.class,
|
||||
inheritProfiles = false)
|
||||
class TransferServiceTest extends AbstractIntegrationTest {
|
||||
// test body
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// profiles resolved programmatically via a custom resolver that
|
||||
// allows "spring.profiles.active" to override @ActiveProfiles
|
||||
@ActiveProfiles(
|
||||
resolver = SystemPropertyOverrideActiveProfilesResolver::class,
|
||||
inheritProfiles = false)
|
||||
class TransferServiceTest : AbstractIntegrationTest() {
|
||||
// test body
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",fold="-imports"]
|
||||
----
|
||||
import org.springframework.core.env.AbstractEnvironment;
|
||||
import org.springframework.test.context.support.DefaultActiveProfilesResolver;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class SystemPropertyOverrideActiveProfilesResolver extends DefaultActiveProfilesResolver {
|
||||
|
||||
@Override
|
||||
public String[] resolve(Class<?> testClass) {
|
||||
String profiles = System.getProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
if (StringUtils.hasText(profiles)) {
|
||||
return StringUtils.commaDelimitedListToStringArray(
|
||||
StringUtils.trimAllWhitespace(profiles));
|
||||
}
|
||||
return super.resolve(testClass);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",fold="-imports"]
|
||||
----
|
||||
import org.springframework.core.env.AbstractEnvironment
|
||||
import org.springframework.test.context.support.DefaultActiveProfilesResolver
|
||||
import org.springframework.util.StringUtils
|
||||
|
||||
class SystemPropertyOverrideActiveProfilesResolver : DefaultActiveProfilesResolver() {
|
||||
|
||||
override fun resolve(testClass: Class<*>): Array<String> {
|
||||
val profiles = System.getProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME)
|
||||
if (StringUtils.hasText(profiles)) {
|
||||
return StringUtils.commaDelimitedListToStringArray(
|
||||
StringUtils.trimAllWhitespace(profiles)
|
||||
)
|
||||
}
|
||||
return super.resolve(testClass)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
+5
-8
@@ -179,10 +179,6 @@ If a specific parameter in a constructor for a JUnit Jupiter test class is of ty
|
||||
`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with
|
||||
`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific
|
||||
parameter with the corresponding bean or value from the test's `ApplicationContext`.
|
||||
Similarly, if a specific parameter is annotated with `@MockitoBean` or `@MockitoSpyBean`,
|
||||
Spring will inject a Mockito mock or spy, respectively — see
|
||||
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
|
||||
for details.
|
||||
|
||||
Spring can also be configured to autowire all arguments for a test class constructor if
|
||||
the constructor is considered to be _autowirable_. A constructor is considered to be
|
||||
@@ -400,10 +396,11 @@ recursively.
|
||||
====
|
||||
As of Spring Framework 7.0, the `SpringExtension` uses a test-method scoped
|
||||
`ExtensionContext` within `@Nested` test class hierarchies by default. However, the
|
||||
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext` — for
|
||||
example via `@SpringExtensionConfig` or the `spring.test.extension.context.scope` Spring
|
||||
property (see
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]).
|
||||
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext`.
|
||||
|
||||
See the documentation for
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
for details.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
|
||||
@@ -580,8 +580,8 @@ Kotlin::
|
||||
[[webtestclient-stream]]
|
||||
==== Streaming Responses
|
||||
|
||||
To test potentially infinite streams such as `"text/event-stream"`,
|
||||
`"application/jsonl"` or `"application/x-ndjson"`, start by verifying the response status and headers, and then
|
||||
To test potentially infinite streams such as `"text/event-stream"` or
|
||||
`"application/x-ndjson"`, start by verifying the response status and headers, and then
|
||||
obtain a `FluxExchangeResult`:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -107,4 +107,7 @@ Kotlin::
|
||||
|
||||
|
||||
[[webflux-ann-initbinder-model-design]]
|
||||
NOTE: For more guidance on model design, please see xref:web/webflux/data-binding.adoc[Data Binding].
|
||||
== Model Design
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[See equivalent in the Servlet stack]#
|
||||
|
||||
include::partial$web/web-data-binding-model-design.adoc[]
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
[[webflux-data-binding]]
|
||||
= Data Binding
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-data-binding.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Data binding is a mechanism that binds string parameters onto an object graph with type conversion.
|
||||
It is a core mechanism of the Spring Framework that helps with application configuration.
|
||||
In web applications it makes it easy to access query parameters and form data through richly typed objects rather than through maps of string values.
|
||||
|
||||
To learn more about the data binding mechanism, including constructor and setter binding, property name syntax, type conversion,
|
||||
and more, see xref:core/validation/data-binding.adoc[Data binding] in the Core Technologies section.
|
||||
|
||||
For annotated controllers, data binding applies to a
|
||||
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute] method argument.
|
||||
For functional endpoints, use the `bind` method of xref:web/webflux-functional.adoc#webflux-fn-request[ServerRequest].
|
||||
|
||||
TIP: For browser applications with annotated controllers, you can use
|
||||
xref:web/webflux/controller/ann-modelattrib-methods.adoc[@ModelAttribute methods]
|
||||
to initialize additional model attributes for use in rendered views.
|
||||
|
||||
Each request uses a separate `WebDataBinder` instance.
|
||||
For annotated controllers, this instance can be customized through
|
||||
xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder methods] within a controller, or
|
||||
across controllers through xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice].
|
||||
For functional endpoints, use overloaded `ServerRequest.bind` methods.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-data-binding-design]]
|
||||
== Model Design
|
||||
[.small]#xref:web/webmvc/mvc-data-binding.adoc#mvc-data-binding-design[See equivalent in the Servlet stack]#
|
||||
|
||||
include::partial$web/web-data-binding-model-design.adoc[]
|
||||
|
||||
@@ -485,8 +485,8 @@ The `JacksonJsonEncoder` works as follows:
|
||||
* For a multi-value publisher with `application/json`, by default collect the values with
|
||||
`Flux#collectToList()` and then serialize the resulting collection.
|
||||
* For a multi-value publisher with a streaming media type such as
|
||||
`application/jsonl`, `application/x-ndjson` or `application/stream+x-jackson-smile`,
|
||||
encode, write, and flush each value individually using a
|
||||
`application/x-ndjson` or `application/stream+x-jackson-smile`, encode, write, and
|
||||
flush each value individually using a
|
||||
https://en.wikipedia.org/wiki/JSON_streaming[line-delimited JSON] format. Other
|
||||
streaming media types may be registered with the encoder.
|
||||
* For SSE the `JacksonJsonEncoder` is invoked per event and the output is flushed to ensure
|
||||
@@ -598,7 +598,7 @@ To configure all three in WebFlux, you'll need to supply a pre-configured instan
|
||||
[.small]#xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[See equivalent in the Servlet stack]#
|
||||
|
||||
When streaming to the HTTP response (for example, `text/event-stream`,
|
||||
`application/jsonl`, `application/x-ndjson`), it is important to send data periodically, in order to
|
||||
`application/x-ndjson`), it is important to send data periodically, in order to
|
||||
reliably detect a disconnected client sooner rather than later. Such a send could be a
|
||||
comment-only, empty SSE event or any other "no-op" data that would effectively serve as
|
||||
a heartbeat.
|
||||
|
||||
@@ -182,7 +182,7 @@ The parameters to any of the above macros have consistent meanings:
|
||||
For strictly sorted maps, you can use a `SortedMap` (such as a `TreeMap`) with a
|
||||
suitable `Comparator` and, for arbitrary Maps that should return values in insertion
|
||||
order, use a `LinkedHashMap` or a `LinkedMap` from `commons-collections`.
|
||||
* `separator`: Where multiple options are available as discrete elements (radio buttons
|
||||
* `separator`: Where multiple options are available as discreet elements (radio buttons
|
||||
or checkboxes), the sequence of characters used to separate each one in the list
|
||||
(such as `<br>`).
|
||||
* `attributes`: An additional string of arbitrary tags or text to be included within
|
||||
|
||||
@@ -23,17 +23,13 @@ For all converters, a default media type is used, but you can override it by set
|
||||
By default, this converter supports all text media types(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`.
|
||||
|
||||
| `FormHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write URL encoded forms.
|
||||
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP request and response.
|
||||
By default, this converter reads and writes the `application/x-www-form-urlencoded` media type.
|
||||
Form data is read from and written into a `MultiValueMap<String, String>`.
|
||||
`Map<String, String>` is also supported, but multiple values under the same key will be ignored.
|
||||
|
||||
| `MultipartHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write multipart messages.
|
||||
`MultiValueMap<String, Object>` can be written to multipart messages, converting each part independently using
|
||||
the configured message converters. Multipart messages can be read into `MultiValueMap<String, Part>`, each value
|
||||
being a `Part` or one of its subtypes (`FormFieldPart` and `FilePart`).
|
||||
By default, `multipart/form-data` is supported. Additional multipart subtypes can be supported for writing form data.
|
||||
The converter can also write (but not read) multipart data read from a `MultiValueMap<String, Object>`.
|
||||
By default, `multipart/form-data` is supported.
|
||||
Additional multipart subtypes can be supported for writing form data.
|
||||
Consult the javadoc for `FormHttpMessageConverter` for further details.
|
||||
|
||||
| `ByteArrayHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write byte arrays from the HTTP request and response.
|
||||
|
||||
@@ -423,8 +423,8 @@ Reactive return values are handled as follows:
|
||||
|
||||
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
|
||||
include `CompletionStage` (JDK), `Mono` (Reactor), and `Single` (RxJava).
|
||||
* A multi-value stream with a streaming media type (such as `application/jsonl`,
|
||||
`application/x-ndjson` or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
|
||||
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
|
||||
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
|
||||
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
|
||||
Applications can also return `Flux<ServerSentEvent>` or `Observable<ServerSentEvent>`.
|
||||
* A multi-value stream with any other media type (such as `application/json`) is adapted
|
||||
|
||||
+1
-1
@@ -188,7 +188,7 @@ the content negotiation during the error handling phase will decide which conten
|
||||
| `View`
|
||||
| A `View` instance to use for rendering together with the implicit model -- determined
|
||||
through command objects and `@ModelAttribute` methods. The handler method may also
|
||||
programmatically enrich the model by declaring a `Model` argument (described earlier).
|
||||
programmatically enrich the model by declaring a `Model` argument (descried earlier).
|
||||
|
||||
| `java.util.Map`, `org.springframework.ui.Model`
|
||||
| Attributes to be added to the implicit model with the view name implicitly determined
|
||||
|
||||
@@ -107,4 +107,7 @@ Kotlin::
|
||||
|
||||
|
||||
[[mvc-ann-initbinder-model-design]]
|
||||
NOTE: For more guidance on model design, please see xref:web/webmvc/mvc-data-binding.adoc[Data Binding].
|
||||
== Model Design
|
||||
[.small]#xref:web/webflux/controller/ann-initbinder.adoc#webflux-ann-initbinder-model-design[See equivalent in the Reactive stack]#
|
||||
|
||||
include::partial$web/web-data-binding-model-design.adoc[]
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
[[mvc-data-binding]]
|
||||
= Data Binding
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webflux/data-binding.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Data binding is a mechanism that binds string parameters onto an object graph with type conversion.
|
||||
It is a core mechanism of the Spring Framework that helps with application configuration.
|
||||
In web applications it makes it easy to access query parameters and form data through richly typed objects rather than through maps of string values.
|
||||
|
||||
To learn more about the data binding mechanism, including constructor and setter binding, property name syntax, type conversion,
|
||||
and more, see xref:core/validation/data-binding.adoc[Data binding] in the Core Technologies section.
|
||||
|
||||
For annotated controllers, data binding applies to a
|
||||
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute] method argument.
|
||||
For functional endpoints, use the `bind` method of xref:web/webmvc-functional.adoc#webmvc-fn-request[ServerRequest].
|
||||
|
||||
TIP: For browser applications with annotated controllers, you can use
|
||||
xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[@ModelAttribute methods]
|
||||
to initialize additional model attributes for use in rendered views.
|
||||
|
||||
Each request uses a separate `WebDataBinder` instance.
|
||||
For annotated controllers, this instance can be customized through
|
||||
xref:web/webmvc/mvc-controller/ann-initbinder.adoc[@InitBinder methods] within a controller, or
|
||||
across controllers through xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice].
|
||||
For functional endpoints, use overloaded `ServerRequest.bind` methods.
|
||||
|
||||
|
||||
|
||||
[[mvc-data-binding-design]]
|
||||
== Model Design
|
||||
[.small]#xref:web/webflux/data-binding.adoc#webflux-data-binding-design[See equivalent in the Reactive stack]#
|
||||
|
||||
include::partial$web/web-data-binding-model-design.adoc[]
|
||||
@@ -257,7 +257,7 @@ An `XhrTransport`, by definition, supports both `xhr-streaming` and `xhr-polling
|
||||
from a client perspective, there is no difference other than in the URL used to connect
|
||||
to the server. At present there are two implementations:
|
||||
|
||||
* `RestClientXhrTransport` uses Spring's `RestClient` for HTTP requests.
|
||||
* `RestTemplateXhrTransport` uses Spring's `RestTemplate` for HTTP requests.
|
||||
* `JettyXhrTransport` uses Jetty's `HttpClient` for HTTP requests.
|
||||
|
||||
The following example shows how to create a SockJS client and connect to a SockJS endpoint:
|
||||
@@ -266,7 +266,7 @@ The following example shows how to create a SockJS client and connect to a SockJ
|
||||
----
|
||||
List<Transport> transports = new ArrayList<>(2);
|
||||
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
|
||||
transports.add(new RestClientXhrTransport());
|
||||
transports.add(new RestTemplateXhrTransport());
|
||||
|
||||
SockJsClient sockJsClient = new SockJsClient(transports);
|
||||
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
|
||||
|
||||
@@ -1,53 +1,91 @@
|
||||
Data binding involves binding untrusted input onto application objects.
|
||||
For security reasons, it's crucial to ensure that input is properly constrained to expected fields only.
|
||||
This section provides guidance for safe binding.
|
||||
xref:core/validation/data-binding.adoc[Data binding] for web requests involves
|
||||
binding request parameters to a model object. By default, request parameters can be bound
|
||||
to any public property of the model object, which means malicious clients can provide
|
||||
extra values for properties that exist in the model object graph, but are not expected to
|
||||
be set. This is why model object design requires careful consideration.
|
||||
|
||||
First, prefer **immutable object design** for web binding purposes.
|
||||
It is safe because a constructor naturally constrains binding to expected inputs.
|
||||
You can use a Java record or a class with a primary constructor, and either can have further nested objects.
|
||||
See xref:core/validation/data-binding.adoc#data-binding-constructor-binding[Constructor Binding] for details.
|
||||
TIP: The model object, and its nested object graph is also sometimes referred to as a
|
||||
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
|
||||
|
||||
Another option for safe binding is to use **dedicated objects** designed for the expected input.
|
||||
Such objects, even if mutable, are safe because they constrain binding to the expected inputs.
|
||||
A good practice is to use a _dedicated model object_ rather than exposing your domain
|
||||
model such as JPA or Hibernate entities for web data binding. For example, on a form to
|
||||
change an email address, create a `ChangeEmailForm` model object that declares only
|
||||
the properties required for the input:
|
||||
|
||||
Domain objects such as JPA or Hibernate entities are generally not safe for web binding
|
||||
as they likely contain more properties than the expected inputs.
|
||||
For such cases, it's crucial to declare the properties to expose for binding.
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class ChangeEmailForm {
|
||||
|
||||
private String oldEmailAddress;
|
||||
private String newEmailAddress;
|
||||
|
||||
public void setOldEmailAddress(String oldEmailAddress) {
|
||||
this.oldEmailAddress = oldEmailAddress;
|
||||
}
|
||||
|
||||
public String getOldEmailAddress() {
|
||||
return this.oldEmailAddress;
|
||||
}
|
||||
|
||||
public void setNewEmailAddress(String newEmailAddress) {
|
||||
this.newEmailAddress = newEmailAddress;
|
||||
}
|
||||
|
||||
public String getNewEmailAddress() {
|
||||
return this.newEmailAddress;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Another good practice is to apply
|
||||
xref:core/validation/data-binding.adoc#data-binding-constructor-binding[constructor binding],
|
||||
which uses only the request parameters it needs for constructor arguments, and any other
|
||||
input is ignored. This is in contrast to property binding which by default binds every
|
||||
request parameter for which there is a matching property.
|
||||
|
||||
If neither a dedicated model object nor constructor binding is sufficient, and you must
|
||||
use property binding, we strongly recommend registering `allowedFields` patterns (case
|
||||
sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set.
|
||||
For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class PersonController {
|
||||
public class ChangeEmailController {
|
||||
|
||||
@InitBinder
|
||||
void initBinder(WebDataBinder binder) {
|
||||
// See Javadoc for supported pattern syntax
|
||||
binder.setAllowedFields("firstName", "lastName", "*Address");
|
||||
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
|
||||
}
|
||||
|
||||
// @RequestMapping methods, etc.
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: It is also possible to configure `disallowedFields`, but that's fragile, and
|
||||
due to be https://github.com/spring-projects/spring-framework/issues/36802[deprecated] in Spring Framework 7.1.
|
||||
It is easy to overlook fields or introduce additional fields over time that should also be excluded.
|
||||
You can also register `disallowedFields` patterns (case insensitive). However,
|
||||
"allowed" configuration is preferred over "disallowed" as it is more explicit and less
|
||||
prone to mistakes.
|
||||
|
||||
By default, `DataBinder` applies both constructor and setter binding.
|
||||
This is fine with immutable objects and dedicated objects, but for domain objects, you must
|
||||
remember to set `allowedFields`. To ensure data binding is only used in declarative style where
|
||||
expected inputs are explicitly declared, you can set `declarativeBinding` on `DataBinder`.
|
||||
That applies constructor binding always, and setter binding conditionally if `allowedFields` is set.
|
||||
The following shows how to set this flag globally, or
|
||||
you can also narrow it through attributes on `ControllerAdvice`:
|
||||
By default, constructor and property binding are both used. If you want to use
|
||||
constructor binding only, you can set the `declarativeBinding` flag on `WebDataBinder`
|
||||
through an `@InitBinder` method either locally within a controller or globally through an
|
||||
`@ControllerAdvice`. Turning this flag on ensures that only constructor binding is used
|
||||
and that property binding is not used unless `allowedFields` patterns are configured.
|
||||
For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
public class ControllerConfig {
|
||||
@Controller
|
||||
public class MyController {
|
||||
|
||||
@InitBinder
|
||||
void initBinder(WebDataBinder binder) {
|
||||
binder.setDeclarativeBinding(true);
|
||||
}
|
||||
|
||||
// @RequestMapping methods, etc.
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-alpha.12",
|
||||
"antora": "3.2.0-alpha.11",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.5",
|
||||
"@antora/collector-extension": "1.0.3",
|
||||
"@antora/collector-extension": "1.0.2",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.12",
|
||||
"fast-xml-parser": "5.7.0",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.18"
|
||||
"@springio/antora-extensions": "1.14.7",
|
||||
"fast-xml-parser": "5.3.8",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.testing.resttestclient.multipart;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.multipart.FilePart;
|
||||
import org.springframework.http.converter.multipart.FormFieldPart;
|
||||
import org.springframework.http.converter.multipart.Part;
|
||||
import org.springframework.test.web.servlet.client.RestTestClient;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class MultipartTests {
|
||||
|
||||
RestTestClient client;
|
||||
|
||||
@Test
|
||||
void multipart() {
|
||||
// tag::multipart[]
|
||||
client.get().uri("/upload")
|
||||
.accept(MediaType.MULTIPART_FORM_DATA)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(new ParameterizedTypeReference<MultiValueMap<String, Part>>() {})
|
||||
.value(result -> {
|
||||
Part field = result.getFirst("fieldPart");
|
||||
assertThat(field).isInstanceOfSatisfying(FormFieldPart.class,
|
||||
formField -> assertThat(formField.value()).isEqualTo("fieldValue"));
|
||||
Part file = result.getFirst("filePart");
|
||||
assertThat(file).isInstanceOfSatisfying(FilePart.class,
|
||||
filePart -> assertThat(filePart.filename()).isEqualTo("logo.png"));
|
||||
});
|
||||
// end::multipart[]
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@ public class ExceptionController {
|
||||
|
||||
// tag::narrow[]
|
||||
@ExceptionHandler({FileSystemException.class, RemoteException.class})
|
||||
public ResponseEntity<String> handleIOException(IOException ex) {
|
||||
public ResponseEntity<String> handleIoException(IOException ex) {
|
||||
return ResponseEntity.internalServerError().body(ex.getMessage());
|
||||
}
|
||||
// end::narrow[]
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -54,6 +54,6 @@ class ConcurrentOperationExecutor : Ordered {
|
||||
lockFailureException = ex
|
||||
}
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
throw lockFailureException!!
|
||||
}
|
||||
} // end::snippet[]
|
||||
+1
-1
@@ -27,7 +27,7 @@ class ExceptionController {
|
||||
|
||||
// tag::narrow[]
|
||||
@ExceptionHandler(FileSystemException::class, RemoteException::class)
|
||||
fun handleIOException(ex: IOException): ResponseEntity<String> {
|
||||
fun handleIoException(ex: IOException): ResponseEntity<String> {
|
||||
return ResponseEntity.internalServerError().body(ex.message)
|
||||
}
|
||||
// end::narrow[]
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -7,31 +7,31 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.21.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.16.6"))
|
||||
api(platform("io.netty:netty-bom:4.2.15.Final"))
|
||||
api(platform("io.projectreactor:reactor-bom:2025.0.6"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.16.4"))
|
||||
api(platform("io.netty:netty-bom:4.2.10.Final"))
|
||||
api(platform("io.projectreactor:reactor-bom:2025.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:5.0.6"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.26.0"))
|
||||
api(platform("org.apache.groovy:groovy-bom:5.0.4"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.25.3"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.7"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.1.9"))
|
||||
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.9"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.1.7"))
|
||||
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.7"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.11.0"))
|
||||
api(platform("org.junit:junit-bom:6.1.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.23.0"))
|
||||
api(platform("tools.jackson:jackson-bom:3.1.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
|
||||
api(platform("org.junit:junit-bom:6.0.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.22.0"))
|
||||
api(platform("tools.jackson:jackson-bom:3.0.4"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.4")
|
||||
api("com.fasterxml.woodstox:woodstox-core:7.1.1")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.3")
|
||||
api("com.github.librepdf:openpdf:1.3.43")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.13.2")
|
||||
api("com.google.protobuf:protobuf-java-util:4.34.1")
|
||||
api("com.google.protobuf:protobuf-java-util:4.34.0")
|
||||
api("com.h2database:h2:2.4.240")
|
||||
api("com.jayway.jsonpath:json-path:2.10.0")
|
||||
api("com.networknt:json-schema-validator:1.5.3")
|
||||
@@ -96,10 +96,10 @@ dependencies {
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.6")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4.2")
|
||||
api("org.apache.poi:poi-ooxml:5.5.1")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.22")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.22")
|
||||
api("org.apache.tomcat:tomcat-util:11.0.22")
|
||||
api("org.apache.tomcat:tomcat-websocket:11.0.22")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.18")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.18")
|
||||
api("org.apache.tomcat:tomcat-util:11.0.18")
|
||||
api("org.apache.tomcat:tomcat-websocket:11.0.18")
|
||||
api("org.aspectj:aspectjrt:1.9.25")
|
||||
api("org.aspectj:aspectjtools:1.9.25")
|
||||
api("org.aspectj:aspectjweaver:1.9.25")
|
||||
@@ -111,7 +111,7 @@ dependencies {
|
||||
api("org.easymock:easymock:5.6.0")
|
||||
api("org.eclipse.angus:angus-mail:2.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.1.4")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-RC1")
|
||||
api("org.eclipse:yasson:3.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
@@ -120,7 +120,7 @@ dependencies {
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.1")
|
||||
api("org.hamcrest:hamcrest:3.0")
|
||||
api("org.hibernate.orm:hibernate-core:7.4.0.Final")
|
||||
api("org.hibernate.orm:hibernate-core:7.2.6.Final")
|
||||
api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.21.0")
|
||||
@@ -138,6 +138,7 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:selenium-java:4.41.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.testng:testng:7.12.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-lite:1.1.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.4")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.4")
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
version=7.1.0-SNAPSHOT
|
||||
version=7.0.6
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=2.3.20
|
||||
kotlinVersion=2.2.21
|
||||
byteBuddyVersion=1.17.6
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
|
||||
@@ -84,6 +84,19 @@ eclipse.classpath.file.whenMerged {
|
||||
}
|
||||
}
|
||||
|
||||
// Due to an apparent bug in Gradle, even though we exclude the "main" classpath
|
||||
// entries for sources generated by XJC in spring-oxm.gradle, the Gradle eclipse
|
||||
// plugin still includes them in the generated .classpath file. So, we have to
|
||||
// manually remove those lingering "main" entries.
|
||||
if (project.name == "spring-oxm") {
|
||||
eclipse.classpath.file.whenMerged { classpath ->
|
||||
classpath.entries.removeAll {
|
||||
it.path =~ /build\/generated\/sources\/xjc\/.+/ &&
|
||||
it.entryAttributes.get("gradle_scope") == "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Include project specific settings
|
||||
tasks.register('eclipseSettings', Copy) {
|
||||
from rootProject.files(
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-3
@@ -1,9 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
||||
Vendored
+21
-10
@@ -23,8 +23,8 @@
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@@ -51,7 +51,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
"%COMSPEC%" /c exit 1
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
@@ -65,7 +65,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
"%COMSPEC%" /c exit 1
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
@@ -73,10 +73,21 @@ echo location of your Java installation. 1>&2
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
+13
-8
@@ -61,9 +61,10 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
|
||||
|
||||
@Test
|
||||
void singletonScoping() throws Exception {
|
||||
void testSingletonScoping() throws Exception {
|
||||
assertThat(AopUtils.isAopProxy(singletonScoped)).as("Should be AOP proxy").isTrue();
|
||||
assertThat(singletonScoped).as("Should be target class proxy").isInstanceOf(TestBean.class);
|
||||
boolean condition = singletonScoped instanceof TestBean;
|
||||
assertThat(condition).as("Should be target class proxy").isTrue();
|
||||
String rob = "Rob Harrop";
|
||||
String bram = "Bram Smeets";
|
||||
assertThat(singletonScoped.getName()).isEqualTo(rob);
|
||||
@@ -74,17 +75,19 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestScoping() {
|
||||
void testRequestScoping() {
|
||||
MockHttpServletRequest oldRequest = new MockHttpServletRequest();
|
||||
MockHttpServletRequest newRequest = new MockHttpServletRequest();
|
||||
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(oldRequest));
|
||||
|
||||
assertThat(AopUtils.isAopProxy(requestScoped)).as("Should be AOP proxy").isTrue();
|
||||
assertThat(requestScoped).as("Should be target class proxy").isInstanceOf(TestBean.class);
|
||||
boolean condition = requestScoped instanceof TestBean;
|
||||
assertThat(condition).as("Should be target class proxy").isTrue();
|
||||
|
||||
assertThat(AopUtils.isAopProxy(testBean)).as("Should be AOP proxy").isTrue();
|
||||
assertThat(testBean).as("Regular bean should be JDK proxy").isNotInstanceOf(TestBean.class);
|
||||
boolean condition1 = testBean instanceof TestBean;
|
||||
assertThat(condition1).as("Regular bean should be JDK proxy").isFalse();
|
||||
|
||||
String rob = "Rob Harrop";
|
||||
String bram = "Bram Smeets";
|
||||
@@ -100,7 +103,7 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionScoping() {
|
||||
void testSessionScoping() {
|
||||
MockHttpSession oldSession = new MockHttpSession();
|
||||
MockHttpSession newSession = new MockHttpSession();
|
||||
|
||||
@@ -109,12 +112,14 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
assertThat(AopUtils.isAopProxy(sessionScoped)).as("Should be AOP proxy").isTrue();
|
||||
assertThat(sessionScoped).as("Should not be target class proxy").isNotInstanceOf(TestBean.class);
|
||||
boolean condition1 = sessionScoped instanceof TestBean;
|
||||
assertThat(condition1).as("Should not be target class proxy").isFalse();
|
||||
|
||||
assertThat(sessionScopedAlias).isSameAs(sessionScoped);
|
||||
|
||||
assertThat(AopUtils.isAopProxy(testBean)).as("Should be AOP proxy").isTrue();
|
||||
assertThat(testBean).as("Regular bean should be JDK proxy").isNotInstanceOf(TestBean.class);
|
||||
boolean condition = testBean instanceof TestBean;
|
||||
assertThat(condition).as("Regular bean should be JDK proxy").isFalse();
|
||||
|
||||
String rob = "Rob Harrop";
|
||||
String bram = "Bram Smeets";
|
||||
|
||||
+10
-9
@@ -65,7 +65,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultExclusionPrefix() {
|
||||
void testDefaultExclusionPrefix() {
|
||||
DefaultAdvisorAutoProxyCreator aapc = (DefaultAdvisorAutoProxyCreator) getBeanFactory().getBean(ADVISOR_APC_BEAN_NAME);
|
||||
assertThat(aapc.getAdvisorBeanNamePrefix()).isEqualTo((ADVISOR_APC_BEAN_NAME + DefaultAdvisorAutoProxyCreator.SEPARATOR));
|
||||
assertThat(aapc.isUsePrefix()).isFalse();
|
||||
@@ -75,21 +75,21 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
* If no pointcuts match (no attrs) there should be proxying.
|
||||
*/
|
||||
@Test
|
||||
void noProxy() {
|
||||
void testNoProxy() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
Object o = bf.getBean("noSetters");
|
||||
assertThat(AopUtils.isAopProxy(o)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void txIsProxied() {
|
||||
void testTxIsProxied() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
assertThat(AopUtils.isAopProxy(test)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void regexpApplied() {
|
||||
void testRegexpApplied() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
MethodCounter counter = (MethodCounter) bf.getBean("countingAdvice");
|
||||
@@ -99,7 +99,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void transactionAttributeOnMethod() {
|
||||
void testTransactionAttributeOnMethod() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
|
||||
@@ -121,7 +121,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
* Should not roll back on servlet exception.
|
||||
*/
|
||||
@Test
|
||||
void rollbackRulesOnMethodCauseRollback() throws Exception {
|
||||
void testRollbackRulesOnMethodCauseRollback() throws Exception {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
Rollback rb = (Rollback) bf.getBean("rollback");
|
||||
|
||||
@@ -147,7 +147,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void rollbackRulesOnMethodPreventRollback() throws Exception {
|
||||
void testRollbackRulesOnMethodPreventRollback() throws Exception {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
Rollback rb = (Rollback) bf.getBean("rollback");
|
||||
|
||||
@@ -164,11 +164,12 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void programmaticRollback() {
|
||||
void testProgrammaticRollback() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
|
||||
Object bean = bf.getBean(TXMANAGER_BEAN_NAME);
|
||||
assertThat(bean).isInstanceOf(CallCountingTransactionManager.class);
|
||||
boolean condition = bean instanceof CallCountingTransactionManager;
|
||||
assertThat(condition).isTrue();
|
||||
CallCountingTransactionManager txMan = (CallCountingTransactionManager) bf.getBean(TXMANAGER_BEAN_NAME);
|
||||
|
||||
Rollback rb = (Rollback) bf.getBean("rollback");
|
||||
|
||||
+3
-3
@@ -50,13 +50,13 @@ class ComponentBeanDefinitionParserTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void bionicBasic() {
|
||||
void testBionicBasic() {
|
||||
Component cp = getBionicFamily();
|
||||
assertThat(cp.getName()).isEqualTo("Bionic-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bionicFirstLevelChildren() {
|
||||
void testBionicFirstLevelChildren() {
|
||||
Component cp = getBionicFamily();
|
||||
List<Component> components = cp.getComponents();
|
||||
assertThat(components).hasSize(2);
|
||||
@@ -65,7 +65,7 @@ class ComponentBeanDefinitionParserTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void bionicSecondLevelChildren() {
|
||||
void testBionicSecondLevelChildren() {
|
||||
Component cp = getBionicFamily();
|
||||
List<Component> components = cp.getComponents().get(0).getComponents();
|
||||
assertThat(components).hasSize(2);
|
||||
|
||||
+20
-15
@@ -83,7 +83,7 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
|
||||
|
||||
@Test
|
||||
void prototype() {
|
||||
void testPrototype() {
|
||||
ApplicationContext context = createContext(ScopedProxyMode.NO);
|
||||
ScopedTestBean bean = (ScopedTestBean) context.getBean("prototype");
|
||||
assertThat(bean).isNotNull();
|
||||
@@ -92,7 +92,7 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void singletonScopeWithNoProxy() {
|
||||
void testSingletonScopeWithNoProxy() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.NO);
|
||||
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
|
||||
@@ -115,7 +115,7 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void singletonScopeIgnoresProxyInterfaces() {
|
||||
void testSingletonScopeIgnoresProxyInterfaces() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
|
||||
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
|
||||
@@ -136,7 +136,7 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void singletonScopeIgnoresProxyTargetClass() {
|
||||
void testSingletonScopeIgnoresProxyTargetClass() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
|
||||
ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
|
||||
@@ -157,7 +157,7 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestScopeWithNoProxy() {
|
||||
void testRequestScopeWithNoProxy() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.NO);
|
||||
ScopedTestBean bean = (ScopedTestBean) context.getBean("request");
|
||||
@@ -178,14 +178,15 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestScopeWithProxiedInterfaces() {
|
||||
void testRequestScopeWithProxiedInterfaces() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
|
||||
IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
|
||||
|
||||
// should be dynamic proxy, implementing both interfaces
|
||||
assertThat(AopUtils.isJdkDynamicProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(AnotherScopeTestInterface.class);
|
||||
boolean condition = bean instanceof AnotherScopeTestInterface;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
@@ -199,14 +200,15 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestScopeWithProxiedTargetClass() {
|
||||
void testRequestScopeWithProxiedTargetClass() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributes);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
|
||||
IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
|
||||
|
||||
// should be a class-based proxy
|
||||
assertThat(AopUtils.isCglibProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(RequestScopedTestBean.class);
|
||||
boolean condition = bean instanceof RequestScopedTestBean;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
@@ -220,7 +222,7 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionScopeWithNoProxy() {
|
||||
void testSessionScopeWithNoProxy() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.NO);
|
||||
ScopedTestBean bean = (ScopedTestBean) context.getBean("session");
|
||||
@@ -241,14 +243,15 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionScopeWithProxiedInterfaces() {
|
||||
void testSessionScopeWithProxiedInterfaces() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
|
||||
IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
|
||||
|
||||
// should be dynamic proxy, implementing both interfaces
|
||||
assertThat(AopUtils.isJdkDynamicProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(AnotherScopeTestInterface.class);
|
||||
boolean condition = bean instanceof AnotherScopeTestInterface;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
@@ -268,15 +271,17 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionScopeWithProxiedTargetClass() {
|
||||
void testSessionScopeWithProxiedTargetClass() {
|
||||
RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
|
||||
ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
|
||||
IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
|
||||
|
||||
// should be a class-based proxy
|
||||
assertThat(AopUtils.isCglibProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(ScopedTestBean.class);
|
||||
assertThat(bean).isInstanceOf(SessionScopedTestBean.class);
|
||||
boolean condition1 = bean instanceof ScopedTestBean;
|
||||
assertThat(condition1).isTrue();
|
||||
boolean condition = bean instanceof SessionScopedTestBean;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
|
||||
+10
-5
@@ -166,7 +166,8 @@ class ClassPathBeanDefinitionScannerScopeIntegrationTests {
|
||||
|
||||
// should be dynamic proxy, implementing both interfaces
|
||||
assertThat(AopUtils.isJdkDynamicProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(AnotherScopeTestInterface.class);
|
||||
boolean condition = bean instanceof AnotherScopeTestInterface;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
@@ -187,7 +188,8 @@ class ClassPathBeanDefinitionScannerScopeIntegrationTests {
|
||||
|
||||
// should be a class-based proxy
|
||||
assertThat(AopUtils.isCglibProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(RequestScopedTestBean.class);
|
||||
boolean condition = bean instanceof RequestScopedTestBean;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
@@ -229,7 +231,8 @@ class ClassPathBeanDefinitionScannerScopeIntegrationTests {
|
||||
|
||||
// should be dynamic proxy, implementing both interfaces
|
||||
assertThat(AopUtils.isJdkDynamicProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(AnotherScopeTestInterface.class);
|
||||
boolean condition = bean instanceof AnotherScopeTestInterface;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
@@ -256,8 +259,10 @@ class ClassPathBeanDefinitionScannerScopeIntegrationTests {
|
||||
|
||||
// should be a class-based proxy
|
||||
assertThat(AopUtils.isCglibProxy(bean)).isTrue();
|
||||
assertThat(bean).isInstanceOf(ScopedTestBean.class);
|
||||
assertThat(bean).isInstanceOf(SessionScopedTestBean.class);
|
||||
boolean condition1 = bean instanceof ScopedTestBean;
|
||||
assertThat(condition1).isTrue();
|
||||
boolean condition = bean instanceof SessionScopedTestBean;
|
||||
assertThat(condition).isTrue();
|
||||
|
||||
assertThat(bean.getName()).isEqualTo(DEFAULT_NAME);
|
||||
bean.setName(MODIFIED_NAME);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.env;
|
||||
|
||||
/**
|
||||
* Constants used both locally and in scan* sub-packages
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String XML_PATH = "org/springframework/core/env/EnvironmentSystemIntegrationTests-context.xml";
|
||||
|
||||
public static final String ENVIRONMENT_AWARE_BEAN_NAME = "envAwareBean";
|
||||
|
||||
public static final String PROD_BEAN_NAME = "prodBean";
|
||||
public static final String DEV_BEAN_NAME = "devBean";
|
||||
public static final String DERIVED_DEV_BEAN_NAME = "derivedDevBean";
|
||||
public static final String TRANSITIVE_BEAN_NAME = "transitiveBean";
|
||||
|
||||
public static final String PROD_ENV_NAME = "prod";
|
||||
public static final String DEV_ENV_NAME = "dev";
|
||||
public static final String DERIVED_DEV_ENV_NAME = "derivedDev";
|
||||
}
|
||||
Vendored
+31
-11
@@ -58,15 +58,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
|
||||
import static org.springframework.context.ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME;
|
||||
import static org.springframework.core.env.Constants.DERIVED_DEV_BEAN_NAME;
|
||||
import static org.springframework.core.env.Constants.DERIVED_DEV_ENV_NAME;
|
||||
import static org.springframework.core.env.Constants.DEV_BEAN_NAME;
|
||||
import static org.springframework.core.env.Constants.DEV_ENV_NAME;
|
||||
import static org.springframework.core.env.Constants.ENVIRONMENT_AWARE_BEAN_NAME;
|
||||
import static org.springframework.core.env.Constants.PROD_BEAN_NAME;
|
||||
import static org.springframework.core.env.Constants.PROD_ENV_NAME;
|
||||
import static org.springframework.core.env.Constants.TRANSITIVE_BEAN_NAME;
|
||||
import static org.springframework.core.env.Constants.XML_PATH;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DERIVED_DEV_BEAN_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DERIVED_DEV_ENV_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DEV_BEAN_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DEV_ENV_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.ENVIRONMENT_AWARE_BEAN_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.PROD_BEAN_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.PROD_ENV_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.TRANSITIVE_BEAN_NAME;
|
||||
import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.XML_PATH;
|
||||
|
||||
/**
|
||||
* System integration tests for container support of the {@link Environment} API.
|
||||
@@ -87,7 +87,7 @@ import static org.springframework.core.env.Constants.XML_PATH;
|
||||
* @author Sam Brannen
|
||||
* @see org.springframework.context.support.EnvironmentIntegrationTests
|
||||
*/
|
||||
class EnvironmentSystemIntegrationTests {
|
||||
public class EnvironmentSystemIntegrationTests {
|
||||
|
||||
private final ConfigurableEnvironment prodEnv = new StandardEnvironment();
|
||||
|
||||
@@ -648,7 +648,7 @@ class EnvironmentSystemIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Profile(Constants.DERIVED_DEV_ENV_NAME)
|
||||
@Profile(DERIVED_DEV_ENV_NAME)
|
||||
@Configuration
|
||||
static class DerivedDevConfig extends DevConfig {
|
||||
@Bean
|
||||
@@ -666,4 +666,24 @@ class EnvironmentSystemIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constants used both locally and in scan* sub-packages
|
||||
*/
|
||||
public static class Constants {
|
||||
|
||||
public static final String XML_PATH = "org/springframework/core/env/EnvironmentSystemIntegrationTests-context.xml";
|
||||
|
||||
public static final String ENVIRONMENT_AWARE_BEAN_NAME = "envAwareBean";
|
||||
|
||||
public static final String PROD_BEAN_NAME = "prodBean";
|
||||
public static final String DEV_BEAN_NAME = "devBean";
|
||||
public static final String DERIVED_DEV_BEAN_NAME = "derivedDevBean";
|
||||
public static final String TRANSITIVE_BEAN_NAME = "transitiveBean";
|
||||
|
||||
public static final String PROD_ENV_NAME = "prod";
|
||||
public static final String DEV_ENV_NAME = "dev";
|
||||
public static final String DERIVED_DEV_ENV_NAME = "derivedDev";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Profile(org.springframework.core.env.Constants.DEV_ENV_NAME)
|
||||
@Profile(org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DEV_ENV_NAME)
|
||||
@Configuration
|
||||
class DevConfig {
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Profile(org.springframework.core.env.Constants.PROD_ENV_NAME)
|
||||
@Profile(org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.PROD_ENV_NAME)
|
||||
@Configuration
|
||||
class ProdConfig {
|
||||
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ package org.springframework.core.env.scan2;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Profile(org.springframework.core.env.Constants.DEV_ENV_NAME)
|
||||
@Component(org.springframework.core.env.Constants.DEV_BEAN_NAME)
|
||||
@Profile(org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DEV_ENV_NAME)
|
||||
@Component(org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.DEV_BEAN_NAME)
|
||||
class DevBean {
|
||||
}
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ package org.springframework.core.env.scan2;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Profile(org.springframework.core.env.Constants.PROD_ENV_NAME)
|
||||
@Component(org.springframework.core.env.Constants.PROD_BEAN_NAME)
|
||||
@Profile(org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.PROD_ENV_NAME)
|
||||
@Component(org.springframework.core.env.EnvironmentSystemIntegrationTests.Constants.PROD_BEAN_NAME)
|
||||
class ProdBean {
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "execution(" + getSignature() + ")";
|
||||
return "execution(" + getSignature().toString() + ")";
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public class AspectJProxyFactory extends ProxyCreatorSupport {
|
||||
|
||||
/**
|
||||
* Add the supplied aspect instance to the chain. The type of the aspect instance
|
||||
* supplied must be a singleton aspect. True singleton lifecycle is not honored when
|
||||
* supplied must be a singleton aspect. True singleton lifecycle is not honoured when
|
||||
* using this method - the caller is responsible for managing the lifecycle of any
|
||||
* aspects added in this way.
|
||||
* @param aspectInstance the AspectJ aspect instance
|
||||
|
||||
+2
-2
@@ -119,7 +119,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
|
||||
/**
|
||||
* The pointcut for Spring AOP to use.
|
||||
* Actual behavior of the pointcut will change depending on the state of the advice.
|
||||
* Actual behaviour of the pointcut will change depending on the state of the advice.
|
||||
*/
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
@@ -258,7 +258,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
|
||||
|
||||
/**
|
||||
* Pointcut implementation that changes its behavior when the advice is instantiated.
|
||||
* Pointcut implementation that changes its behaviour when the advice is instantiated.
|
||||
* Note that this is a <i>dynamic</i> pointcut; otherwise it might be optimized out
|
||||
* if it does not at first match statically.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ public class DelegatePerTargetObjectIntroductionInterceptor extends Introduction
|
||||
|
||||
/**
|
||||
* Subclasses may need to override this if they want to perform custom
|
||||
* behavior in around advice. However, subclasses should invoke this
|
||||
* behaviour in around advice. However, subclasses should invoke this
|
||||
* method, which handles introduced interfaces and forwarding to the target.
|
||||
*/
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
|
||||
|
||||
/**
|
||||
* Subclasses may need to override this if they want to perform custom
|
||||
* behavior in around advice. However, subclasses should invoke this
|
||||
* behaviour in around advice. However, subclasses should invoke this
|
||||
* method, which handles introduced interfaces and forwarding to the target.
|
||||
*/
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import org.springframework.aop.Pointcut;
|
||||
* Convenient superclass when we want to force subclasses to
|
||||
* implement MethodMatcher interface, but subclasses
|
||||
* will want to be pointcuts. The getClassFilter() method can
|
||||
* be overridden to customize ClassFilter behavior as well.
|
||||
* be overridden to customize ClassFilter behaviour as well.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
*/
|
||||
|
||||
+44
-36
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.annotation.EmptySpringAnnotation;
|
||||
import test.annotation.transaction.Tx;
|
||||
@@ -36,7 +37,6 @@ import org.springframework.beans.testfixture.beans.IOther;
|
||||
import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
@@ -50,22 +50,30 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class AspectJExpressionPointcutTests {
|
||||
|
||||
private final Method getAge = ClassUtils.getMethod(TestBean.class, "getAge");
|
||||
private final Method setAge = ClassUtils.getMethod(TestBean.class, "setAge", int.class);
|
||||
private final Method setSomeNumber = ClassUtils.getMethod(TestBean.class, "setSomeNumber", Number.class);
|
||||
private Method getAge;
|
||||
|
||||
private Method setAge;
|
||||
|
||||
private Method setSomeNumber;
|
||||
|
||||
private final Map<String, Method> methodsOnHasGeneric = new HashMap<>();
|
||||
|
||||
|
||||
AspectJExpressionPointcutTests() throws NoSuchMethodException {
|
||||
@BeforeEach
|
||||
void setup() throws NoSuchMethodException {
|
||||
getAge = TestBean.class.getMethod("getAge");
|
||||
setAge = TestBean.class.getMethod("setAge", int.class);
|
||||
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
|
||||
|
||||
// Assumes no overloading
|
||||
for (Method method : HasGeneric.class.getMethods()) {
|
||||
this.methodsOnHasGeneric.put(method.getName(), method);
|
||||
methodsOnHasGeneric.put(method.getName(), method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void matchExplicit() {
|
||||
void testMatchExplicit() {
|
||||
String expression = "execution(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -83,7 +91,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchWithTypePattern() {
|
||||
void testMatchWithTypePattern() {
|
||||
String expression = "execution(* *..TestBean.*Age(..))";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -102,12 +110,12 @@ class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@Test
|
||||
void thisCase() throws SecurityException, NoSuchMethodException{
|
||||
void testThis() throws SecurityException, NoSuchMethodException{
|
||||
testThisOrTarget("this");
|
||||
}
|
||||
|
||||
@Test
|
||||
void target() throws SecurityException, NoSuchMethodException {
|
||||
void testTarget() throws SecurityException, NoSuchMethodException {
|
||||
testThisOrTarget("target");
|
||||
}
|
||||
|
||||
@@ -131,12 +139,12 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void withinRootPackage() throws SecurityException, NoSuchMethodException {
|
||||
void testWithinRootPackage() throws SecurityException, NoSuchMethodException {
|
||||
testWithinPackage(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withinRootAndSubpackages() throws SecurityException, NoSuchMethodException {
|
||||
void testWithinRootAndSubpackages() throws SecurityException, NoSuchMethodException {
|
||||
testWithinPackage(true);
|
||||
}
|
||||
|
||||
@@ -160,7 +168,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void friendlyErrorOnNoLocationClassMatching() {
|
||||
void testFriendlyErrorOnNoLocationClassMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
|
||||
@@ -168,7 +176,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void friendlyErrorOnNoLocation2ArgMatching() {
|
||||
void testFriendlyErrorOnNoLocation2ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
|
||||
@@ -176,7 +184,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void friendlyErrorOnNoLocation3ArgMatching() {
|
||||
void testFriendlyErrorOnNoLocation3ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
|
||||
@@ -185,7 +193,7 @@ class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@Test
|
||||
void matchWithArgs() {
|
||||
void testMatchWithArgs() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -206,7 +214,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleAdvice() {
|
||||
void testSimpleAdvice() {
|
||||
String expression = "execution(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
CallCountingInterceptor interceptor = new CallCountingInterceptor();
|
||||
TestBean testBean = getAdvisedProxy(expression, interceptor);
|
||||
@@ -219,7 +227,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicMatchingProxy() {
|
||||
void testDynamicMatchingProxy() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
|
||||
CallCountingInterceptor interceptor = new CallCountingInterceptor();
|
||||
TestBean testBean = getAdvisedProxy(expression, interceptor);
|
||||
@@ -233,7 +241,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidExpression() {
|
||||
void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
|
||||
}
|
||||
@@ -263,20 +271,20 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void withUnsupportedPointcutPrimitive() {
|
||||
void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void andSubstitution() {
|
||||
void testAndSubstitution() {
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleAndSubstitutions() {
|
||||
void testMultipleAndSubstitutions() {
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
@@ -289,7 +297,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchGenericArgument() {
|
||||
void testMatchGenericArgument() {
|
||||
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -308,7 +316,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchVarargs() throws Exception {
|
||||
void testMatchVarargs() throws Exception {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class MyTemplate {
|
||||
@@ -334,19 +342,19 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchAnnotationOnClassWithAtWithin() throws Exception {
|
||||
void testMatchAnnotationOnClassWithAtWithin() throws Exception {
|
||||
String expression = "@within(test.annotation.transaction.Tx)";
|
||||
testMatchAnnotationOnClass(expression);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchAnnotationOnClassWithoutBinding() throws Exception {
|
||||
void testMatchAnnotationOnClassWithoutBinding() throws Exception {
|
||||
String expression = "within(@test.annotation.transaction.Tx *)";
|
||||
testMatchAnnotationOnClass(expression);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchAnnotationOnClassWithSubpackageWildcard() throws Exception {
|
||||
void testMatchAnnotationOnClassWithSubpackageWildcard() throws Exception {
|
||||
String expression = "within(@(test.annotation..*) *)";
|
||||
AspectJExpressionPointcut springAnnotatedPc = testMatchAnnotationOnClass(expression);
|
||||
assertThat(springAnnotatedPc.matches(TestBean.class.getMethod("setName", String.class), TestBean.class)).isFalse();
|
||||
@@ -358,7 +366,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchAnnotationOnClassWithExactPackageWildcard() throws Exception {
|
||||
void testMatchAnnotationOnClassWithExactPackageWildcard() throws Exception {
|
||||
String expression = "within(@(test.annotation.transaction.*) *)";
|
||||
testMatchAnnotationOnClass(expression);
|
||||
}
|
||||
@@ -376,7 +384,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationOnMethodWithFQN() throws Exception {
|
||||
void testAnnotationOnMethodWithFQN() throws Exception {
|
||||
String expression = "@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -390,7 +398,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationOnCglibProxyMethod() throws Exception {
|
||||
void testAnnotationOnCglibProxyMethod() throws Exception {
|
||||
String expression = "@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -402,7 +410,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void notAnnotationOnCglibProxyMethod() throws Exception {
|
||||
void testNotAnnotationOnCglibProxyMethod() throws Exception {
|
||||
String expression = "!@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -414,7 +422,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationOnDynamicProxyMethod() throws Exception {
|
||||
void testAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
String expression = "@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -426,7 +434,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void notAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
void testNotAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
String expression = "!@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -438,7 +446,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationOnMethodWithWildcard() throws Exception {
|
||||
void testAnnotationOnMethodWithWildcard() throws Exception {
|
||||
String expression = "execution(@(test.annotation..*) * *(..))";
|
||||
AspectJExpressionPointcut anySpringMethodAnnotation = new AspectJExpressionPointcut();
|
||||
anySpringMethodAnnotation.setExpression(expression);
|
||||
@@ -454,7 +462,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationOnMethodArgumentsWithFQN() throws Exception {
|
||||
void testAnnotationOnMethodArgumentsWithFQN() throws Exception {
|
||||
String expression = "@args(*, test.annotation.EmptySpringAnnotation))";
|
||||
AspectJExpressionPointcut takesSpringAnnotatedArgument2 = new AspectJExpressionPointcut();
|
||||
takesSpringAnnotatedArgument2.setExpression(expression);
|
||||
@@ -483,7 +491,7 @@ class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationOnMethodArgumentsWithWildcards() throws Exception {
|
||||
void testAnnotationOnMethodArgumentsWithWildcards() throws Exception {
|
||||
String expression = "execution(* *(*, @(test..*) *))";
|
||||
AspectJExpressionPointcut takesSpringAnnotatedArgument2 = new AspectJExpressionPointcut();
|
||||
takesSpringAnnotatedArgument2.setExpression(expression);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user