mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
269 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bd6aef090 | |||
| f41c7ceaf7 | |||
| 9a068869ef | |||
| d147609e04 | |||
| e9a359f873 | |||
| 78f0688ed0 | |||
| 504b7619bd | |||
| f9ae54d91e | |||
| 2284254d39 | |||
| 6e9607ce99 | |||
| b4bec4ca61 | |||
| 48da9524c3 | |||
| 68189f3de9 | |||
| 10bc93c058 | |||
| 9b93c948a7 | |||
| abe381488a | |||
| cc6dd19324 | |||
| 2fe3321813 | |||
| 8c3fc8c549 | |||
| d3ca6f9f6a | |||
| 2d7b2e59b6 | |||
| 46108deff4 | |||
| 4454b3b5ef | |||
| 2dd22f64e1 | |||
| 6be0432e3d | |||
| 6a5953dca3 | |||
| b4153618a4 | |||
| 0b09f1e12f | |||
| 120ea0a51c | |||
| c46d6286ee | |||
| 750cb73902 | |||
| 5851cdc679 | |||
| 2e833d908a | |||
| 728d5eeb74 | |||
| 89e34ae5ff | |||
| 9ee7c6383f | |||
| ee801d1b28 | |||
| eebdff23e7 | |||
| a2000dba33 | |||
| 4a3ef3e24a | |||
| 4a5dc7c1b0 | |||
| 347d085020 | |||
| f295def2a8 | |||
| dc2dbd9700 | |||
| 6b67972ec4 | |||
| 64fc9ee301 | |||
| ce43d1b1da | |||
| dc73ec76fc | |||
| 888e50175d | |||
| 1080c145e3 | |||
| f726e806cd | |||
| b1f6401e4f | |||
| 169b9abeef | |||
| e72b523995 | |||
| 99bdc4211d | |||
| d47c69746b | |||
| 052bbcc530 | |||
| f9791664ef | |||
| af44b3e6c0 | |||
| 2724c6d8fe | |||
| f7e5c9fbb2 | |||
| 4486ab1cb7 | |||
| 78c96b6d78 | |||
| 43bbe8f3e8 | |||
| 7d612e8958 | |||
| 9a38355896 | |||
| 3ecbc4de13 | |||
| 81c156eefb | |||
| d8c4a33bea | |||
| cfa47fa4fb | |||
| 80949eb30f | |||
| 4ed337247c | |||
| 81cdfafa78 | |||
| d5cb1d9adb | |||
| 9698dbc232 | |||
| 9c15b3fa4c | |||
| c559ec4dfb | |||
| 341ac76209 | |||
| 8ff102115a | |||
| f50a262cf2 | |||
| c570f3b2da | |||
| 0fdf759896 | |||
| c04d4da9a3 | |||
| b737f36f39 | |||
| 8d601384d3 | |||
| ea52ecc5e0 | |||
| 9b5febea20 | |||
| 7627d8c6fc | |||
| 20f91b7dc3 | |||
| e15c150696 | |||
| 7025b7aac2 | |||
| 1e432ff95d | |||
| ae17b11b70 | |||
| 1a783f41aa | |||
| 521fbfdb85 | |||
| 87377d6f3e | |||
| af2934c09b | |||
| 17ee82e004 | |||
| a82108ec73 | |||
| d4401cc69a | |||
| 2367314b52 | |||
| 80a3c1923f | |||
| c989cba963 | |||
| 3d4d68c26f | |||
| b61552b9df | |||
| 615973cce4 | |||
| 00577ed80a | |||
| 9b2b485444 | |||
| d586513d66 | |||
| 2b52582dff | |||
| 67958656e4 | |||
| a4db0e7448 | |||
| ef2cae36ac | |||
| efaf41862c | |||
| af5acb6d34 | |||
| 067638ae6e | |||
| 398cc01650 | |||
| 3a518b60e7 | |||
| 298f308ce1 | |||
| 6ffb74def3 | |||
| b55a4d3908 | |||
| db535863dd | |||
| 95a3f3bb6e | |||
| 84cce6018c | |||
| e97fc7be38 | |||
| 9eae0ba50e | |||
| f3c8102882 | |||
| 005d5ef922 | |||
| 5dc26460fb | |||
| 542502b2b6 | |||
| f6d8443781 | |||
| a44341ece3 | |||
| 969b18b0e8 | |||
| 2e9d6a1d4e | |||
| 7e5efdd8dd | |||
| 08e6df8832 | |||
| a738e4d5fd | |||
| 9c4b4ab81e | |||
| 0ee2d41528 | |||
| dc6ce30663 | |||
| 62fa3f11c1 | |||
| 2e56361fe4 | |||
| 9b0162da49 | |||
| ab98210e6d | |||
| 97ad479250 | |||
| 24d6565cad | |||
| e34ad6bf5f | |||
| 179b976964 | |||
| 1ff84671f8 | |||
| e1c22c5385 | |||
| 3f30a1540c | |||
| ae9153e644 | |||
| 003407a7e3 | |||
| a7764dc61d | |||
| 4b4778d569 | |||
| d96a63944c | |||
| ad7c090f4c | |||
| b9bad56fc1 | |||
| fdf0a6f6c7 | |||
| 86266b3d67 | |||
| 68cf3b928b | |||
| 3024c6efa9 | |||
| 500767a0fb | |||
| 5b5319a659 | |||
| 3ce7c52030 | |||
| bafcd1dc1c | |||
| 00b07659d9 | |||
| 9df94357de | |||
| 0e45f4cec4 | |||
| 8815788004 | |||
| b7e4fa16ca | |||
| 645d0db260 | |||
| f4b3c768e8 | |||
| 6b3bf554ce | |||
| c6121da151 | |||
| c5a75219ce | |||
| 89e7174cc4 | |||
| 4f16297e45 | |||
| 11898daed7 | |||
| 4d7da0059e | |||
| a4fcef4a62 | |||
| f2e267b494 | |||
| f9726ae0c8 | |||
| 199a675692 | |||
| 2f921dd13d | |||
| b92877990d | |||
| d7778c0212 | |||
| 531ac89e7e | |||
| 358555929d | |||
| 9230a7db16 | |||
| 7e53a1f048 | |||
| 45a1f98bd6 | |||
| 5faace0eb3 | |||
| 5656eaccb7 | |||
| 3b2f6e74a6 | |||
| 484aee069e | |||
| def7075695 | |||
| 2daa074561 | |||
| 6bd7f0231d | |||
| 70d9f7c62c | |||
| 5856d2e54e | |||
| a8fa98e2a6 | |||
| 73725905ba | |||
| 1e71d3d363 | |||
| c3127249ac | |||
| 91b980f5cb | |||
| d9e86dd68c | |||
| 11c8b22c5a | |||
| 1a52c56bd4 | |||
| 4cc91a2869 | |||
| 17cef18760 | |||
| 00bda65848 | |||
| 6697461003 | |||
| c820e44a99 | |||
| 472dcdb59c | |||
| 6691ff2072 | |||
| 9c6e55939e | |||
| 7e511931b3 | |||
| 4bd898c359 | |||
| 1fba430dfc | |||
| efe85c0d70 | |||
| 2ec0c16889 | |||
| 899de4f3bf | |||
| d1d9d483fe | |||
| 375e0e6827 | |||
| b8b31ff8a1 | |||
| c5c77b93fe | |||
| 6b905049eb | |||
| 88a7ca0b0a | |||
| e8012a64c3 | |||
| b9d366d203 | |||
| f5b0d9509d | |||
| 699da7c383 | |||
| 5bf74cae11 | |||
| 46128cb4b9 | |||
| 5dd48fc068 | |||
| 0ada78ad84 | |||
| 682f4715cf | |||
| c4a34fa26c | |||
| e3f185a696 | |||
| 1b312b6f3f | |||
| dc5a21fbd1 | |||
| e1c8a84fec | |||
| 410fe409d2 | |||
| e0c5068d0b | |||
| faba044735 | |||
| 4d885f9aad | |||
| fdf187ec46 | |||
| 0c42965fc3 | |||
| e1236a8672 | |||
| 7daff59cb1 | |||
| 5d309d5724 | |||
| eeb145ee0b | |||
| 9f5cda958e | |||
| 47779d6a53 | |||
| 2a43cc7574 | |||
| c4831d2586 | |||
| e4569defd9 | |||
| 65cb59517d | |||
| b729008f4d | |||
| 122d8b9e4e | |||
| b16f379788 | |||
| c868bc554f | |||
| 5c77c3739e | |||
| ee04442be7 | |||
| f067ff862b | |||
| 68864674cc | |||
| f1a335708a | |||
| d786635239 |
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.9-librca
|
||||
java=17.0.10-librca
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ plugins {
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.50.0'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
@@ -104,7 +104,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.10.1/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.12.7");
|
||||
checkstyle.setToolVersion("10.13.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x[Spring Framework 6.0.x].
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
@@ -51,7 +51,7 @@ The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.0.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:jammy-20231211.1
|
||||
FROM ubuntu:jammy-20240125
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -3,13 +3,13 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://download.bell-sw.com/java/17.0.9+11/bellsoft-jdk17.0.9+11-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
|
||||
;;
|
||||
java21)
|
||||
echo "https://download.bell-sw.com/java/21.0.1+12/bellsoft-jdk21.0.1+12-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java22)
|
||||
echo "https://download.java.net/java/early_access/jdk22/28/GPL/openjdk-22-ea+28_linux-x64_bin.tar.gz"
|
||||
echo "https://download.java.net/java/early_access/jdk22/33/GPL/openjdk-22-ea+33_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
@@ -18,6 +18,7 @@ asciidoc:
|
||||
# FIXME: The package is not renamed
|
||||
chomp: 'all'
|
||||
fold: 'all'
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
|
||||
@@ -28,8 +28,8 @@ antora {
|
||||
'@antora/atlas-extension': '1.0.0-alpha.1',
|
||||
'@antora/collector-extension': '1.0.0-alpha.3',
|
||||
'@asciidoctor/tabs': '1.0.0-beta.3',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0-alpha.2',
|
||||
'@springio/antora-extensions': '1.3.0',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0',
|
||||
'@springio/antora-extensions': '1.8.2',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/checkpoint-restore.adoc[]
|
||||
** xref:integration/class-data-sharing.adoc[]
|
||||
** xref:integration/cds.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
|
||||
@@ -19,7 +19,7 @@ of the classpath -- for example, deployed within the application's JAR file.
|
||||
The following table lists all currently supported Spring properties.
|
||||
|
||||
.Supported Spring Properties
|
||||
[cols="1,1", stripes=odd]
|
||||
[cols="1,1"]
|
||||
|===
|
||||
| Name | Description
|
||||
|
||||
|
||||
@@ -521,8 +521,8 @@ standard AspectJ. The following example shows the `aop.xml` file:
|
||||
<aspectj>
|
||||
|
||||
<weaver>
|
||||
<!-- only weave classes in our application-specific packages -->
|
||||
<include within="com.xyz.*"/>
|
||||
<!-- only weave classes in our application-specific packages and sub-packages -->
|
||||
<include within="com.xyz..*"/>
|
||||
</weaver>
|
||||
|
||||
<aspects>
|
||||
@@ -731,8 +731,8 @@ For example:
|
||||
<aspectj>
|
||||
|
||||
<weaver>
|
||||
<!-- only weave classes in our application-specific packages -->
|
||||
<include within="com.xyz.*"/>
|
||||
<!-- only weave classes in our application-specific packages and sub-packages -->
|
||||
<include within="com.xyz..*"/>
|
||||
</weaver>
|
||||
|
||||
</aspectj>
|
||||
|
||||
@@ -17,8 +17,11 @@ Applying such optimizations early implies the following restrictions:
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time (see related {spring-framework-issues}/29555[spring-framework#29555] issue).
|
||||
* Make sure that the bean type is as precise as possible.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.
|
||||
* Beans registered as singletons (using `registerSingleton`, typically from
|
||||
`ConfigurableListableBeanFactory`) cannot be transformed ahead-of-time either.
|
||||
* As we cannot rely on the instance, make sure that the bean type is as precise as
|
||||
possible.
|
||||
|
||||
TIP: See also the xref:core/aot.adoc#aot.bestpractices[] section.
|
||||
|
||||
@@ -27,7 +30,7 @@ A Spring AOT processed application typically generates:
|
||||
|
||||
* Java source code
|
||||
* Bytecode (usually for dynamic proxies)
|
||||
* {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies.
|
||||
* {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies
|
||||
|
||||
NOTE: At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM.
|
||||
We intend to support more JVM-based use cases in future generations.
|
||||
@@ -35,7 +38,7 @@ We intend to support more JVM-based use cases in future generations.
|
||||
[[aot.basics]]
|
||||
== AOT engine overview
|
||||
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` arrangement is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
|
||||
* Refresh an `ApplicationContext` for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances.
|
||||
* Invoke the available `BeanFactoryInitializationAotProcessor` implementations and apply their contributions against the `GenerationContext`.
|
||||
@@ -67,7 +70,14 @@ include-code::./AotProcessingSample[tag=aotcontext]
|
||||
In this mode, xref:core/beans/factory-extension.adoc#beans-factory-extension-factory-postprocessors[`BeanFactoryPostProcessor` implementations] are invoked as usual.
|
||||
This includes configuration class parsing, import selectors, classpath scanning, etc.
|
||||
Such steps make sure that the `BeanRegistry` contains the relevant bean definitions for the application.
|
||||
If bean definitions are guarded by conditions (such as `@Profile`), these are discarded at this stage.
|
||||
If bean definitions are guarded by conditions (such as `@Profile`), these are evaluated,
|
||||
and bean definitions that don't match their conditions are discarded at this stage.
|
||||
|
||||
If custom code needs to register extra beans programmatically, make sure that custom
|
||||
registration code uses `BeanDefinitionRegistry` instead of `BeanFactory` as only bean
|
||||
definitions are taken into account. A good pattern is to implement
|
||||
`ImportBeanDefinitionRegistrar` and register it via an `@Import` on one of your
|
||||
configuration classes.
|
||||
|
||||
Because this mode does not actually create bean instances, `BeanPostProcessor` implementations are not invoked, except for specific variants that are relevant for AOT processing.
|
||||
These are:
|
||||
@@ -84,12 +94,12 @@ Once this part completes, the `BeanFactory` contains the bean definitions that a
|
||||
Components that want to participate in this step can implement the {spring-framework-api}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface.
|
||||
Each implementation can return an AOT contribution, based on the state of the bean factory.
|
||||
|
||||
An AOT contribution is a component that contributes generated code that reproduces a particular behavior.
|
||||
An AOT contribution is a component that contributes generated code which reproduces a particular behavior.
|
||||
It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxies.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
|
||||
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` can also be implemented directly by a bean.
|
||||
The `BeanFactoryInitializationAotProcessor` interface can also be implemented directly by a bean.
|
||||
In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime.
|
||||
Consequently, such a bean is automatically excluded from the AOT-optimized context.
|
||||
|
||||
@@ -111,7 +121,7 @@ This interface is used as follows:
|
||||
|
||||
* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior.
|
||||
For instance xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[`AutowiredAnnotationBeanPostProcessor`] implements this interface to generate code that injects members annotated with `@Autowired`.
|
||||
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
|
||||
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
|
||||
Typically used when the bean definition needs to be tuned for specific features of the core framework.
|
||||
|
||||
[NOTE]
|
||||
@@ -207,11 +217,33 @@ However, keep in mind that some optimizations are made at build time based on a
|
||||
|
||||
This section lists the best practices that make sure your application is ready for AOT.
|
||||
|
||||
[[aot.bestpractices.bean-registration]]
|
||||
== Programmatic bean registration
|
||||
|
||||
The AOT engine takes care of the `@Configuration` model and any callback that might be
|
||||
invoked as part of processing your configuration. If you need to register additional
|
||||
beans programmatically, make sure to use a `BeanDefinitionRegistry` to register
|
||||
bean definitions.
|
||||
|
||||
This can typically be done via a `BeanDefinitionRegistryPostProcessor`. Note that, if it
|
||||
is registered itself as a bean, it will be invoked again at runtime unless you make
|
||||
sure to implement `BeanFactoryInitializationAotProcessor` as well. A more idiomatic
|
||||
way is to implement `ImportBeanDefinitionRegistrar` and register it using `@Import` on
|
||||
one of your configuration classes. This invokes your custom code as part of configuration
|
||||
class parsing.
|
||||
|
||||
If you declare additional beans programmatically using a different callback, they are
|
||||
likely not going to be handled by the AOT engine, and therefore no hints are going to be
|
||||
generated for them. Depending on the environment, those beans may not be registered at
|
||||
all. For instance, classpath scanning does not work in a native image as there is no
|
||||
notion of a classpath. For cases like this, it is crucial that the scanning happens at
|
||||
build time.
|
||||
|
||||
[[aot.bestpractices.bean-type]]
|
||||
=== Expose The Most Precise Bean Type
|
||||
|
||||
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
|
||||
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members, or lifecycle callback methods.
|
||||
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
|
||||
|
||||
For `@Configuration` classes, make sure that the return type of the factory `@Bean` method is as precise as possible.
|
||||
Consider the following example:
|
||||
@@ -262,10 +294,11 @@ If you are registering bean definitions programmatically, consider using `RootBe
|
||||
|
||||
[[aot.bestpractices.constructors]]
|
||||
=== Avoid Multiple Constructors
|
||||
|
||||
The container is able to choose the most appropriate constructor to use based on several candidates.
|
||||
However, this is not a best practice and flagging the preferred constructor with `@Autowired` if necessary is preferred.
|
||||
|
||||
In case you are working on a code base that you can't modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
@@ -283,7 +316,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -380,7 +413,7 @@ Java::
|
||||
|
||||
Running an application as a native image requires additional information compared to a regular JVM runtime.
|
||||
For instance, GraalVM needs to know ahead of time if a component uses reflection.
|
||||
Similarly, classpath resources are not shipped in a native image unless specified explicitly.
|
||||
Similarly, classpath resources are not included in a native image unless specified explicitly.
|
||||
Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.
|
||||
|
||||
The {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
|
||||
@@ -415,7 +448,7 @@ include-code::./SpellCheckService[]
|
||||
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
|
||||
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
|
||||
|
||||
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the `RuntimeHintsRegistrar` interface.
|
||||
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the `RuntimeHintsRegistrar` interface.
|
||||
|
||||
|
||||
[[aot.hints.reflective]]
|
||||
@@ -424,7 +457,7 @@ It is also possible to register an implementation statically by adding an entry
|
||||
{spring-framework-api}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
|
||||
For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection.
|
||||
|
||||
By default, only Spring beans are considered and an invocation hint is registered for the annotated element.
|
||||
By default, only Spring beans are considered, and an invocation hint is registered for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
|
||||
`@Reflective` annotation.
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ Kotlin::
|
||||
====
|
||||
After you learn about Spring's IoC container, you may want to know more about Spring's
|
||||
`Resource` abstraction (as described in
|
||||
xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
|
||||
xref:core/resources.adoc[Resources])
|
||||
which provides a convenient mechanism for reading an InputStream from locations defined
|
||||
in a URI syntax. In particular, `Resource` paths are used to construct applications contexts,
|
||||
as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
[[expressions]]
|
||||
= Spring Expression Language (SpEL)
|
||||
|
||||
The Spring Expression Language ("`SpEL`" for short) is a powerful expression language that
|
||||
The Spring Expression Language ("SpEL" for short) is a powerful expression language that
|
||||
supports querying and manipulating an object graph at runtime. The language syntax is
|
||||
similar to Unified EL but offers additional features, most notably method invocation and
|
||||
basic string templating functionality.
|
||||
similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Expression
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
@@ -33,26 +34,24 @@ populate them are listed at the end of the chapter.
|
||||
The expression language supports the following functionality:
|
||||
|
||||
* Literal expressions
|
||||
* Boolean and relational operators
|
||||
* Regular expressions
|
||||
* Class expressions
|
||||
* Accessing properties, arrays, lists, and maps
|
||||
* Method invocation
|
||||
* Assignment
|
||||
* Calling constructors
|
||||
* Bean references
|
||||
* Array construction
|
||||
* Inline lists
|
||||
* Inline maps
|
||||
* Ternary operator
|
||||
* Array construction
|
||||
* Relational operators
|
||||
* Regular expressions
|
||||
* Logical operators
|
||||
* String operators
|
||||
* Mathematical operators
|
||||
* Assignment
|
||||
* Type expressions
|
||||
* Method invocation
|
||||
* Constructor invocation
|
||||
* Variables
|
||||
* User-defined functions added to the context
|
||||
* reflective invocation of `Method`
|
||||
* various cases of `MethodHandle`
|
||||
* User-defined functions
|
||||
* Bean references
|
||||
* Ternary, Elvis, and safe-navigation operators
|
||||
* Collection projection
|
||||
* Collection selection
|
||||
* Templated expressions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -517,6 +517,7 @@ following kinds of expressions cannot be compiled.
|
||||
* Expressions involving assignment
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers or accessors
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using selection or projection
|
||||
|
||||
Compilation of additional kinds of expressions may be supported in the future.
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
This section lists the classes used in the examples throughout this chapter.
|
||||
|
||||
== `Inventor`
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Inventor.Java::
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
@@ -80,7 +82,7 @@ Inventor.Java::
|
||||
}
|
||||
----
|
||||
|
||||
Inventor.kt::
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
@@ -95,9 +97,11 @@ Inventor.kt::
|
||||
----
|
||||
======
|
||||
|
||||
== `PlaceOfBirth`
|
||||
|
||||
[tabs]
|
||||
======
|
||||
PlaceOfBirth.java::
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
@@ -135,7 +139,7 @@ PlaceOfBirth.java::
|
||||
}
|
||||
----
|
||||
|
||||
PlaceOfBirth.kt::
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
@@ -145,9 +149,11 @@ PlaceOfBirth.kt::
|
||||
----
|
||||
======
|
||||
|
||||
== `Society`
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Society.java::
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
@@ -192,7 +198,7 @@ Society.java::
|
||||
}
|
||||
----
|
||||
|
||||
Society.kt::
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
|
||||
+13
-4
@@ -13,16 +13,18 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// returns ['Smiljan', 'Idvor' ]
|
||||
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
.getValue(societyContext, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// returns ['Smiljan', 'Idvor' ]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
.getValue(societyContext) as List<*>
|
||||
----
|
||||
======
|
||||
|
||||
@@ -32,5 +34,12 @@ evaluated against each entry in the map (represented as a Java `Map.Entry`). The
|
||||
of a projection across a map is a list that consists of the evaluation of the projection
|
||||
expression against each map entry.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The Spring Expression Language also supports safe navigation for collection projection.
|
||||
|
||||
See
|
||||
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
|
||||
for details.
|
||||
====
|
||||
|
||||
|
||||
+19
-11
@@ -28,13 +28,14 @@ Kotlin::
|
||||
======
|
||||
|
||||
Selection is supported for arrays and anything that implements `java.lang.Iterable` or
|
||||
`java.util.Map`. For a list or array, the selection criteria is evaluated against each
|
||||
individual element. Against a map, the selection criteria is evaluated against each map
|
||||
entry (objects of the Java type `Map.Entry`). Each map entry has its `key` and `value`
|
||||
accessible as properties for use in the selection.
|
||||
`java.util.Map`. For an array or `Iterable`, the selection expression is evaluated
|
||||
against each individual element. Against a map, the selection expression is evaluated
|
||||
against each map entry (objects of the Java type `Map.Entry`). Each map entry has its
|
||||
`key` and `value` accessible as properties for use in the selection.
|
||||
|
||||
The following expression returns a new map that consists of those elements of the
|
||||
original map where the entry's value is less than 27:
|
||||
Given a `Map` stored in a variable named `#map`, the following expression returns a new
|
||||
map that consists of those elements of the original map where the entry's value is less
|
||||
than 27:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -42,21 +43,28 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
|
||||
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val newMap = parser.parseExpression("map.?[value<27]").getValue()
|
||||
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map
|
||||
----
|
||||
======
|
||||
|
||||
In addition to returning all the selected elements, you can retrieve only the first or
|
||||
the last element. To obtain the first element matching the selection, the syntax is
|
||||
`.^[selectionExpression]`. To obtain the last matching selection, the syntax is
|
||||
`.$[selectionExpression]`.
|
||||
the last element. To obtain the first element matching the selection expression, the
|
||||
syntax is `.^[selectionExpression]`. To obtain the last element matching the selection
|
||||
expression, the syntax is `.$[selectionExpression]`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The Spring Expression Language also supports safe navigation for collection selection.
|
||||
|
||||
See
|
||||
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
|
||||
for details.
|
||||
====
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
[[expressions-ref-functions]]
|
||||
= Functions
|
||||
|
||||
You can extend SpEL by registering user-defined functions that can be called within the
|
||||
expression string. The function is registered through the `EvaluationContext`. The
|
||||
following example shows how to register a user-defined function to be invoked via reflection
|
||||
(i.e. a `Method`):
|
||||
You can extend SpEL by registering user-defined functions that can be called within
|
||||
expressions by using the `#functionName(...)` syntax. Functions can be registered as
|
||||
variables in `EvaluationContext` implementations via the `setVariable()` method.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
`StandardEvaluationContext` also defines `registerFunction(...)` methods that provide a
|
||||
convenient way to register a function as a `java.lang.reflect.Method` or a
|
||||
`java.lang.invoke.MethodHandle`.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Since functions share a common namespace with
|
||||
xref:core/expressions/language-ref/variables.adoc[variables] in the evaluation context,
|
||||
care must be taken to ensure that function names and variable names do not overlap.
|
||||
====
|
||||
|
||||
The following example shows how to register a user-defined function to be invoked via
|
||||
reflection using a `java.lang.reflect.Method`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -40,11 +56,7 @@ Java::
|
||||
public abstract class StringUtils {
|
||||
|
||||
public static String reverseString(String input) {
|
||||
StringBuilder backwards = new StringBuilder(input.length());
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
backwards.append(input.charAt(input.length() - 1 - i));
|
||||
}
|
||||
return backwards.toString();
|
||||
return new StringBuilder(input).reverse().toString();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -54,16 +66,12 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun reverseString(input: String): String {
|
||||
val backwards = StringBuilder(input.length)
|
||||
for (i in 0 until input.length) {
|
||||
backwards.append(input[input.length - 1 - i])
|
||||
}
|
||||
return backwards.toString()
|
||||
return StringBuilder(input).reverse().toString()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can then register and use the preceding method, as the following example shows:
|
||||
You can register and use the preceding method, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -75,8 +83,9 @@ Java::
|
||||
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
context.setVariable("reverseString",
|
||||
StringUtils.class.getDeclaredMethod("reverseString", String.class));
|
||||
StringUtils.class.getMethod("reverseString", String.class));
|
||||
|
||||
// evaluates to "olleh"
|
||||
String helloWorldReversed = parser.parseExpression(
|
||||
"#reverseString('hello')").getValue(context, String.class);
|
||||
----
|
||||
@@ -88,16 +97,18 @@ Kotlin::
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
context.setVariable("reverseString", ::reverseString::javaMethod)
|
||||
context.setVariable("reverseString", ::reverseString.javaMethod)
|
||||
|
||||
// evaluates to "olleh"
|
||||
val helloWorldReversed = parser.parseExpression(
|
||||
"#reverseString('hello')").getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
The use of `MethodHandle` is also supported. This enables potentially more efficient use
|
||||
cases if the `MethodHandle` target and parameters have been fully bound prior to
|
||||
registration, but partially bound handles are also supported.
|
||||
A function can also be registered as a `java.lang.invoke.MethodHandle`. This enables
|
||||
potentially more efficient use cases if the `MethodHandle` target and parameters have
|
||||
been fully bound prior to registration; however, partially bound handles are also
|
||||
supported.
|
||||
|
||||
Consider the `String#formatted(String, Object...)` instance method, which produces a
|
||||
message according to a template and a variable number of arguments.
|
||||
@@ -118,9 +129,9 @@ Java::
|
||||
MethodType.methodType(String.class, Object[].class));
|
||||
context.setVariable("message", mh);
|
||||
|
||||
// evaluates to "Simple message: <Hello World>"
|
||||
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String.class);
|
||||
//returns "Simple message: <Hello World>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -134,6 +145,7 @@ Kotlin::
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
context.setVariable("message", mh)
|
||||
|
||||
// evaluates to "Simple message: <Hello World>"
|
||||
val message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
@@ -161,9 +173,9 @@ Java::
|
||||
.bindTo(varargs); //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh);
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
String message = parser.parseExpression("#message()")
|
||||
.getValue(context, String.class);
|
||||
//returns "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -182,6 +194,7 @@ Kotlin::
|
||||
.bindTo(varargs) //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh)
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
val message = parser.parseExpression("#message()")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
|
||||
@@ -3,20 +3,46 @@
|
||||
|
||||
SpEL supports the following types of literal expressions.
|
||||
|
||||
- strings
|
||||
- numeric values: integer (`int` or `long`), hexadecimal (`int` or `long`), real (`float`
|
||||
or `double`)
|
||||
- boolean values: `true` or `false`
|
||||
- null
|
||||
String ::
|
||||
Strings can be delimited by single quotation marks (`'`) or double quotation marks
|
||||
(`"`). To include a single quotation mark within a string literal enclosed in single
|
||||
quotation marks, use two adjacent single quotation mark characters. Similarly, to
|
||||
include a double quotation mark within a string literal enclosed in double quotation
|
||||
marks, use two adjacent double quotation mark characters.
|
||||
Number ::
|
||||
Numbers support the use of the negative sign, exponential notation, and decimal points.
|
||||
* Integer: `int` or `long`
|
||||
* Hexadecimal: `int` or `long`
|
||||
* Real: `float` or `double`
|
||||
** By default, real numbers are parsed using `Double.parseDouble()`.
|
||||
Boolean ::
|
||||
`true` or `false`
|
||||
Null ::
|
||||
`null`
|
||||
|
||||
Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To
|
||||
include a single quotation mark within a string literal enclosed in single quotation
|
||||
marks, use two adjacent single quotation mark characters. Similarly, to include a double
|
||||
quotation mark within a string literal enclosed in double quotation marks, use two
|
||||
adjacent double quotation mark characters.
|
||||
[NOTE]
|
||||
====
|
||||
Due to the design and implementation of the Spring Expression Language, literal numbers
|
||||
are always stored internally as positive numbers.
|
||||
|
||||
Numbers support the use of the negative sign, exponential notation, and decimal points.
|
||||
By default, real numbers are parsed by using `Double.parseDouble()`.
|
||||
For example, `-2` is stored internally as a positive `2` which is then negated while
|
||||
evaluating the expression (by calculating the value of `0 - 2`).
|
||||
|
||||
This means that it is not possible to represent a negative literal number equal to the
|
||||
minimum value of that type of number in Java. For example, the minimum supported value
|
||||
for an `int` in Java is `Integer.MIN_VALUE` which has a value of `-2147483648`. However,
|
||||
if you include `-2147483648` in a SpEL expression, an exception will be thrown informing
|
||||
you that the value `2147483648` cannot be parsed as an `int` (because it exceeds the
|
||||
value of `Integer.MAX_VALUE` which is `2147483647`).
|
||||
|
||||
If you need to use the minimum value for a particular type of number within a SpEL
|
||||
expression, you can either reference the `MIN_VALUE` constant for the respective wrapper
|
||||
type (such as `Integer.MIN_VALUE`, `Long.MIN_VALUE`, etc.) or calculate the minimum
|
||||
value. For example, to use the minimum integer value:
|
||||
|
||||
- `T(Integer).MIN_VALUE` -- requires a `StandardEvaluationContext`
|
||||
- `-2^31` -- can be used with any type of `EvaluationContext`
|
||||
====
|
||||
|
||||
The following listing shows simple usage of literals. Typically, they are not used in
|
||||
isolation like this but, rather, as part of a more complex expression -- for example,
|
||||
|
||||
+326
-14
@@ -1,12 +1,27 @@
|
||||
[[expressions-operator-safe-navigation]]
|
||||
= Safe Navigation Operator
|
||||
|
||||
The safe navigation operator is used to avoid a `NullPointerException` and comes from
|
||||
the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify that
|
||||
it is not null before accessing methods or properties of the object. To avoid this, the
|
||||
safe navigation operator returns null instead of throwing an exception. The following
|
||||
example shows how to use the safe navigation operator:
|
||||
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
|
||||
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify
|
||||
that it is not `null` before accessing methods or properties of the object. To avoid
|
||||
this, the safe navigation operator returns `null` for the particular null-safe operation
|
||||
instead of throwing an exception.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
When the safe navigation operator evaluates to `null` for a particular null-safe
|
||||
operation within a compound expression, the remainder of the compound expression will
|
||||
still be evaluated.
|
||||
|
||||
See <<expressions-operator-safe-navigation-compound-expressions>> for details.
|
||||
====
|
||||
|
||||
[[expressions-operator-safe-navigation-property-access]]
|
||||
== Safe Property and Method Access
|
||||
|
||||
The following example shows how to use the safe navigation operator for property access
|
||||
(`?.`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -20,13 +35,18 @@ Java::
|
||||
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
|
||||
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
|
||||
|
||||
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
|
||||
System.out.println(city); // Smiljan
|
||||
// evaluates to "Smiljan"
|
||||
String city = parser.parseExpression("placeOfBirth?.city") // <1>
|
||||
.getValue(context, tesla, String.class);
|
||||
|
||||
tesla.setPlaceOfBirth(null);
|
||||
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
|
||||
System.out.println(city); // null - does not throw NullPointerException!!!
|
||||
|
||||
// evaluates to null - does not throw NullPointerException
|
||||
city = parser.parseExpression("placeOfBirth?.city") // <2>
|
||||
.getValue(context, tesla, String.class);
|
||||
----
|
||||
<1> Use safe navigation operator on non-null `placeOfBirth` property
|
||||
<2> Use safe navigation operator on null `placeOfBirth` property
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -38,14 +58,306 @@ Kotlin::
|
||||
val tesla = Inventor("Nikola Tesla", "Serbian")
|
||||
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
|
||||
|
||||
var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
|
||||
println(city) // Smiljan
|
||||
// evaluates to "Smiljan"
|
||||
var city = parser.parseExpression("placeOfBirth?.city") // <1>
|
||||
.getValue(context, tesla, String::class.java)
|
||||
|
||||
tesla.setPlaceOfBirth(null)
|
||||
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
|
||||
println(city) // null - does not throw NullPointerException!!!
|
||||
|
||||
// evaluates to null - does not throw NullPointerException
|
||||
city = parser.parseExpression("placeOfBirth?.city") // <2>
|
||||
.getValue(context, tesla, String::class.java)
|
||||
----
|
||||
<1> Use safe navigation operator on non-null `placeOfBirth` property
|
||||
<2> Use safe navigation operator on null `placeOfBirth` property
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The safe navigation operator also applies to method invocations on an object.
|
||||
|
||||
For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`#calculator` variable has not been configured in the context. Otherwise, the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
|
||||
The Spring Expression Language supports safe navigation for
|
||||
xref:core/expressions/language-ref/collection-selection.adoc[collection selection] and
|
||||
xref:core/expressions/language-ref/collection-projection.adoc[collection projection] via
|
||||
the following operators.
|
||||
|
||||
* null-safe selection: `?.?`
|
||||
* null-safe select first: `?.^`
|
||||
* null-safe select last: `?.$`
|
||||
* null-safe projection: `?.!`
|
||||
|
||||
The following example shows how to use the safe navigation operator for collection
|
||||
selection (`?.?`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.?[nationality == 'Serbian']"; // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
----
|
||||
<1> Use null-safe selection operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression = "members?.?[nationality == 'Serbian']" // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
var list = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Inventor>
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Inventor>
|
||||
----
|
||||
<1> Use null-safe selection operator on potentially null `members` list
|
||||
======
|
||||
|
||||
The following example shows how to use the "null-safe select first" operator for
|
||||
collections (`?.^`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression =
|
||||
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression =
|
||||
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the "null-safe select last" operator for
|
||||
collections (`?.$`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression =
|
||||
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use "null-safe select last" operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression =
|
||||
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
var inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use "null-safe select last" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
The following example shows how to use the safe navigation operator for collection
|
||||
projection (`?.!`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List.class);
|
||||
----
|
||||
<1> Use null-safe projection operator on non-null `members` list
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List::class.java)
|
||||
----
|
||||
<1> Use null-safe projection operator on non-null `members` list
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
======
|
||||
|
||||
|
||||
[[expressions-operator-safe-navigation-compound-expressions]]
|
||||
== Null-safe Operations in Compound Expressions
|
||||
|
||||
As mentioned at the beginning of this section, when the safe navigation operator
|
||||
evaluates to `null` for a particular null-safe operation within a compound expression,
|
||||
the remainder of the compound expression will still be evaluated. This means that the
|
||||
safe navigation operator must be applied throughout a compound expression in order to
|
||||
avoid any unwanted `NullPointerException`.
|
||||
|
||||
Given the expression `#person?.address.city`, if `#person` is `null` the safe navigation
|
||||
operator (`?.`) ensures that no exception will be thrown when attempting to access the
|
||||
`address` property of `#person`. However, since `#person?.address` evaluates to `null`, a
|
||||
`NullPointerException` will be thrown when attempting to access the `city` property of
|
||||
`null`. To address that, you can apply null-safe navigation throughout the compound
|
||||
expression as in `#person?.address?.city`. That expression will safely evaluate to `null`
|
||||
if either `#person` or `#person?.address` evaluates to `null`.
|
||||
|
||||
The following example demonstrates how to use the "null-safe select first" operator
|
||||
(`?.^`) on a collection combined with null-safe property access (`?.`) within a compound
|
||||
expression. If `members` is `null`, the result of the "null-safe select first" operator
|
||||
(`members?.^[nationality == 'Serbian']`) evaluates to `null`, and the additional use of
|
||||
the safe navigation operator (`?.name`) ensures that the entire compound expression
|
||||
evaluates to `null` instead of throwing an exception.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression(expression)
|
||||
.getValue(context, String.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
name = parser.parseExpression(expression)
|
||||
.getValue(context, String.class);
|
||||
----
|
||||
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression = "members?.^[nationality == 'Serbian']?.name" // <1>
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression(expression)
|
||||
.getValue(context, String::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
name = parser.parseExpression(expression)
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
|
||||
======
|
||||
|
||||
@@ -5,8 +5,11 @@ The Spring Expression Language supports the following kinds of operators:
|
||||
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-relational[Relational Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-logical[Logical Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-string[String Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-mathematical[Mathematical Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-assignment[The Assignment Operator]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-overloaded[Overloaded Operators]
|
||||
|
||||
|
||||
|
||||
[[expressions-operators-relational]]
|
||||
@@ -15,7 +18,7 @@ The Spring Expression Language supports the following kinds of operators:
|
||||
The relational operators (equal, not equal, less than, less than or equal, greater than,
|
||||
and greater than or equal) are supported by using standard operator notation.
|
||||
These operators work on `Number` types as well as types implementing `Comparable`.
|
||||
The following listing shows a few examples of operators:
|
||||
The following listing shows a few examples of relational operators:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -65,53 +68,9 @@ If you prefer numeric comparisons instead, avoid number-based `null` comparisons
|
||||
in favor of comparisons against zero (for example, `X > 0` or `X < 0`).
|
||||
====
|
||||
|
||||
In addition to the standard relational operators, SpEL supports the `instanceof` and regular
|
||||
expression-based `matches` operator. The following listing shows examples of both:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to false
|
||||
boolean falseValue = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
boolean trueValue = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
boolean falseValue = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to false
|
||||
val falseValue = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
val trueValue = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
val falseValue = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
CAUTION: Be careful with primitive types, as they are immediately boxed up to their
|
||||
wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while
|
||||
`1 instanceof T(Integer)` evaluates to `true`, as expected.
|
||||
|
||||
Each symbolic operator can also be specified as a purely alphabetic equivalent. This
|
||||
avoids problems where the symbols used have special meaning for the document type in
|
||||
which the expression is embedded (such as in an XML document). The textual equivalents are:
|
||||
Each symbolic operator can also be specified as a purely textual equivalent. This avoids
|
||||
problems where the symbols used have special meaning for the document type in which the
|
||||
expression is embedded (such as in an XML document). The textual equivalents are:
|
||||
|
||||
* `lt` (`<`)
|
||||
* `gt` (`>`)
|
||||
@@ -119,22 +78,117 @@ which the expression is embedded (such as in an XML document). The textual equiv
|
||||
* `ge` (`>=`)
|
||||
* `eq` (`==`)
|
||||
* `ne` (`!=`)
|
||||
* `div` (`/`)
|
||||
* `mod` (`%`)
|
||||
* `not` (`!`).
|
||||
|
||||
All of the textual operators are case-insensitive.
|
||||
|
||||
In addition to the standard relational operators, SpEL supports the `between`,
|
||||
`instanceof`, and regular expression-based `matches` operators. The following listing
|
||||
shows examples of all three:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
boolean result;
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"1 between {1, 5}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"1 between {10, 15}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"123 instanceof T(Integer)").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to true
|
||||
var result = parser.parseExpression(
|
||||
"1 between {1, 5}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"1 between {10, 15}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"123 instanceof T(Integer)").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
|
||||
|
||||
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
|
||||
to `false`.
|
||||
====
|
||||
|
||||
CAUTION: Be careful with primitive types, as they are immediately boxed up to their
|
||||
wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while
|
||||
`1 instanceof T(Integer)` evaluates to `true`.
|
||||
|
||||
|
||||
[[expressions-operators-logical]]
|
||||
== Logical Operators
|
||||
|
||||
SpEL supports the following logical operators:
|
||||
SpEL supports the following logical (`boolean`) operators:
|
||||
|
||||
* `and` (`&&`)
|
||||
* `or` (`||`)
|
||||
* `not` (`!`)
|
||||
|
||||
All of the textual operators are case-insensitive.
|
||||
|
||||
The following example shows how to use the logical operators:
|
||||
|
||||
[tabs]
|
||||
@@ -167,6 +221,7 @@ Java::
|
||||
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
|
||||
|
||||
// -- AND and NOT --
|
||||
|
||||
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
|
||||
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
|
||||
----
|
||||
@@ -199,20 +254,24 @@ Kotlin::
|
||||
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
|
||||
|
||||
// -- AND and NOT --
|
||||
|
||||
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
|
||||
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[expressions-operators-mathematical]]
|
||||
== Mathematical Operators
|
||||
[[expressions-operators-string]]
|
||||
== String Operators
|
||||
|
||||
You can use the addition operator (`+`) on both numbers and strings. You can use the
|
||||
subtraction (`-`), multiplication (`*`), and division (`/`) operators only on numbers.
|
||||
You can also use the modulus (`%`) and exponential power (`^`) operators on numbers.
|
||||
Standard operator precedence is enforced. The following example shows the mathematical
|
||||
operators in use:
|
||||
You can use the following operators on strings.
|
||||
|
||||
* concatenation (`+`)
|
||||
* subtraction (`-`)
|
||||
- for use with a string containing a single character
|
||||
* repeat (`*`)
|
||||
|
||||
The following example shows the `String` operators in use:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -220,67 +279,212 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Addition
|
||||
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
|
||||
// -- Concatenation --
|
||||
|
||||
String testString = parser.parseExpression(
|
||||
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
|
||||
// evaluates to "hello world"
|
||||
String helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
|
||||
.getValue(String.class);
|
||||
|
||||
// Subtraction
|
||||
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
|
||||
// -- Character Subtraction --
|
||||
|
||||
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
|
||||
// evaluates to 'a'
|
||||
char ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(char.class);
|
||||
|
||||
// Multiplication
|
||||
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
|
||||
// -- Repeat --
|
||||
|
||||
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
|
||||
|
||||
// Division
|
||||
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
|
||||
|
||||
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
|
||||
|
||||
// Modulus
|
||||
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
|
||||
|
||||
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
|
||||
|
||||
// Operator precedence
|
||||
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
|
||||
// evaluates to "abcabc"
|
||||
String repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Addition
|
||||
val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
|
||||
// -- Concatenation --
|
||||
|
||||
val testString = parser.parseExpression(
|
||||
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
|
||||
// evaluates to "hello world"
|
||||
val helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
|
||||
.getValue(String::class.java)
|
||||
|
||||
// -- Character Subtraction --
|
||||
|
||||
// evaluates to 'a'
|
||||
val ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(Character::class.java);
|
||||
|
||||
// -- Repeat --
|
||||
|
||||
// evaluates to "abcabc"
|
||||
val repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String::class.java);
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-operators-mathematical]]
|
||||
== Mathematical Operators
|
||||
|
||||
You can use the following operators on numbers, and standard operator precedence is enforced.
|
||||
|
||||
* addition (`+`)
|
||||
* subtraction (`-`)
|
||||
* increment (`{pp}`)
|
||||
* decrement (`--`)
|
||||
* multiplication (`*`)
|
||||
* division (`/`)
|
||||
* modulus (`%`)
|
||||
* exponential power (`^`)
|
||||
|
||||
The division and modulus operators can also be specified as a purely textual equivalent.
|
||||
This avoids problems where the symbols used have special meaning for the document type in
|
||||
which the expression is embedded (such as in an XML document). The textual equivalents
|
||||
are:
|
||||
|
||||
* `div` (`/`)
|
||||
* `mod` (`%`)
|
||||
|
||||
All of the textual operators are case-insensitive.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The increment and decrement operators can be used with either prefix (`{pp}A`, `--A`) or
|
||||
postfix (`A{pp}`, `A--`) notation with variables or properties that can be written to.
|
||||
====
|
||||
|
||||
The following example shows the mathematical operators in use:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Inventor inventor = new Inventor();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
|
||||
// -- Addition --
|
||||
|
||||
int two = parser.parseExpression("1 + 1").getValue(int.class); // 2
|
||||
|
||||
// -- Subtraction --
|
||||
|
||||
int four = parser.parseExpression("1 - -3").getValue(int.class); // 4
|
||||
|
||||
double d = parser.parseExpression("1000.00 - 1e4").getValue(double.class); // -9000
|
||||
|
||||
// -- Increment --
|
||||
|
||||
// The counter property in Inventor has an initial value of 0.
|
||||
|
||||
// evaluates to 2; counter is now 1
|
||||
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, int.class);
|
||||
|
||||
// evaluates to 5; counter is now 2
|
||||
int five = parser.parseExpression("3 + ++counter").getValue(context, inventor, int.class);
|
||||
|
||||
// -- Decrement --
|
||||
|
||||
// The counter property in Inventor has a value of 2.
|
||||
|
||||
// evaluates to 6; counter is now 1
|
||||
int six = parser.parseExpression("counter-- + 4").getValue(context, inventor, int.class);
|
||||
|
||||
// evaluates to 5; counter is now 0
|
||||
five = parser.parseExpression("5 + --counter").getValue(context, inventor, int.class);
|
||||
|
||||
// -- Multiplication --
|
||||
|
||||
six = parser.parseExpression("-2 * -3").getValue(int.class); // 6
|
||||
|
||||
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(double.class); // 24.0
|
||||
|
||||
// -- Division --
|
||||
|
||||
int minusTwo = parser.parseExpression("6 / -3").getValue(int.class); // -2
|
||||
|
||||
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(double.class); // 1.0
|
||||
|
||||
// -- Modulus --
|
||||
|
||||
int three = parser.parseExpression("7 % 4").getValue(int.class); // 3
|
||||
|
||||
int oneInt = parser.parseExpression("8 / 5 % 2").getValue(int.class); // 1
|
||||
|
||||
// -- Exponential power --
|
||||
|
||||
int maxInt = parser.parseExpression("(2^31) - 1").getValue(int.class); // Integer.MAX_VALUE
|
||||
|
||||
int minInt = parser.parseExpression("-2^31").getValue(int.class); // Integer.MIN_VALUE
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(int.class); // -21
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val inventor = Inventor()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
|
||||
// -- Addition --
|
||||
|
||||
var two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
|
||||
|
||||
// -- Subtraction --
|
||||
|
||||
// Subtraction
|
||||
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
|
||||
|
||||
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000
|
||||
|
||||
// Multiplication
|
||||
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
|
||||
// -- Increment --
|
||||
|
||||
// The counter property in Inventor has an initial value of 0.
|
||||
|
||||
// evaluates to 2; counter is now 1
|
||||
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// evaluates to 5; counter is now 2
|
||||
var five = parser.parseExpression("3 + ++counter").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// -- Decrement --
|
||||
|
||||
// The counter property in Inventor has a value of 2.
|
||||
|
||||
// evaluates to 6; counter is now 1
|
||||
var six = parser.parseExpression("counter-- + 4").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// evaluates to 5; counter is now 0
|
||||
five = parser.parseExpression("5 + --counter").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// -- Multiplication --
|
||||
|
||||
six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
|
||||
|
||||
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
|
||||
|
||||
// Division
|
||||
// -- Division --
|
||||
|
||||
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
|
||||
|
||||
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
|
||||
|
||||
// Modulus
|
||||
// -- Modulus --
|
||||
|
||||
val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3
|
||||
|
||||
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
|
||||
val oneInt = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
|
||||
|
||||
// -- Exponential power --
|
||||
|
||||
val maxInt = parser.parseExpression("(2^31) - 1").getValue(Int::class.java) // Integer.MAX_VALUE
|
||||
|
||||
val minInt = parser.parseExpression("-2^31").getValue(Int::class.java) // Integer.MIN_VALUE
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
// Operator precedence
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
----
|
||||
======
|
||||
@@ -325,3 +529,83 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[expressions-operators-overloaded]]
|
||||
== Overloaded Operators
|
||||
|
||||
By default, the mathematical operations defined in SpEL's `Operation` enum (`ADD`,
|
||||
`SUBTRACT`, `DIVIDE`, `MULTIPLY`, `MODULUS`, and `POWER`) support simple types like
|
||||
numbers. By providing an implementation of `OperatorOverloader`, the expression language
|
||||
can support these operations on other types.
|
||||
|
||||
For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD &&
|
||||
left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If we register `ListConcatenation` as the `OperatorOverloader` in a
|
||||
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
|
||||
as demonstrated in the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setOperatorOverloader(new ListConcatenation());
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
An `OperatorOverloader` does not change the default semantics for an operator. For
|
||||
example, `2 + 2` in the above example still evaluates to `4`.
|
||||
====
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
Any expression that uses an overloaded operator cannot be compiled. See
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-limitations[Compiler Limitations]
|
||||
for details.
|
||||
====
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[[expressions-templating]]
|
||||
= Expression templating
|
||||
= Expression Templating
|
||||
|
||||
Expression templates allow mixing literal text with one or more evaluation blocks.
|
||||
Each evaluation block is delimited with prefix and suffix characters that you can
|
||||
@@ -32,53 +32,13 @@ Kotlin::
|
||||
======
|
||||
|
||||
The string is evaluated by concatenating the literal text `'random number is '` with the
|
||||
result of evaluating the expression inside the `#{ }` delimiter (in this case, the result
|
||||
of calling that `random()` method). The second argument to the `parseExpression()` method
|
||||
is of the type `ParserContext`. The `ParserContext` interface is used to influence how
|
||||
the expression is parsed in order to support the expression templating functionality.
|
||||
The definition of `TemplateParserContext` follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class TemplateParserContext implements ParserContext {
|
||||
|
||||
public String getExpressionPrefix() {
|
||||
return "#{";
|
||||
}
|
||||
|
||||
public String getExpressionSuffix() {
|
||||
return "}";
|
||||
}
|
||||
|
||||
public boolean isTemplate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TemplateParserContext : ParserContext {
|
||||
|
||||
override fun getExpressionPrefix(): String {
|
||||
return "#{"
|
||||
}
|
||||
|
||||
override fun getExpressionSuffix(): String {
|
||||
return "}"
|
||||
}
|
||||
|
||||
override fun isTemplate(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
result of evaluating the expression inside the `#{ }` delimiters (in this case, the
|
||||
result of calling that `random()` method). The second argument to the `parseExpression()`
|
||||
method is of the type `ParserContext`. The `ParserContext` interface is used to influence
|
||||
how the expression is parsed in order to support the expression templating functionality.
|
||||
The `TemplateParserContext` used in the previous example resides in the
|
||||
`org.springframework.expression.common` package and is an implementation of the
|
||||
`ParserContext` which by default configures the prefix and suffix to `#{` and `}`,
|
||||
respectively.
|
||||
|
||||
|
||||
|
||||
@@ -6,15 +6,27 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Valid variable names must be composed of one or more of the following supported
|
||||
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
|
||||
sign.
|
||||
|
||||
Variable names must be composed of one or more of the following supported types of
|
||||
characters.
|
||||
|
||||
* letters: `A` to `Z` and `a` to `z`
|
||||
* digits: `0` to `9`
|
||||
* letter: any character for which `java.lang.Character.isLetter(char)` returns `true`
|
||||
- This includes letters such as `A` to `Z`, `a` to `z`, `ü`, `ñ`, and `é` as well as
|
||||
letters from other character sets such as Chinese, Japanese, Cyrillic, etc.
|
||||
* digit: `0` to `9`
|
||||
* underscore: `_`
|
||||
* dollar sign: `$`
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Since variables share a common namespace with
|
||||
xref:core/expressions/language-ref/functions.adoc[functions] in the evaluation context,
|
||||
care must be taken to ensure that variable names and functions names do not overlap.
|
||||
====
|
||||
|
||||
The following example shows how to use variables.
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -429,7 +429,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldErrro` with the following:
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` with the following:
|
||||
|
||||
- Error codes `"Size.student.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
|
||||
- Message arguments `"name"`, `10`, and `1` (the field name and the constraint attributes)
|
||||
|
||||
@@ -717,7 +717,7 @@ For example, with positional parameters:
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
|
||||
.param(firstName);
|
||||
.param(firstName)
|
||||
.query(Integer.class).single();
|
||||
}
|
||||
----
|
||||
@@ -730,7 +730,7 @@ For example, with named parameters:
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
|
||||
.param("firstName", firstName);
|
||||
.param("firstName", firstName)
|
||||
.query(Integer.class).single();
|
||||
}
|
||||
----
|
||||
@@ -759,8 +759,8 @@ With a required single object result:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?",
|
||||
.param(1212L);
|
||||
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
|
||||
.param(1212L)
|
||||
.query(Actor.class)
|
||||
.single();
|
||||
----
|
||||
@@ -769,8 +769,8 @@ With a `java.util.Optional` result:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?",
|
||||
.param(1212L);
|
||||
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
|
||||
.param(1212L)
|
||||
.query(Actor.class)
|
||||
.optional();
|
||||
----
|
||||
@@ -780,7 +780,7 @@ And for an update statement:
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
|
||||
.param("Leonor").param("Watling");
|
||||
.param("Leonor").param("Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
@@ -789,7 +789,7 @@ Or an update statement with named parameters:
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.param("firstName", "Leonor").param("lastName", "Watling");
|
||||
.param("firstName", "Leonor").param("lastName", "Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
@@ -800,7 +800,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.paramSource(new Actor("Leonor", "Watling");
|
||||
.paramSource(new Actor("Leonor", "Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ WebSocket, RSocket.
|
||||
xref:integration.adoc[Integration] :: REST Clients, JMS, JCA, JMX,
|
||||
Email, Tasks, Scheduling, Caching, Observability, JVM Checkpoint Restore.
|
||||
xref:languages.adoc[Languages] :: Kotlin, Groovy, Dynamic Languages.
|
||||
xref:testing/appendix.adoc[Appendix] :: Spring properties.
|
||||
xref:appendix.adoc[Appendix] :: Spring properties.
|
||||
{spring-framework-wiki}[Wiki] :: What's New,
|
||||
Upgrade Notes, Supported Versions, additional cross-version information.
|
||||
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
[[class-data-sharing]]
|
||||
= Class Data Sharing
|
||||
[[cds]]
|
||||
= CDS
|
||||
:page-aliases: integration/class-data-sharing.adoc
|
||||
|
||||
Class Data Sharing (CDS) is a https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[JVM feature]
|
||||
that can help reduce the startup time and memory footprint of Java applications.
|
||||
@@ -51,9 +51,9 @@ val defaultClient = RestClient.create()
|
||||
|
||||
val customClient = RestClient.builder()
|
||||
.requestFactory(HttpComponentsClientHttpRequestFactory())
|
||||
.messageConverters(converters -> converters.add(MyCustomMessageConverter()))
|
||||
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
|
||||
.baseUrl("https://example.com")
|
||||
.defaultUriVariables(Map.of("variable", "foo"))
|
||||
.defaultUriVariables(mapOf("variable" to "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
@@ -973,6 +973,9 @@ method parameters:
|
||||
`Map<String, ?>` with multiple variables, or an individual value. Type conversion
|
||||
is supported for non-String values.
|
||||
|
||||
| `@RequestAttribute`
|
||||
| Provide an `Object` to add as a request attribute. Only supported by `WebClient`.
|
||||
|
||||
| `@RequestBody`
|
||||
| Provide the body of the request either as an Object to be serialized, or a
|
||||
Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported
|
||||
|
||||
@@ -167,7 +167,7 @@ public class SampleConfiguration {
|
||||
|
||||
As a consequence, the related bean name represented as a Kotlin string is `"sampleBean\$demo_kotlin_internal_test"`,
|
||||
instead of `"sampleBean"` for the regular `public` function use-case. Make sure to use the mangled name when injecting
|
||||
such bean by name.
|
||||
such bean by name, or add `@JvmName("sampleBean")` to disable name mangling.
|
||||
|
||||
[[injecting-configuration-properties]]
|
||||
== Injecting Configuration Properties
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ Kotlin::
|
||||
@Autowired
|
||||
lateinit var accountService: AccountService
|
||||
|
||||
lateinit mockMvc: MockMvc
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup(wac: WebApplicationContext) {
|
||||
|
||||
@@ -782,6 +782,73 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[webflux-fn-serving-resources]]
|
||||
== Serving Resources
|
||||
|
||||
WebFlux.fn provides built-in support for serving resources.
|
||||
|
||||
NOTE: In addition to the capabilities described below, it is possible to implement even more flexible resource handling thanks to
|
||||
{spring-framework-api}++/web/reactive/function/server/RouterFunctions.html#resources(java.util.function.Function)++[`RouterFunctions#resource(java.util.function.Function)`].
|
||||
|
||||
[[webflux-fn-resource]]
|
||||
=== Redirecting to a resource
|
||||
|
||||
It is possible to redirect requests matching a specified predicate to a resource. This can be useful, for example,
|
||||
for handling redirects in Single Page Applications.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
pathExtension(extensions::contains))
|
||||
resource(spaPredicate, index)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[webflux-fn-resources]]
|
||||
=== Serving resources from a root location
|
||||
|
||||
It is also possible to route requests that match a given pattern to resources relative to a given root location.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Resource location = new FileSystemResource("public-resources/");
|
||||
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val location = FileSystemResource("public-resources/")
|
||||
val resources = router { resources("/resources/**", location) }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-running[See equivalent in the Servlet stack]#
|
||||
|
||||
@@ -28,6 +28,12 @@ because, arguably, most controller methods should be mapped to a specific HTTP m
|
||||
using `@RequestMapping`, which, by default, matches to all HTTP methods. At the same time, a
|
||||
`@RequestMapping` is still needed at the class level to express shared mappings.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
The following example uses type and method level mappings:
|
||||
|
||||
[tabs]
|
||||
@@ -436,8 +442,14 @@ attributes with a narrower, more specific purpose.
|
||||
`@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, and `@PatchMapping` are
|
||||
examples of composed annotations. They are provided, because, arguably, most
|
||||
controller methods should be mapped to a specific HTTP method versus using `@RequestMapping`,
|
||||
which, by default, matches to all HTTP methods. If you need an example of composed
|
||||
annotations, look at how those are declared.
|
||||
which, by default, matches to all HTTP methods. If you need an example of how to implement
|
||||
a composed annotation, look at how those are declared.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
Spring WebFlux also supports custom request mapping attributes with custom request matching
|
||||
logic. This is a more advanced option that requires sub-classing
|
||||
@@ -511,10 +523,17 @@ Kotlin::
|
||||
== `@HttpExchange`
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Servlet stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] and can be used as
|
||||
a client via `HttpServiceProxyFactory` or implemented by a server `@Controller`.
|
||||
While the main purpose of `@HttpExchange` is to abstract HTTP client code with a
|
||||
generated proxy, the
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] on which
|
||||
such annotations are placed is a contract neutral to client vs server use.
|
||||
In addition to simplifying client code, there are also cases where an HTTP Interface
|
||||
may be a convenient way for servers to expose their API for client access. This leads
|
||||
to increased coupling between client and server and is often not a good choice,
|
||||
especially for public API's, but may be exactly the goal for an internal API.
|
||||
It is an approach commonly used in Spring Cloud, and it is why `@HttpExchange` is
|
||||
supported as an alternative to `@RequestMapping` for server side handling in
|
||||
controller classes.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -524,16 +543,23 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
Person getPerson(@PathVariable Long id);
|
||||
|
||||
@PostExchange
|
||||
void add(@RequestBody Person person);
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController implements PersonService {
|
||||
|
||||
public Person getPerson(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void add(@RequestBody Person person) {
|
||||
// ...
|
||||
@@ -545,30 +571,38 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
fun getPerson(@PathVariable id: Long): Person {
|
||||
fun getPerson(@PathVariable id: Long): Person
|
||||
|
||||
@PostExchange
|
||||
fun add(@RequestBody person: Person)
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController : PersonService {
|
||||
|
||||
override fun getPerson(@PathVariable id: Long): Person {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun add(@RequestBody person: Person) {
|
||||
override fun add(@RequestBody person: Person) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There some differences between `@HttpExchange` and `@RequestMapping` since the
|
||||
former needs to remain suitable for client and server use. For example, while
|
||||
`@RequestMapping` can be declared to handle any number of paths and each path can
|
||||
be a pattern, `@HttpExchange` must be declared with a single, concrete path. There are
|
||||
also differences in the supported method parameters. Generally, `@HttpExchange` supports
|
||||
a subset of method parameters that `@RequestMapping` does, excluding any parameters that
|
||||
are server side only. For details see the list of supported method parameters for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[HTTP interface] and for
|
||||
`@HttpExchange` and `@RequestMapping` have differences.
|
||||
`@RequestMapping` can map to any number of requests by path patterns, HTTP methods,
|
||||
and more, while `@HttpExchange` declares a single endpoint with a concrete HTTP method,
|
||||
path, and content types.
|
||||
|
||||
For method parameters and returns values, generally, `@HttpExchange` supports a
|
||||
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
|
||||
server-side specific parameter types. For details, see the list for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
@@ -8,22 +8,23 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] supp
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
|
||||
First, method parameters such as
|
||||
First, resolvers for
|
||||
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] do perform
|
||||
validation if annotated with Jakarta's `@Valid` or Spring's `@Validated` annotation, and
|
||||
raise `MethodArgumentNotValidException` in case of validation errors. If you want to handle
|
||||
the errors in the controller method instead, you can declare an `Errors` or `BindingResult`
|
||||
method parameter immediately after the validated parameter.
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ other method
|
||||
parameters, e.g. `@RequestHeader`, `@RequestParam`, `@PathVariable` have `@Constraint`
|
||||
annotations, then method validation is applied to all method arguments, raising
|
||||
`HandlerMethodValidationException` in case of validation errors. You can still declare an
|
||||
`Errors` or `BindingResult` after an `@Valid` method parameter, and handle validation
|
||||
errors within the controller method, as long as there are no validation errors on other
|
||||
method arguments.
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
|
||||
|
||||
@@ -760,6 +760,73 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-serving-resources]]
|
||||
== Serving Resources
|
||||
|
||||
WebMvc.fn provides built-in support for serving resources.
|
||||
|
||||
NOTE: In addition to the capabilities described below, it is possible to implement even more flexible resource handling thanks to
|
||||
{spring-framework-api}++/web/servlet/function/RouterFunctions.html#resources(java.util.function.Function)++[`RouterFunctions#resource(java.util.function.Function)`].
|
||||
|
||||
[[webmvc-fn-resource]]
|
||||
=== Redirecting to a resource
|
||||
|
||||
It is possible to redirect requests matching a specified predicate to a resource. This can be useful, for example,
|
||||
for handling redirects in Single Page Applications.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
pathExtension(extensions::contains))
|
||||
resource(spaPredicate, index)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[webmvc-fn-resources]]
|
||||
=== Serving resources from a root location
|
||||
|
||||
It is also possible to route requests that match a given pattern to resources relative to a given root location.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Resource location = new FileSystemResource("public-resources/");
|
||||
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val location = FileSystemResource("public-resources/")
|
||||
val resources = router { resources("/resources/**", location) }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#xref:web/webflux-functional.adoc#webflux-fn-running[See equivalent in the Reactive stack]#
|
||||
|
||||
+56
-27
@@ -30,6 +30,12 @@ arguably, most controller methods should be mapped to a specific HTTP method ver
|
||||
using `@RequestMapping`, which, by default, matches to all HTTP methods.
|
||||
A `@RequestMapping` is still needed at the class level to express shared mappings.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
The following example has type and method level mappings:
|
||||
|
||||
[tabs]
|
||||
@@ -462,11 +468,6 @@ transparently for request mapping. Controller methods do not need to change.
|
||||
A response wrapper, applied in `jakarta.servlet.http.HttpServlet`, ensures a `Content-Length`
|
||||
header is set to the number of bytes written (without actually writing to the response).
|
||||
|
||||
`@GetMapping` (and `@RequestMapping(method=HttpMethod.GET)`) are implicitly mapped to
|
||||
and support HTTP HEAD. An HTTP HEAD request is processed as if it were HTTP GET except
|
||||
that, instead of writing the body, the number of bytes are counted and the `Content-Length`
|
||||
header is set.
|
||||
|
||||
By default, HTTP OPTIONS is handled by setting the `Allow` response header to the list of HTTP
|
||||
methods listed in all `@RequestMapping` methods that have matching URL patterns.
|
||||
|
||||
@@ -491,8 +492,14 @@ attributes with a narrower, more specific purpose.
|
||||
`@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, and `@PatchMapping` are
|
||||
examples of composed annotations. They are provided because, arguably, most
|
||||
controller methods should be mapped to a specific HTTP method versus using `@RequestMapping`,
|
||||
which, by default, matches to all HTTP methods. If you need an example of composed
|
||||
annotations, look at how those are declared.
|
||||
which, by default, matches to all HTTP methods. If you need an example of how to implement
|
||||
a composed annotation, look at how those are declared.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
Spring MVC also supports custom request-mapping attributes with custom request-matching
|
||||
logic. This is a more advanced option that requires subclassing
|
||||
@@ -562,10 +569,17 @@ Kotlin::
|
||||
== `@HttpExchange`
|
||||
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-httpexchange-annotation[See equivalent in the Reactive stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] and can be used as
|
||||
a client via `HttpServiceProxyFactory` or implemented by a server `@Controller`.
|
||||
While the main purpose of `@HttpExchange` is to abstract HTTP client code with a
|
||||
generated proxy, the
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] on which
|
||||
such annotations are placed is a contract neutral to client vs server use.
|
||||
In addition to simplifying client code, there are also cases where an HTTP Interface
|
||||
may be a convenient way for servers to expose their API for client access. This leads
|
||||
to increased coupling between client and server and is often not a good choice,
|
||||
especially for public API's, but may be exactly the goal for an internal API.
|
||||
It is an approach commonly used in Spring Cloud, and it is why `@HttpExchange` is
|
||||
supported as an alternative to `@RequestMapping` for server side handling in
|
||||
controller classes.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -575,16 +589,23 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
Person getPerson(@PathVariable Long id);
|
||||
|
||||
@PostExchange
|
||||
void add(@RequestBody Person person);
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController implements PersonService {
|
||||
|
||||
public Person getPerson(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void add(@RequestBody Person person) {
|
||||
// ...
|
||||
@@ -596,30 +617,38 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
fun getPerson(@PathVariable id: Long): Person {
|
||||
fun getPerson(@PathVariable id: Long): Person
|
||||
|
||||
@PostExchange
|
||||
fun add(@RequestBody person: Person)
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController : PersonService {
|
||||
|
||||
override fun getPerson(@PathVariable id: Long): Person {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun add(@RequestBody person: Person) {
|
||||
override fun add(@RequestBody person: Person) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There some differences between `@HttpExchange` and `@RequestMapping` since the
|
||||
former needs to remain suitable for client and server use. For example, while
|
||||
`@RequestMapping` can be declared to handle any number of paths and each path can
|
||||
be a pattern, `@HttpExchange` must be declared with a single, concrete path. There are
|
||||
also differences in the supported method parameters. Generally, `@HttpExchange` supports
|
||||
a subset of method parameters that `@RequestMapping` does, excluding any parameters that
|
||||
are server side only. For details see the list of supported method parameters for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[HTTP interface] and for
|
||||
`@HttpExchange` and `@RequestMapping` have differences.
|
||||
`@RequestMapping` can map to any number of requests by path patterns, HTTP methods,
|
||||
and more, while `@HttpExchange` declares a single endpoint with a concrete HTTP method,
|
||||
path, and content types.
|
||||
|
||||
For method parameters and returns values, generally, `@HttpExchange` supports a
|
||||
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
|
||||
server-side specific parameter types. For details, see the list for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
@@ -8,23 +8,23 @@ Spring MVC has built-in xref:core/validation/validator.adoc[Validation] support
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
|
||||
First, method parameters such as
|
||||
First, resolvers for
|
||||
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] do perform
|
||||
validation if annotated with Jakarta's `@Valid` or Spring's `@Validated` annotation, and
|
||||
raise `MethodArgumentNotValidException` in case of validation errors. If you want to handle
|
||||
the errors in the controller method instead, you can declare an `Errors` or `BindingResult`
|
||||
method parameter immediately after the validated parameter.
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ other method
|
||||
parameters, e.g. `@RequestHeader`, `@RequestParam`, `@PathVariable` have `@Constraint`
|
||||
annotations, then method validation is applied to all method arguments, raising
|
||||
`HandlerMethodValidationException` in case of validation errors. You can still declare an
|
||||
`Errors` or `BindingResult` after an `@Valid` method parameter, and handle validation
|
||||
errors within the controller method, as long as there are no validation errors on other
|
||||
method arguments. Method validation is also applied to the return value if the method
|
||||
is annotated with `@Valid` or has other `@Constraint` annotations.
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an
|
||||
@@ -109,4 +109,4 @@ Kotlin::
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
======
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -24,7 +24,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class SpellCheckServiceTests {
|
||||
class SpellCheckServiceTests {
|
||||
|
||||
// tag::hintspredicates[]
|
||||
@Test
|
||||
|
||||
@@ -8,30 +8,30 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.2"))
|
||||
api(platform("io.netty:netty-bom:4.1.104.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.3"))
|
||||
api(platform("io.netty:netty-bom:4.1.107.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.2"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.3"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.17"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.18"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.25.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.5"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.5"))
|
||||
api(platform("org.assertj:assertj-bom:3.25.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.6"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.6"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.8.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.10.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.36")
|
||||
api("com.github.librepdf:openpdf:1.3.40")
|
||||
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.10.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.2")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
@@ -46,7 +46,7 @@ dependencies {
|
||||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.1.0")
|
||||
api("io.micrometer:context-propagation:1.1.1")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
@@ -55,9 +55,9 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.10.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.10.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.10.Final")
|
||||
api("io.undertow:undertow-core:2.3.11.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.11.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.11.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -100,7 +100,7 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.18")
|
||||
@@ -115,7 +115,7 @@ dependencies {
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.1")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.2")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
@@ -139,7 +139,7 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.1")
|
||||
api("org.slf4j:slf4j-api:2.0.11")
|
||||
api("org.slf4j:slf4j-api:2.0.12")
|
||||
api("org.testng:testng:7.9.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.1.3-SNAPSHOT
|
||||
version=6.1.4
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -8,8 +8,9 @@ apply plugin: 'me.champeau.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.37'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-bytecode:1.37'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple'
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Vendored
+10
-10
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
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
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
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
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -75,7 +75,7 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestScoping() throws Exception {
|
||||
void testRequestScoping() {
|
||||
MockHttpServletRequest oldRequest = new MockHttpServletRequest();
|
||||
MockHttpServletRequest newRequest = new MockHttpServletRequest();
|
||||
|
||||
@@ -103,7 +103,7 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSessionScoping() throws Exception {
|
||||
void testSessionScoping() {
|
||||
MockHttpSession oldSession = new MockHttpSession();
|
||||
MockHttpSession newSession = new MockHttpSession();
|
||||
|
||||
|
||||
+9
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.aop.framework.autoproxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
@@ -61,12 +60,12 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
/**
|
||||
* Return a bean factory with attributes and EnterpriseServices configured.
|
||||
*/
|
||||
protected BeanFactory getBeanFactory() throws IOException {
|
||||
protected BeanFactory getBeanFactory() {
|
||||
return new ClassPathXmlApplicationContext(DEFAULT_CONTEXT, CLASS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultExclusionPrefix() throws Exception {
|
||||
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();
|
||||
@@ -76,21 +75,21 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
* If no pointcuts match (no attrs) there should be proxying.
|
||||
*/
|
||||
@Test
|
||||
void testNoProxy() throws Exception {
|
||||
void testNoProxy() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
Object o = bf.getBean("noSetters");
|
||||
assertThat(AopUtils.isAopProxy(o)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxIsProxied() throws Exception {
|
||||
void testTxIsProxied() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
assertThat(AopUtils.isAopProxy(test)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegexpApplied() throws Exception {
|
||||
void testRegexpApplied() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
MethodCounter counter = (MethodCounter) bf.getBean("countingAdvice");
|
||||
@@ -100,7 +99,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionAttributeOnMethod() throws Exception {
|
||||
void testTransactionAttributeOnMethod() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
|
||||
@@ -166,7 +165,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProgrammaticRollback() throws Exception {
|
||||
void testProgrammaticRollback() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
|
||||
Object bean = bf.getBean(TXMANAGER_BEAN_NAME);
|
||||
@@ -250,7 +249,7 @@ class OrderedTxCheckAdvisor extends StaticMethodMatcherPointcutAdvisor implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
public void afterPropertiesSet() {
|
||||
setAdvice(new TxCountingBeforeAdvice());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@EnabledIfRuntimeHintsAgent
|
||||
public class RuntimeHintsAgentTests {
|
||||
class RuntimeHintsAgentTests {
|
||||
|
||||
private static final ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class RuntimeHintsAgentTests {
|
||||
|
||||
|
||||
@BeforeAll
|
||||
public static void classSetup() throws NoSuchMethodException {
|
||||
static void classSetup() throws NoSuchMethodException {
|
||||
defaultConstructor = String.class.getConstructor();
|
||||
toStringMethod = String.class.getMethod("toString");
|
||||
privateGreetMethod = PrivateClass.class.getDeclaredMethod("greet");
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -39,7 +39,7 @@ class ComponentBeanDefinitionParserTests {
|
||||
|
||||
|
||||
@BeforeAll
|
||||
void setUp() throws Exception {
|
||||
void setUp() {
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
new ClassPathResource("component-config.xml", ComponentBeanDefinitionParserTests.class));
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -34,7 +34,7 @@ public class ComponentFactoryBean implements FactoryBean<Component> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getObject() throws Exception {
|
||||
public Component getObject() {
|
||||
if (this.children != null && this.children.size() > 0) {
|
||||
for (Component child : children) {
|
||||
this.parent.addComponent(child);
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -42,7 +42,6 @@ import static org.assertj.core.api.Assertions.assertThatException;
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class EnableCachingIntegrationTests {
|
||||
|
||||
@Test
|
||||
|
||||
Vendored
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -87,7 +87,6 @@ import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Con
|
||||
* @author Sam Brannen
|
||||
* @see org.springframework.context.support.EnvironmentIntegrationTests
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class EnvironmentSystemIntegrationTests {
|
||||
|
||||
private final ConfigurableEnvironment prodEnv = new StandardEnvironment();
|
||||
@@ -618,7 +617,7 @@ public class EnvironmentSystemIntegrationTests {
|
||||
@Import({DevConfig.class, ProdConfig.class})
|
||||
static class Config {
|
||||
@Bean
|
||||
public EnvironmentAwareBean envAwareBean() {
|
||||
EnvironmentAwareBean envAwareBean() {
|
||||
return new EnvironmentAwareBean();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Copied from Spring Integration for purposes of reproducing
|
||||
@@ -59,11 +60,9 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
if (beanFactory instanceof ConfigurableBeanFactory) {
|
||||
Object typeConverter = ((ConfigurableBeanFactory) beanFactory).getTypeConverter();
|
||||
if (typeConverter instanceof SimpleTypeConverter) {
|
||||
delegate = (SimpleTypeConverter) typeConverter;
|
||||
}
|
||||
if (beanFactory instanceof ConfigurableBeanFactory cbf &&
|
||||
cbf.getTypeConverter() instanceof SimpleTypeConverter simpleTypeConverter) {
|
||||
this.delegate = simpleTypeConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +85,6 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
|
||||
if (conversionService.canConvert(sourceTypeDescriptor, targetTypeDescriptor)) {
|
||||
return true;
|
||||
}
|
||||
// TODO: what does this mean? This method is not used in SpEL so probably ignorable?
|
||||
Class<?> sourceType = sourceTypeDescriptor.getObjectType();
|
||||
Class<?> targetType = targetTypeDescriptor.getObjectType();
|
||||
return canConvert(sourceType, targetType);
|
||||
@@ -94,7 +92,7 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
|
||||
|
||||
@Override
|
||||
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (targetType.getType() == Void.class || targetType.getType() == Void.TYPE) {
|
||||
if (ClassUtils.isVoidType(targetType.getType())) {
|
||||
return null;
|
||||
}
|
||||
if (conversionService.canConvert(sourceType, targetType)) {
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -51,7 +51,6 @@ import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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,7 +54,6 @@ import static org.assertj.core.api.Assertions.assertThatException;
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class EnableTransactionManagementIntegrationTests {
|
||||
|
||||
@Test
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -27,14 +27,13 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
/**
|
||||
* Tests proving that regardless the proxy strategy used (JDK interface-based vs. CGLIB
|
||||
* subclass-based), discovery of advice-oriented annotations is consistent.
|
||||
*
|
||||
* <p>
|
||||
* For example, Spring's @Transactional may be declared at the interface or class level,
|
||||
* and whether interface or subclass proxies are used, the @Transactional annotation must
|
||||
* be discovered in a consistent fashion.
|
||||
*
|
||||
* @author Chris Beams
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class ProxyAnnotationDiscoveryTests {
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.14"
|
||||
id "io.spring.ge.conventions" version "0.0.13"
|
||||
id "com.gradle.enterprise" version "3.16.1"
|
||||
id "io.spring.ge.conventions" version "0.0.15"
|
||||
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -22,7 +22,6 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -85,10 +84,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
private boolean preFiltered = false;
|
||||
|
||||
/** The AdvisorChainFactory to use. */
|
||||
private AdvisorChainFactory advisorChainFactory;
|
||||
|
||||
/** Cache with Method as key and advisor chain List as value. */
|
||||
private transient Map<MethodCacheKey, List<Object>> methodCache;
|
||||
private AdvisorChainFactory advisorChainFactory = DefaultAdvisorChainFactory.INSTANCE;
|
||||
|
||||
/**
|
||||
* Interfaces to be implemented by the proxy. Held in List to keep the order
|
||||
@@ -110,6 +106,14 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
*/
|
||||
private List<Advisor> advisorKey = this.advisors;
|
||||
|
||||
/** Cache with Method as key and advisor chain List as value. */
|
||||
@Nullable
|
||||
private transient Map<MethodCacheKey, List<Object>> methodCache;
|
||||
|
||||
/** Cache with shared interceptors which are not method-specific. */
|
||||
@Nullable
|
||||
private transient volatile List<Object> cachedInterceptors;
|
||||
|
||||
/**
|
||||
* Optional field for {@link AopProxy} implementations to store metadata in.
|
||||
* Used by {@link JdkDynamicAopProxy}.
|
||||
@@ -124,8 +128,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* No-arg constructor for use as a JavaBean.
|
||||
*/
|
||||
public AdvisedSupport() {
|
||||
this.advisorChainFactory = DefaultAdvisorChainFactory.INSTANCE;
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,19 +135,9 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* @param interfaces the proxied interfaces
|
||||
*/
|
||||
public AdvisedSupport(Class<?>... interfaces) {
|
||||
this();
|
||||
setInterfaces(interfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal constructor for {@link #getConfigurationOnlyCopy()}.
|
||||
* @since 6.0.10
|
||||
*/
|
||||
private AdvisedSupport(AdvisorChainFactory advisorChainFactory, Map<MethodCacheKey, List<Object>> methodCache) {
|
||||
this.advisorChainFactory = advisorChainFactory;
|
||||
this.methodCache = methodCache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the given object as target.
|
||||
@@ -497,6 +489,18 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
|
||||
*/
|
||||
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
|
||||
if (this.methodCache == null) {
|
||||
// Shared cache since there are no method-specific advisors (see below).
|
||||
List<Object> cachedInterceptors = this.cachedInterceptors;
|
||||
if (cachedInterceptors == null) {
|
||||
cachedInterceptors = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
|
||||
this, method, targetClass);
|
||||
this.cachedInterceptors = cachedInterceptors;
|
||||
}
|
||||
return cachedInterceptors;
|
||||
}
|
||||
|
||||
// Method-specific cache for method-specific pointcuts
|
||||
return this.methodCache.computeIfAbsent(new MethodCacheKey(method), k ->
|
||||
this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass));
|
||||
}
|
||||
@@ -505,8 +509,18 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* Invoked when advice has changed.
|
||||
*/
|
||||
protected void adviceChanged() {
|
||||
this.methodCache.clear();
|
||||
this.methodCache = null;
|
||||
this.cachedInterceptors = null;
|
||||
this.proxyMetadataCache = null;
|
||||
|
||||
// Initialize method cache if necessary; otherwise,
|
||||
// cachedInterceptors is going to be shared (see above).
|
||||
for (Advisor advisor : this.advisors) {
|
||||
if (advisor instanceof PointcutAdvisor) {
|
||||
this.methodCache = new ConcurrentHashMap<>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,21 +559,28 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* replacing the {@link TargetSource}.
|
||||
*/
|
||||
AdvisedSupport getConfigurationOnlyCopy() {
|
||||
AdvisedSupport copy = new AdvisedSupport(this.advisorChainFactory, this.methodCache);
|
||||
AdvisedSupport copy = new AdvisedSupport();
|
||||
copy.copyFrom(this);
|
||||
copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic());
|
||||
copy.preFiltered = this.preFiltered;
|
||||
copy.advisorChainFactory = this.advisorChainFactory;
|
||||
copy.interfaces = new ArrayList<>(this.interfaces);
|
||||
copy.advisors = new ArrayList<>(this.advisors);
|
||||
copy.advisorKey = new ArrayList<>(this.advisors.size());
|
||||
for (Advisor advisor : this.advisors) {
|
||||
copy.advisorKey.add(new AdvisorKeyEntry(advisor));
|
||||
}
|
||||
copy.methodCache = this.methodCache;
|
||||
copy.cachedInterceptors = this.cachedInterceptors;
|
||||
copy.proxyMetadataCache = this.proxyMetadataCache;
|
||||
return copy;
|
||||
}
|
||||
|
||||
void reduceToAdvisorKey() {
|
||||
this.advisors = this.advisorKey;
|
||||
this.methodCache = Collections.emptyMap();
|
||||
this.methodCache = null;
|
||||
this.cachedInterceptors = null;
|
||||
this.proxyMetadataCache = null;
|
||||
}
|
||||
|
||||
Object getAdvisorKey() {
|
||||
@@ -596,8 +617,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
// Initialize method cache if necessary.
|
||||
adviceChanged();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -417,7 +417,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
returnValue = proxy;
|
||||
}
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnValue == null && returnType != Void.TYPE && returnType.isPrimitive()) {
|
||||
if (returnValue == null && returnType != void.class && returnType.isPrimitive()) {
|
||||
throw new AopInvocationException(
|
||||
"Null return value from advice does not match primitive return type for: " + method);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -32,8 +32,13 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
abstract class CoroutinesUtils {
|
||||
|
||||
static Object asFlow(Object publisher) {
|
||||
return ReactiveFlowKt.asFlow((Publisher<?>) publisher);
|
||||
static Object asFlow(@Nullable Object publisher) {
|
||||
if (publisher instanceof Publisher<?> rsPublisher) {
|
||||
return ReactiveFlowKt.asFlow(rsPublisher);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Not a Reactive Streams Publisher: " + publisher);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -230,7 +230,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
// a reference to itself in another returned object.
|
||||
retVal = proxy;
|
||||
}
|
||||
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
|
||||
else if (retVal == null && returnType != void.class && returnType.isPrimitive()) {
|
||||
throw new AopInvocationException(
|
||||
"Null return value from advice does not match primitive return type for: " + method);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -101,7 +101,7 @@ public abstract class Pointcuts {
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
return (method.getName().startsWith("set") &&
|
||||
method.getParameterCount() == 1 &&
|
||||
method.getReturnType() == Void.TYPE);
|
||||
method.getReturnType() == void.class);
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
@@ -127,7 +127,7 @@ public abstract class Pointcuts {
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
return (method.getName().startsWith("get") &&
|
||||
method.getParameterCount() == 0 &&
|
||||
method.getReturnType() != Void.TYPE);
|
||||
method.getReturnType() != void.class);
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
|
||||
+31
-32
@@ -51,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* @author Chris Beams
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public class AspectJExpressionPointcutTests {
|
||||
class AspectJExpressionPointcutTests {
|
||||
|
||||
public static final String MATCH_ALL_METHODS = "execution(* *(..))";
|
||||
|
||||
@@ -78,7 +78,7 @@ public class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchExplicit() {
|
||||
void testMatchExplicit() {
|
||||
String expression = "execution(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -96,7 +96,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchWithTypePattern() throws Exception {
|
||||
void testMatchWithTypePattern() {
|
||||
String expression = "execution(* *..TestBean.*Age(..))";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -115,12 +115,12 @@ public class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testThis() throws SecurityException, NoSuchMethodException{
|
||||
void testThis() throws SecurityException, NoSuchMethodException{
|
||||
testThisOrTarget("this");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTarget() throws SecurityException, NoSuchMethodException {
|
||||
void testTarget() throws SecurityException, NoSuchMethodException {
|
||||
testThisOrTarget("target");
|
||||
}
|
||||
|
||||
@@ -144,12 +144,12 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithinRootPackage() throws SecurityException, NoSuchMethodException {
|
||||
void testWithinRootPackage() throws SecurityException, NoSuchMethodException {
|
||||
testWithinPackage(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithinRootAndSubpackages() throws SecurityException, NoSuchMethodException {
|
||||
void testWithinRootAndSubpackages() throws SecurityException, NoSuchMethodException {
|
||||
testWithinPackage(true);
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFriendlyErrorOnNoLocationClassMatching() {
|
||||
void testFriendlyErrorOnNoLocationClassMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
pc.matches(ITestBean.class))
|
||||
@@ -181,7 +181,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFriendlyErrorOnNoLocation2ArgMatching() {
|
||||
void testFriendlyErrorOnNoLocation2ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
pc.matches(getAge, ITestBean.class))
|
||||
@@ -189,7 +189,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFriendlyErrorOnNoLocation3ArgMatching() {
|
||||
void testFriendlyErrorOnNoLocation3ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
pc.matches(getAge, ITestBean.class, (Object[]) null))
|
||||
@@ -198,7 +198,7 @@ public class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchWithArgs() throws Exception {
|
||||
void testMatchWithArgs() throws Exception {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -217,7 +217,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleAdvice() {
|
||||
void testSimpleAdvice() {
|
||||
String expression = "execution(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
CallCountingInterceptor interceptor = new CallCountingInterceptor();
|
||||
TestBean testBean = getAdvisedProxy(expression, interceptor);
|
||||
@@ -230,7 +230,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicMatchingProxy() {
|
||||
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);
|
||||
@@ -244,7 +244,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidExpression() {
|
||||
void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
}
|
||||
@@ -274,7 +274,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithUnsupportedPointcutPrimitive() {
|
||||
void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
|
||||
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
@@ -282,14 +282,14 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAndSubstitution() {
|
||||
void testAndSubstitution() {
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAndSubstitutions() {
|
||||
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)");
|
||||
@@ -302,7 +302,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchGenericArgument() {
|
||||
void testMatchGenericArgument() {
|
||||
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -321,7 +321,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchVarargs() throws Exception {
|
||||
void testMatchVarargs() throws Exception {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class MyTemplate {
|
||||
@@ -347,19 +347,19 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchAnnotationOnClassWithAtWithin() throws Exception {
|
||||
void testMatchAnnotationOnClassWithAtWithin() throws Exception {
|
||||
String expression = "@within(test.annotation.transaction.Tx)";
|
||||
testMatchAnnotationOnClass(expression);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchAnnotationOnClassWithoutBinding() throws Exception {
|
||||
void testMatchAnnotationOnClassWithoutBinding() throws Exception {
|
||||
String expression = "within(@test.annotation.transaction.Tx *)";
|
||||
testMatchAnnotationOnClass(expression);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchAnnotationOnClassWithSubpackageWildcard() 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();
|
||||
@@ -371,7 +371,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchAnnotationOnClassWithExactPackageWildcard() throws Exception {
|
||||
void testMatchAnnotationOnClassWithExactPackageWildcard() throws Exception {
|
||||
String expression = "within(@(test.annotation.transaction.*) *)";
|
||||
testMatchAnnotationOnClass(expression);
|
||||
}
|
||||
@@ -389,7 +389,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationOnMethodWithFQN() throws Exception {
|
||||
void testAnnotationOnMethodWithFQN() throws Exception {
|
||||
String expression = "@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -403,7 +403,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationOnCglibProxyMethod() throws Exception {
|
||||
void testAnnotationOnCglibProxyMethod() throws Exception {
|
||||
String expression = "@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -414,9 +414,8 @@ public class AspectJExpressionPointcutTests {
|
||||
assertThat(ajexp.matches(BeanA.class.getMethod("getAge"), proxy.getClass())).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNotAnnotationOnCglibProxyMethod() throws Exception {
|
||||
void testNotAnnotationOnCglibProxyMethod() throws Exception {
|
||||
String expression = "!@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -428,7 +427,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
void testAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
String expression = "@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -440,7 +439,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
void testNotAnnotationOnDynamicProxyMethod() throws Exception {
|
||||
String expression = "!@annotation(test.annotation.transaction.Tx)";
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(expression);
|
||||
@@ -452,7 +451,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationOnMethodWithWildcard() throws Exception {
|
||||
void testAnnotationOnMethodWithWildcard() throws Exception {
|
||||
String expression = "execution(@(test.annotation..*) * *(..))";
|
||||
AspectJExpressionPointcut anySpringMethodAnnotation = new AspectJExpressionPointcut();
|
||||
anySpringMethodAnnotation.setExpression(expression);
|
||||
@@ -468,7 +467,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationOnMethodArgumentsWithFQN() throws Exception {
|
||||
void testAnnotationOnMethodArgumentsWithFQN() throws Exception {
|
||||
String expression = "@args(*, test.annotation.EmptySpringAnnotation))";
|
||||
AspectJExpressionPointcut takesSpringAnnotatedArgument2 = new AspectJExpressionPointcut();
|
||||
takesSpringAnnotatedArgument2.setExpression(expression);
|
||||
@@ -497,7 +496,7 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotationOnMethodArgumentsWithWildcards() throws Exception {
|
||||
void testAnnotationOnMethodArgumentsWithWildcards() throws Exception {
|
||||
String expression = "execution(* *(*, @(test..*) *))";
|
||||
AspectJExpressionPointcut takesSpringAnnotatedArgument2 = new AspectJExpressionPointcut();
|
||||
takesSpringAnnotatedArgument2.setExpression(expression);
|
||||
|
||||
+4
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -28,10 +28,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Ramnivas Laddad
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class BeanNamePointcutMatchingTests {
|
||||
class BeanNamePointcutMatchingTests {
|
||||
|
||||
@Test
|
||||
public void testMatchingPointcuts() {
|
||||
void testMatchingPointcuts() {
|
||||
assertMatch("someName", "bean(someName)");
|
||||
|
||||
// Spring bean names are less restrictive compared to AspectJ names (methods, types etc.)
|
||||
@@ -66,7 +66,7 @@ public class BeanNamePointcutMatchingTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonMatchingPointcuts() {
|
||||
void testNonMatchingPointcuts() {
|
||||
assertMisMatch("someName", "bean(someNamex)");
|
||||
assertMisMatch("someName", "bean(someX*Name)");
|
||||
|
||||
@@ -87,7 +87,6 @@ public class BeanNamePointcutMatchingTests {
|
||||
}
|
||||
|
||||
private static boolean matches(final String beanName, String pcExpression) {
|
||||
@SuppressWarnings("serial")
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut() {
|
||||
@Override
|
||||
protected String getCurrentProxiedBeanName() {
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -46,20 +46,20 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* @author Ramnivas Laddad
|
||||
* @since 2.0
|
||||
*/
|
||||
public class MethodInvocationProceedingJoinPointTests {
|
||||
class MethodInvocationProceedingJoinPointTests {
|
||||
|
||||
@Test
|
||||
public void testingBindingWithJoinPoint() {
|
||||
void testingBindingWithJoinPoint() {
|
||||
assertThatIllegalStateException().isThrownBy(AbstractAspectJAdvice::currentJoinPoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testingBindingWithProceedingJoinPoint() {
|
||||
void testingBindingWithProceedingJoinPoint() {
|
||||
assertThatIllegalStateException().isThrownBy(AbstractAspectJAdvice::currentJoinPoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanGetMethodSignatureFromJoinPoint() {
|
||||
void testCanGetMethodSignatureFromJoinPoint() {
|
||||
final Object raw = new TestBean();
|
||||
// Will be set by advice during a method call
|
||||
final int newAge = 23;
|
||||
@@ -118,7 +118,7 @@ public class MethodInvocationProceedingJoinPointTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanGetSourceLocationFromJoinPoint() {
|
||||
void testCanGetSourceLocationFromJoinPoint() {
|
||||
final Object raw = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(raw);
|
||||
pf.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
|
||||
@@ -135,7 +135,7 @@ public class MethodInvocationProceedingJoinPointTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanGetStaticPartFromJoinPoint() {
|
||||
void testCanGetStaticPartFromJoinPoint() {
|
||||
final Object raw = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(raw);
|
||||
pf.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
|
||||
@@ -152,7 +152,7 @@ public class MethodInvocationProceedingJoinPointTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toShortAndLongStringFormedCorrectly() throws Exception {
|
||||
void toShortAndLongStringFormedCorrectly() {
|
||||
final Object raw = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(raw);
|
||||
pf.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
|
||||
|
||||
+12
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,17 +40,17 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class TrickyAspectJPointcutExpressionTests {
|
||||
class TrickyAspectJPointcutExpressionTests {
|
||||
|
||||
@Test
|
||||
public void testManualProxyJavaWithUnconditionalPointcut() throws Exception {
|
||||
void testManualProxyJavaWithUnconditionalPointcut() {
|
||||
TestService target = new TestServiceImpl();
|
||||
LogUserAdvice logAdvice = new LogUserAdvice();
|
||||
testAdvice(new DefaultPointcutAdvisor(logAdvice), logAdvice, target, "TestServiceImpl");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualProxyJavaWithStaticPointcut() throws Exception {
|
||||
void testManualProxyJavaWithStaticPointcut() {
|
||||
TestService target = new TestServiceImpl();
|
||||
LogUserAdvice logAdvice = new LogUserAdvice();
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
@@ -59,7 +59,7 @@ public class TrickyAspectJPointcutExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualProxyJavaWithDynamicPointcut() throws Exception {
|
||||
void testManualProxyJavaWithDynamicPointcut() {
|
||||
TestService target = new TestServiceImpl();
|
||||
LogUserAdvice logAdvice = new LogUserAdvice();
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
@@ -68,7 +68,7 @@ public class TrickyAspectJPointcutExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualProxyJavaWithDynamicPointcutAndProxyTargetClass() throws Exception {
|
||||
void testManualProxyJavaWithDynamicPointcutAndProxyTargetClass() {
|
||||
TestService target = new TestServiceImpl();
|
||||
LogUserAdvice logAdvice = new LogUserAdvice();
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
@@ -77,7 +77,7 @@ public class TrickyAspectJPointcutExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManualProxyJavaWithStaticPointcutAndTwoClassLoaders() throws Exception {
|
||||
void testManualProxyJavaWithStaticPointcutAndTwoClassLoaders() throws Exception {
|
||||
|
||||
LogUserAdvice logAdvice = new LogUserAdvice();
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
@@ -95,13 +95,12 @@ public class TrickyAspectJPointcutExpressionTests {
|
||||
testAdvice(new DefaultPointcutAdvisor(pointcut, logAdvice), logAdvice, other, "TestServiceImpl");
|
||||
}
|
||||
|
||||
private void testAdvice(Advisor advisor, LogUserAdvice logAdvice, TestService target, String message)
|
||||
throws Exception {
|
||||
private void testAdvice(Advisor advisor, LogUserAdvice logAdvice, TestService target, String message) {
|
||||
testAdvice(advisor, logAdvice, target, message, false);
|
||||
}
|
||||
|
||||
private void testAdvice(Advisor advisor, LogUserAdvice logAdvice, TestService target, String message,
|
||||
boolean proxyTargetClass) throws Exception {
|
||||
boolean proxyTargetClass) {
|
||||
|
||||
logAdvice.reset();
|
||||
|
||||
@@ -148,7 +147,7 @@ public class TrickyAspectJPointcutExpressionTests {
|
||||
|
||||
public interface TestService {
|
||||
|
||||
public String sayHello();
|
||||
String sayHello();
|
||||
}
|
||||
|
||||
|
||||
@@ -162,14 +161,14 @@ public class TrickyAspectJPointcutExpressionTests {
|
||||
}
|
||||
|
||||
|
||||
public class LogUserAdvice implements MethodBeforeAdvice, ThrowsAdvice {
|
||||
public static class LogUserAdvice implements MethodBeforeAdvice, ThrowsAdvice {
|
||||
|
||||
private int countBefore = 0;
|
||||
|
||||
private int countThrows = 0;
|
||||
|
||||
@Override
|
||||
public void before(Method method, Object[] objects, @Nullable Object o) throws Throwable {
|
||||
public void before(Method method, Object[] objects, @Nullable Object o) {
|
||||
countBefore++;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ class TypePatternClassFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void invocationOfMatchesMethodBlowsUpWhenNoTypePatternHasBeenSet() throws Exception {
|
||||
void invocationOfMatchesMethodBlowsUpWhenNoTypePatternHasBeenSet() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> new TypePatternClassFilter().matches(String.class));
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -362,7 +362,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
assertThat(lockable.locked()).as("Already locked").isTrue();
|
||||
lockable.lock();
|
||||
assertThat(lockable.locked()).as("Real target ignores locking").isTrue();
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> lockable.unlock());
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(lockable::unlock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -645,7 +645,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
static class NamedPointcutAspectWithFQN {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
|
||||
private final ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
|
||||
|
||||
@Around("org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.CommonPointcuts.getAge()()")
|
||||
int changeReturnValue(ProceedingJoinPoint pjp) {
|
||||
@@ -762,7 +762,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
|
||||
@Aspect
|
||||
class DoublingAspect {
|
||||
static class DoublingAspect {
|
||||
|
||||
@Around("execution(* getAge())")
|
||||
public Object doubleAge(ProceedingJoinPoint pjp) throws Throwable {
|
||||
@@ -771,7 +771,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
|
||||
@Aspect
|
||||
class IncrementingAspect extends DoublingAspect {
|
||||
static class IncrementingAspect extends DoublingAspect {
|
||||
|
||||
@Around("execution(* getAge())")
|
||||
public int incrementAge(ProceedingJoinPoint pjp) throws Throwable {
|
||||
@@ -1080,7 +1080,7 @@ class PerThisAspect {
|
||||
|
||||
// Just to check that this doesn't cause problems with introduction processing
|
||||
@SuppressWarnings("unused")
|
||||
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
|
||||
private final ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
|
||||
|
||||
@Around("execution(int *.getAge())")
|
||||
int returnCountAsAge() {
|
||||
|
||||
+3
-3
@@ -119,7 +119,7 @@ class ArgumentBindingTests {
|
||||
static class PointcutWithAnnotationArgument {
|
||||
|
||||
@Around("execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transactional) {
|
||||
throw new IllegalStateException("Invoked with @Transactional");
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ class ArgumentBindingTests {
|
||||
public void pointcutWithArgs(String s) {}
|
||||
|
||||
@Around("pointcutWithArgs(aString)")
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) {
|
||||
throw new IllegalArgumentException(aString);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ class ArgumentBindingTests {
|
||||
static class DynamicPointcutWithArgs {
|
||||
|
||||
@Around("execution(* *(..)) && args(java.lang.String)")
|
||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
public Object doAround(ProceedingJoinPoint pjp) {
|
||||
throw new IllegalArgumentException(String.valueOf(pjp.getArgs()[0]));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -20,9 +20,9 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcutTests;
|
||||
import org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.ExceptionThrowingAspect;
|
||||
import org.springframework.aop.framework.AopConfigException;
|
||||
import org.springframework.aop.testfixture.aspectj.CommonExpressions;
|
||||
import org.springframework.aop.testfixture.aspectj.PerTargetAspect;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
|
||||
@@ -33,15 +33,15 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class AspectJPointcutAdvisorTests {
|
||||
class AspectJPointcutAdvisorTests {
|
||||
|
||||
private final AspectJAdvisorFactory af = new ReflectiveAspectJAdvisorFactory();
|
||||
|
||||
|
||||
@Test
|
||||
public void testSingleton() throws SecurityException, NoSuchMethodException {
|
||||
void testSingleton() throws SecurityException, NoSuchMethodException {
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(AspectJExpressionPointcutTests.MATCH_ALL_METHODS);
|
||||
ajexp.setExpression(CommonExpressions.MATCH_ALL_METHODS);
|
||||
|
||||
InstantiationModelAwarePointcutAdvisorImpl ajpa = new InstantiationModelAwarePointcutAdvisorImpl(
|
||||
ajexp, TestBean.class.getMethod("getAge"), af,
|
||||
@@ -53,9 +53,9 @@ public class AspectJPointcutAdvisorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerTarget() throws SecurityException, NoSuchMethodException {
|
||||
void testPerTarget() throws SecurityException, NoSuchMethodException {
|
||||
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
|
||||
ajexp.setExpression(AspectJExpressionPointcutTests.MATCH_ALL_METHODS);
|
||||
ajexp.setExpression(CommonExpressions.MATCH_ALL_METHODS);
|
||||
|
||||
InstantiationModelAwarePointcutAdvisorImpl ajpa = new InstantiationModelAwarePointcutAdvisorImpl(
|
||||
ajexp, TestBean.class.getMethod("getAge"), af,
|
||||
@@ -76,13 +76,13 @@ public class AspectJPointcutAdvisorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerCflowTarget() {
|
||||
void testPerCflowTarget() {
|
||||
assertThatExceptionOfType(AopConfigException.class).isThrownBy(() ->
|
||||
testIllegalInstantiationModel(AbstractAspectJAdvisorFactoryTests.PerCflowAspect.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerCflowBelowTarget() {
|
||||
void testPerCflowBelowTarget() {
|
||||
assertThatExceptionOfType(AopConfigException.class).isThrownBy(() ->
|
||||
testIllegalInstantiationModel(AbstractAspectJAdvisorFactoryTests.PerCflowBelowAspect.class));
|
||||
}
|
||||
|
||||
+13
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -36,17 +36,17 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class AspectProxyFactoryTests {
|
||||
class AspectProxyFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testWithNonAspect() {
|
||||
void testWithNonAspect() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
proxyFactory.addAspect(TestBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithSimpleAspect() throws Exception {
|
||||
void testWithSimpleAspect() {
|
||||
TestBean bean = new TestBean();
|
||||
bean.setAge(2);
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(bean);
|
||||
@@ -56,7 +56,7 @@ public class AspectProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPerThisAspect() throws Exception {
|
||||
void testWithPerThisAspect() {
|
||||
TestBean bean1 = new TestBean();
|
||||
TestBean bean2 = new TestBean();
|
||||
|
||||
@@ -76,7 +76,7 @@ public class AspectProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithInstanceWithNonAspect() throws Exception {
|
||||
void testWithInstanceWithNonAspect() {
|
||||
AspectJProxyFactory pf = new AspectJProxyFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
pf.addAspect(new TestBean()));
|
||||
@@ -84,7 +84,7 @@ public class AspectProxyFactoryTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSerializable() throws Exception {
|
||||
void testSerializable() throws Exception {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(LoggingAspectOnVarargs.class);
|
||||
ITestBean proxy = proxyFactory.getProxy();
|
||||
@@ -94,7 +94,7 @@ public class AspectProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithInstance() throws Exception {
|
||||
void testWithInstance() throws Exception {
|
||||
MultiplyReturnValue aspect = new MultiplyReturnValue();
|
||||
int multiple = 3;
|
||||
aspect.setMultiple(multiple);
|
||||
@@ -113,14 +113,13 @@ public class AspectProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNonSingletonAspectInstance() throws Exception {
|
||||
void testWithNonSingletonAspectInstance() {
|
||||
AspectJProxyFactory pf = new AspectJProxyFactory();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> pf.addAspect(new PerThisAspect()));
|
||||
}
|
||||
|
||||
@Test // SPR-13328
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testProxiedVarargsWithEnumArray() throws Exception {
|
||||
public void testProxiedVarargsWithEnumArray() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(LoggingAspectOnVarargs.class);
|
||||
ITestBean proxy = proxyFactory.getProxy();
|
||||
@@ -128,8 +127,7 @@ public class AspectProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test // SPR-13328
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testUnproxiedVarargsWithEnumArray() throws Exception {
|
||||
public void testUnproxiedVarargsWithEnumArray() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(LoggingAspectOnSetter.class);
|
||||
ITestBean proxy = proxyFactory.getProxy();
|
||||
@@ -174,13 +172,13 @@ public class AspectProxyFactoryTests {
|
||||
|
||||
public enum MyEnum implements MyInterface {
|
||||
|
||||
A, B;
|
||||
A, B
|
||||
}
|
||||
|
||||
|
||||
public enum MyOtherEnum implements MyInterface {
|
||||
|
||||
C, D;
|
||||
C, D
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -37,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Rob Harrop
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class AspectJNamespaceHandlerTests {
|
||||
class AspectJNamespaceHandlerTests {
|
||||
|
||||
private ParserContext parserContext;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class AspectJNamespaceHandlerTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
SourceExtractor sourceExtractor = new PassThroughSourceExtractor();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.registry);
|
||||
XmlReaderContext readerContext =
|
||||
@@ -56,7 +56,7 @@ public class AspectJNamespaceHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterAutoProxyCreator() throws Exception {
|
||||
void testRegisterAutoProxyCreator() {
|
||||
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(this.parserContext, null);
|
||||
assertThat(registry.getBeanDefinitionCount()).as("Incorrect number of definitions registered").isEqualTo(1);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class AspectJNamespaceHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterAspectJAutoProxyCreator() throws Exception {
|
||||
void testRegisterAspectJAutoProxyCreator() {
|
||||
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(this.parserContext, null);
|
||||
assertThat(registry.getBeanDefinitionCount()).as("Incorrect number of definitions registered").isEqualTo(1);
|
||||
|
||||
@@ -77,7 +77,7 @@ public class AspectJNamespaceHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterAspectJAutoProxyCreatorWithExistingAutoProxyCreator() throws Exception {
|
||||
void testRegisterAspectJAutoProxyCreatorWithExistingAutoProxyCreator() {
|
||||
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(this.parserContext, null);
|
||||
assertThat(registry.getBeanDefinitionCount()).isEqualTo(1);
|
||||
|
||||
@@ -89,7 +89,7 @@ public class AspectJNamespaceHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterAutoProxyCreatorWhenAspectJAutoProxyCreatorAlreadyExists() throws Exception {
|
||||
void testRegisterAutoProxyCreatorWhenAspectJAutoProxyCreatorAlreadyExists() {
|
||||
AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(this.parserContext, null);
|
||||
assertThat(registry.getBeanDefinitionCount()).isEqualTo(1);
|
||||
|
||||
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Adrian Colyer
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class AspectJPrecedenceComparatorTests {
|
||||
class AspectJPrecedenceComparatorTests {
|
||||
|
||||
private static final int HIGH_PRECEDENCE_ADVISOR_ORDER = 100;
|
||||
private static final int LOW_PRECEDENCE_ADVISOR_ORDER = 200;
|
||||
@@ -56,7 +56,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
this.comparator = new AspectJPrecedenceComparator();
|
||||
this.anyOldMethod = getClass().getMethods()[0];
|
||||
this.anyOldPointcut = new AspectJExpressionPointcut();
|
||||
@@ -65,7 +65,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testSameAspectNoAfterAdvice() {
|
||||
void testSameAspectNoAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor1 sorted before advisor2").isEqualTo(-1);
|
||||
@@ -76,7 +76,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameAspectAfterAdvice() {
|
||||
void testSameAspectAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJAfterAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJAroundAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor2 sorted before advisor1").isEqualTo(1);
|
||||
@@ -87,14 +87,14 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameAspectOneOfEach() {
|
||||
void testSameAspectOneOfEach() {
|
||||
Advisor advisor1 = createAspectJAfterAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor1 and advisor2 not comparable").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameAdvisorPrecedenceDifferentAspectNoAfterAdvice() {
|
||||
void testSameAdvisorPrecedenceDifferentAspectNoAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someOtherAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("nothing to say about order here").isEqualTo(0);
|
||||
@@ -105,7 +105,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameAdvisorPrecedenceDifferentAspectAfterAdvice() {
|
||||
void testSameAdvisorPrecedenceDifferentAspectAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJAfterAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJAroundAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someOtherAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("nothing to say about order here").isEqualTo(0);
|
||||
@@ -116,7 +116,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHigherAdvisorPrecedenceNoAfterAdvice() {
|
||||
void testHigherAdvisorPrecedenceNoAfterAdvice() {
|
||||
Advisor advisor1 = createSpringAOPBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER);
|
||||
Advisor advisor2 = createAspectJBeforeAdvice(LOW_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someOtherAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor1 sorted before advisor2").isEqualTo(-1);
|
||||
@@ -127,7 +127,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHigherAdvisorPrecedenceAfterAdvice() {
|
||||
void testHigherAdvisorPrecedenceAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJAfterAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJAroundAdvice(LOW_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someOtherAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor1 sorted before advisor2").isEqualTo(-1);
|
||||
@@ -138,7 +138,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerAdvisorPrecedenceNoAfterAdvice() {
|
||||
void testLowerAdvisorPrecedenceNoAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJBeforeAdvice(LOW_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJBeforeAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someOtherAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor1 sorted after advisor2").isEqualTo(1);
|
||||
@@ -149,7 +149,7 @@ public class AspectJPrecedenceComparatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerAdvisorPrecedenceAfterAdvice() {
|
||||
void testLowerAdvisorPrecedenceAfterAdvice() {
|
||||
Advisor advisor1 = createAspectJAfterAdvice(LOW_PRECEDENCE_ADVISOR_ORDER, EARLY_ADVICE_DECLARATION_ORDER, "someAspect");
|
||||
Advisor advisor2 = createAspectJAroundAdvice(HIGH_PRECEDENCE_ADVISOR_ORDER, LATE_ADVICE_DECLARATION_ORDER, "someOtherAspect");
|
||||
assertThat(this.comparator.compare(advisor1, advisor2)).as("advisor1 sorted after advisor2").isEqualTo(1);
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -25,14 +25,14 @@ import org.springframework.util.StopWatch;
|
||||
|
||||
/**
|
||||
* Benchmarks for introductions.
|
||||
*
|
||||
* <p>
|
||||
* NOTE: No assertions!
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
* @since 2.0
|
||||
*/
|
||||
public class IntroductionBenchmarkTests {
|
||||
class IntroductionBenchmarkTests {
|
||||
|
||||
private static final int EXPECTED_COMPARE = 13;
|
||||
|
||||
@@ -53,7 +53,7 @@ public class IntroductionBenchmarkTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeManyInvocations() {
|
||||
void timeManyInvocations() {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
TestBean target = new TestBean();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -29,14 +29,14 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class NullPrimitiveTests {
|
||||
class NullPrimitiveTests {
|
||||
|
||||
interface Foo {
|
||||
int getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPrimitiveWithJdkProxy() {
|
||||
void testNullPrimitiveWithJdkProxy() {
|
||||
|
||||
class SimpleFoo implements Foo {
|
||||
@Override
|
||||
@@ -51,8 +51,7 @@ public class NullPrimitiveTests {
|
||||
|
||||
Foo foo = (Foo) factory.getProxy();
|
||||
|
||||
assertThatExceptionOfType(AopInvocationException.class).isThrownBy(() ->
|
||||
foo.getValue())
|
||||
assertThatExceptionOfType(AopInvocationException.class).isThrownBy(foo::getValue)
|
||||
.withMessageContaining("Foo.getValue()");
|
||||
}
|
||||
|
||||
@@ -63,7 +62,7 @@ public class NullPrimitiveTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPrimitiveWithCglibProxy() {
|
||||
void testNullPrimitiveWithCglibProxy() {
|
||||
|
||||
Bar target = new Bar();
|
||||
ProxyFactory factory = new ProxyFactory(target);
|
||||
@@ -71,8 +70,7 @@ public class NullPrimitiveTests {
|
||||
|
||||
Bar bar = (Bar) factory.getProxy();
|
||||
|
||||
assertThatExceptionOfType(AopInvocationException.class).isThrownBy(() ->
|
||||
bar.getValue())
|
||||
assertThatExceptionOfType(AopInvocationException.class).isThrownBy(bar::getValue)
|
||||
.withMessageContaining("Bar.getValue()");
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -32,13 +32,13 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Chris Beams
|
||||
* @since 03.09.2004
|
||||
*/
|
||||
public class PrototypeTargetTests {
|
||||
class PrototypeTargetTests {
|
||||
|
||||
private static final Resource CONTEXT = qualifiedResource(PrototypeTargetTests.class, "context.xml");
|
||||
|
||||
|
||||
@Test
|
||||
public void testPrototypeProxyWithPrototypeTarget() {
|
||||
void testPrototypeProxyWithPrototypeTarget() {
|
||||
TestBeanImpl.constructionCount = 0;
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CONTEXT);
|
||||
@@ -52,7 +52,7 @@ public class PrototypeTargetTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingletonProxyWithPrototypeTarget() {
|
||||
void testSingletonProxyWithPrototypeTarget() {
|
||||
TestBeanImpl.constructionCount = 0;
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CONTEXT);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -57,10 +57,10 @@ import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
||||
* @author Chris Beams
|
||||
* @since 14.05.2003
|
||||
*/
|
||||
public class ProxyFactoryTests {
|
||||
class ProxyFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testIndexOfMethods() {
|
||||
void testIndexOfMethods() {
|
||||
TestBean target = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
@@ -76,7 +76,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAdvisorByReference() {
|
||||
void testRemoveAdvisorByReference() {
|
||||
TestBean target = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
@@ -96,7 +96,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAdvisorByIndex() {
|
||||
void testRemoveAdvisorByIndex() {
|
||||
TestBean target = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
@@ -144,7 +144,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceAdvisor() {
|
||||
void testReplaceAdvisor() {
|
||||
TestBean target = new TestBean();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
NopInterceptor nop = new NopInterceptor();
|
||||
@@ -173,7 +173,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRepeatedInterface() {
|
||||
void testAddRepeatedInterface() {
|
||||
TimeStamped tst = () -> {
|
||||
throw new UnsupportedOperationException("getTimeStamp");
|
||||
};
|
||||
@@ -186,7 +186,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetsAllInterfaces() {
|
||||
void testGetsAllInterfaces() {
|
||||
// Extend to get new interface
|
||||
class TestBeanSubclass extends TestBean implements Comparable<Object> {
|
||||
@Override
|
||||
@@ -220,10 +220,10 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterceptorInclusionMethods() {
|
||||
void testInterceptorInclusionMethods() {
|
||||
class MyInterceptor implements MethodInterceptor {
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
public Object invoke(MethodInvocation invocation) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -244,9 +244,9 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSealedInterfaceExclusion() {
|
||||
void testSealedInterfaceExclusion() {
|
||||
// String implements ConstantDesc on JDK 12+, sealed as of JDK 17
|
||||
ProxyFactory factory = new ProxyFactory(new String());
|
||||
ProxyFactory factory = new ProxyFactory("");
|
||||
NopInterceptor di = new NopInterceptor();
|
||||
factory.addAdvice(0, di);
|
||||
Object proxy = factory.getProxy();
|
||||
@@ -257,7 +257,7 @@ public class ProxyFactoryTests {
|
||||
* Should see effect immediately on behavior.
|
||||
*/
|
||||
@Test
|
||||
public void testCanAddAndRemoveAspectInterfacesOnSingleton() {
|
||||
void testCanAddAndRemoveAspectInterfacesOnSingleton() {
|
||||
ProxyFactory config = new ProxyFactory(new TestBean());
|
||||
|
||||
assertThat(config.getProxy()).as("Shouldn't implement TimeStamped before manipulation")
|
||||
@@ -304,7 +304,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyTargetClassWithInterfaceAsTarget() {
|
||||
void testProxyTargetClassWithInterfaceAsTarget() {
|
||||
ProxyFactory pf = new ProxyFactory();
|
||||
pf.setTargetClass(ITestBean.class);
|
||||
Object proxy = pf.getProxy();
|
||||
@@ -320,7 +320,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyTargetClassWithConcreteClassAsTarget() {
|
||||
void testProxyTargetClassWithConcreteClassAsTarget() {
|
||||
ProxyFactory pf = new ProxyFactory();
|
||||
pf.setTargetClass(TestBean.class);
|
||||
Object proxy = pf.getProxy();
|
||||
@@ -347,19 +347,18 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterfaceProxiesCanBeOrderedThroughAnnotations() {
|
||||
void testInterfaceProxiesCanBeOrderedThroughAnnotations() {
|
||||
Object proxy1 = new ProxyFactory(new A()).getProxy();
|
||||
Object proxy2 = new ProxyFactory(new B()).getProxy();
|
||||
List<Object> list = new ArrayList<>(2);
|
||||
list.add(proxy1);
|
||||
list.add(proxy2);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
assertThat(list).element(0).isSameAs(proxy2);
|
||||
assertThat(list).element(1).isSameAs(proxy1);
|
||||
assertThat(list).containsExactly(proxy2, proxy1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTargetClassProxiesCanBeOrderedThroughAnnotations() {
|
||||
void testTargetClassProxiesCanBeOrderedThroughAnnotations() {
|
||||
ProxyFactory pf1 = new ProxyFactory(new A());
|
||||
pf1.setProxyTargetClass(true);
|
||||
ProxyFactory pf2 = new ProxyFactory(new B());
|
||||
@@ -370,12 +369,11 @@ public class ProxyFactoryTests {
|
||||
list.add(proxy1);
|
||||
list.add(proxy2);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
assertThat(list).element(0).isSameAs(proxy2);
|
||||
assertThat(list).element(1).isSameAs(proxy1);
|
||||
assertThat(list).containsExactly(proxy2, proxy1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterceptorWithoutJoinpoint() {
|
||||
void testInterceptorWithoutJoinpoint() {
|
||||
final TestBean target = new TestBean("tb");
|
||||
ITestBean proxy = ProxyFactory.getProxy(ITestBean.class, (MethodInterceptor) invocation -> {
|
||||
assertThat(invocation.getThis()).isNull();
|
||||
@@ -385,7 +383,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharSequenceProxy() {
|
||||
void testCharSequenceProxy() {
|
||||
CharSequence target = "test";
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
ClassLoader cl = target.getClass().getClassLoader();
|
||||
@@ -395,7 +393,7 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateProxy() {
|
||||
void testDateProxy() {
|
||||
Date target = new Date();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
pf.setProxyTargetClass(true);
|
||||
@@ -406,14 +404,14 @@ public class ProxyFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJdbcSavepointProxy() throws SQLException {
|
||||
void testJdbcSavepointProxy() throws SQLException {
|
||||
Savepoint target = new Savepoint() {
|
||||
@Override
|
||||
public int getSavepointId() throws SQLException {
|
||||
public int getSavepointId() {
|
||||
return 1;
|
||||
}
|
||||
@Override
|
||||
public String getSavepointName() throws SQLException {
|
||||
public String getSavepointName() {
|
||||
return "sp";
|
||||
}
|
||||
};
|
||||
|
||||
+8
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -37,17 +37,17 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class ThrowsAdviceInterceptorTests {
|
||||
class ThrowsAdviceInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void testNoHandlerMethods() {
|
||||
void testNoHandlerMethods() {
|
||||
// should require one handler method at least
|
||||
assertThatExceptionOfType(AopConfigException.class).isThrownBy(() ->
|
||||
new ThrowsAdviceInterceptor(new Object()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotInvoked() throws Throwable {
|
||||
void testNotInvoked() throws Throwable {
|
||||
MyThrowsHandler th = new MyThrowsHandler();
|
||||
ThrowsAdviceInterceptor ti = new ThrowsAdviceInterceptor(th);
|
||||
Object ret = new Object();
|
||||
@@ -58,7 +58,7 @@ public class ThrowsAdviceInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoHandlerMethodForThrowable() throws Throwable {
|
||||
void testNoHandlerMethodForThrowable() throws Throwable {
|
||||
MyThrowsHandler th = new MyThrowsHandler();
|
||||
ThrowsAdviceInterceptor ti = new ThrowsAdviceInterceptor(th);
|
||||
assertThat(ti.getHandlerMethodCount()).isEqualTo(2);
|
||||
@@ -70,7 +70,7 @@ public class ThrowsAdviceInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectHandlerUsed() throws Throwable {
|
||||
void testCorrectHandlerUsed() throws Throwable {
|
||||
MyThrowsHandler th = new MyThrowsHandler();
|
||||
ThrowsAdviceInterceptor ti = new ThrowsAdviceInterceptor(th);
|
||||
FileNotFoundException ex = new FileNotFoundException();
|
||||
@@ -84,7 +84,7 @@ public class ThrowsAdviceInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectHandlerUsedForSubclass() throws Throwable {
|
||||
void testCorrectHandlerUsedForSubclass() throws Throwable {
|
||||
MyThrowsHandler th = new MyThrowsHandler();
|
||||
ThrowsAdviceInterceptor ti = new ThrowsAdviceInterceptor(th);
|
||||
// Extends RemoteException
|
||||
@@ -97,10 +97,9 @@ public class ThrowsAdviceInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandlerMethodThrowsException() throws Throwable {
|
||||
void testHandlerMethodThrowsException() throws Throwable {
|
||||
final Throwable t = new Throwable();
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
MyThrowsHandler th = new MyThrowsHandler() {
|
||||
@Override
|
||||
public void afterThrowing(RemoteException ex) throws Throwable {
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Chris Beams
|
||||
* @since 06.04.2004
|
||||
*/
|
||||
public class ConcurrencyThrottleInterceptorTests {
|
||||
class ConcurrencyThrottleInterceptorTests {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(ConcurrencyThrottleInterceptorTests.class);
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ConcurrencyThrottleInterceptorTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testSerializable() throws Exception {
|
||||
void testSerializable() throws Exception {
|
||||
DerivedTestBean tb = new DerivedTestBean();
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
proxyFactory.setInterfaces(ITestBean.class);
|
||||
@@ -63,12 +63,12 @@ public class ConcurrencyThrottleInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleThreadsWithLimit1() {
|
||||
void testMultipleThreadsWithLimit1() {
|
||||
testMultipleThreads(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleThreadsWithLimit10() {
|
||||
void testMultipleThreadsWithLimit10() {
|
||||
testMultipleThreads(10);
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ public class ConcurrencyThrottleInterceptorTests {
|
||||
|
||||
private static class ConcurrencyThread extends Thread {
|
||||
|
||||
private ITestBean proxy;
|
||||
private Throwable ex;
|
||||
private final ITestBean proxy;
|
||||
private final Throwable ex;
|
||||
|
||||
public ConcurrencyThread(ITestBean proxy, Throwable ex) {
|
||||
this.proxy = proxy;
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -144,7 +144,7 @@ class CustomizableTraceInterceptorTests {
|
||||
void sunnyDayPathLogsCorrectlyWithPrettyMuchAllPlaceholdersMatching() throws Throwable {
|
||||
MethodInvocation methodInvocation = mock();
|
||||
|
||||
given(methodInvocation.getMethod()).willReturn(String.class.getMethod("toString", new Class[0]));
|
||||
given(methodInvocation.getMethod()).willReturn(String.class.getMethod("toString"));
|
||||
given(methodInvocation.getThis()).willReturn(this);
|
||||
given(methodInvocation.getArguments()).willReturn(new Object[]{"$ One \\$", 2L});
|
||||
given(methodInvocation.proceed()).willReturn("Hello!");
|
||||
@@ -177,7 +177,6 @@ class CustomizableTraceInterceptorTests {
|
||||
* is properly configured in {@link CustomizableTraceInterceptor}.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void supportedPlaceholderValues() {
|
||||
assertThat(ALLOWED_PLACEHOLDERS).containsExactlyInAnyOrderElementsOf(getPlaceholderConstantValues());
|
||||
}
|
||||
|
||||
+3
-3
@@ -35,10 +35,10 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class DebugInterceptorTests {
|
||||
class DebugInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void testSunnyDayPathLogsCorrectly() throws Throwable {
|
||||
void testSunnyDayPathLogsCorrectly() throws Throwable {
|
||||
MethodInvocation methodInvocation = mock();
|
||||
|
||||
Log log = mock();
|
||||
@@ -52,7 +52,7 @@ public class DebugInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionPathStillLogsCorrectly() throws Throwable {
|
||||
void testExceptionPathStillLogsCorrectly() throws Throwable {
|
||||
MethodInvocation methodInvocation = mock();
|
||||
|
||||
IllegalArgumentException exception = new IllegalArgumentException();
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -29,9 +29,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class ExposeBeanNameAdvisorsTests {
|
||||
class ExposeBeanNameAdvisorsTests {
|
||||
|
||||
private class RequiresBeanNameBoundTestBean extends TestBean {
|
||||
private static class RequiresBeanNameBoundTestBean extends TestBean {
|
||||
private final String beanName;
|
||||
|
||||
public RequiresBeanNameBoundTestBean(String beanName) {
|
||||
@@ -46,7 +46,7 @@ public class ExposeBeanNameAdvisorsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoIntroduction() {
|
||||
void testNoIntroduction() {
|
||||
String beanName = "foo";
|
||||
TestBean target = new RequiresBeanNameBoundTestBean(beanName);
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
@@ -61,7 +61,7 @@ public class ExposeBeanNameAdvisorsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithIntroduction() {
|
||||
void testWithIntroduction() {
|
||||
String beanName = "foo";
|
||||
TestBean target = new RequiresBeanNameBoundTestBean(beanName);
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -31,10 +31,10 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class ExposeInvocationInterceptorTests {
|
||||
class ExposeInvocationInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void testXmlConfig() {
|
||||
void testXmlConfig() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
qualifiedResource(ExposeInvocationInterceptorTests.class, "context.xml"));
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -32,10 +32,10 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class PerformanceMonitorInterceptorTests {
|
||||
class PerformanceMonitorInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void testSuffixAndPrefixAssignment() {
|
||||
void testSuffixAndPrefixAssignment() {
|
||||
PerformanceMonitorInterceptor interceptor = new PerformanceMonitorInterceptor();
|
||||
|
||||
assertThat(interceptor.getPrefix()).isNotNull();
|
||||
@@ -49,9 +49,9 @@ public class PerformanceMonitorInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSunnyDayPathLogsPerformanceMetricsCorrectly() throws Throwable {
|
||||
void testSunnyDayPathLogsPerformanceMetricsCorrectly() throws Throwable {
|
||||
MethodInvocation mi = mock();
|
||||
given(mi.getMethod()).willReturn(String.class.getMethod("toString", new Class[0]));
|
||||
given(mi.getMethod()).willReturn(String.class.getMethod("toString"));
|
||||
|
||||
Log log = mock();
|
||||
|
||||
@@ -62,10 +62,10 @@ public class PerformanceMonitorInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionPathStillLogsPerformanceMetricsCorrectly() throws Throwable {
|
||||
void testExceptionPathStillLogsPerformanceMetricsCorrectly() throws Throwable {
|
||||
MethodInvocation mi = mock();
|
||||
|
||||
given(mi.getMethod()).willReturn(String.class.getMethod("toString", new Class[0]));
|
||||
given(mi.getMethod()).willReturn(String.class.getMethod("toString"));
|
||||
given(mi.proceed()).willThrow(new IllegalArgumentException());
|
||||
Log log = mock();
|
||||
|
||||
|
||||
+3
-3
@@ -34,10 +34,10 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class SimpleTraceInterceptorTests {
|
||||
class SimpleTraceInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void testSunnyDayPathLogsCorrectly() throws Throwable {
|
||||
void testSunnyDayPathLogsCorrectly() throws Throwable {
|
||||
MethodInvocation mi = mock();
|
||||
given(mi.getMethod()).willReturn(String.class.getMethod("toString"));
|
||||
given(mi.getThis()).willReturn(this);
|
||||
@@ -51,7 +51,7 @@ public class SimpleTraceInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionPathStillLogsCorrectly() throws Throwable {
|
||||
void testExceptionPathStillLogsCorrectly() throws Throwable {
|
||||
MethodInvocation mi = mock();
|
||||
given(mi.getMethod()).willReturn(String.class.getMethod("toString"));
|
||||
given(mi.getThis()).willReturn(this);
|
||||
|
||||
+5
-5
@@ -29,31 +29,31 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class DefaultScopedObjectTests {
|
||||
class DefaultScopedObjectTests {
|
||||
|
||||
private static final String GOOD_BEAN_NAME = "foo";
|
||||
|
||||
|
||||
@Test
|
||||
public void testCtorWithNullBeanFactory() throws Exception {
|
||||
void testCtorWithNullBeanFactory() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new DefaultScopedObject(null, GOOD_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCtorWithNullTargetBeanName() throws Exception {
|
||||
void testCtorWithNullTargetBeanName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
testBadTargetBeanName(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCtorWithEmptyTargetBeanName() throws Exception {
|
||||
void testCtorWithEmptyTargetBeanName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
testBadTargetBeanName(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCtorWithJustWhitespacedTargetBeanName() throws Exception {
|
||||
void testCtorWithJustWhitespacedTargetBeanName() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
testBadTargetBeanName(" "));
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -31,10 +31,10 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class ScopedProxyAutowireTests {
|
||||
class ScopedProxyAutowireTests {
|
||||
|
||||
@Test
|
||||
public void testScopedProxyInheritsAutowireCandidateFalse() {
|
||||
void testScopedProxyInheritsAutowireCandidateFalse() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
qualifiedResource(ScopedProxyAutowireTests.class, "scopedAutowireFalse.xml"));
|
||||
@@ -48,7 +48,7 @@ public class ScopedProxyAutowireTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScopedProxyReplacesAutowireCandidateTrue() {
|
||||
void testScopedProxyReplacesAutowireCandidateTrue() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
qualifiedResource(ScopedProxyAutowireTests.class, "scopedAutowireTrue.xml"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -38,10 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Chris Beams
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class AopUtilsTests {
|
||||
class AopUtilsTests {
|
||||
|
||||
@Test
|
||||
public void testPointcutCanNeverApply() {
|
||||
void testPointcutCanNeverApply() {
|
||||
class TestPointcut extends StaticMethodMatcherPointcut {
|
||||
@Override
|
||||
public boolean matches(Method method, @Nullable Class<?> clazzy) {
|
||||
@@ -54,13 +54,13 @@ public class AopUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPointcutAlwaysApplies() {
|
||||
void testPointcutAlwaysApplies() {
|
||||
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), Object.class)).isTrue();
|
||||
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), TestBean.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPointcutAppliesToOneMethodOnObject() {
|
||||
void testPointcutAppliesToOneMethodOnObject() {
|
||||
class TestPointcut extends StaticMethodMatcherPointcut {
|
||||
@Override
|
||||
public boolean matches(Method method, @Nullable Class<?> clazz) {
|
||||
@@ -80,7 +80,7 @@ public class AopUtilsTests {
|
||||
* that's subverted the singleton construction limitation.
|
||||
*/
|
||||
@Test
|
||||
public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
|
||||
void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE)).isSameAs(MethodMatcher.TRUE);
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(ClassFilter.TRUE)).isSameAs(ClassFilter.TRUE);
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(Pointcut.TRUE)).isSameAs(Pointcut.TRUE);
|
||||
@@ -91,7 +91,7 @@ public class AopUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeJoinpointUsingReflection() throws Throwable {
|
||||
void testInvokeJoinpointUsingReflection() throws Throwable {
|
||||
String name = "foo";
|
||||
TestBean testBean = new TestBean(name);
|
||||
Method method = ReflectionUtils.findMethod(TestBean.class, "getName");
|
||||
|
||||
+7
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class ComposablePointcutTests {
|
||||
class ComposablePointcutTests {
|
||||
|
||||
public static MethodMatcher GETTER_METHOD_MATCHER = new StaticMethodMatcher() {
|
||||
@Override
|
||||
@@ -56,23 +56,16 @@ public class ComposablePointcutTests {
|
||||
}
|
||||
};
|
||||
|
||||
public static MethodMatcher SETTER_METHOD_MATCHER = new StaticMethodMatcher() {
|
||||
@Override
|
||||
public boolean matches(Method m, @Nullable Class<?> targetClass) {
|
||||
return m.getName().startsWith("set");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
public void testMatchAll() throws NoSuchMethodException {
|
||||
void testMatchAll() throws NoSuchMethodException {
|
||||
Pointcut pc = new ComposablePointcut();
|
||||
assertThat(pc.getClassFilter().matches(Object.class)).isTrue();
|
||||
assertThat(pc.getMethodMatcher().matches(Object.class.getMethod("hashCode"), Exception.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterByClass() throws NoSuchMethodException {
|
||||
void testFilterByClass() {
|
||||
ComposablePointcut pc = new ComposablePointcut();
|
||||
|
||||
assertThat(pc.getClassFilter().matches(Object.class)).isTrue();
|
||||
@@ -92,7 +85,7 @@ public class ComposablePointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionMethodMatcher() {
|
||||
void testUnionMethodMatcher() {
|
||||
// Matches the getAge() method in any class
|
||||
ComposablePointcut pc = new ComposablePointcut(ClassFilter.TRUE, GET_AGE_METHOD_MATCHER);
|
||||
assertThat(Pointcuts.matches(pc, PointcutsTests.TEST_BEAN_ABSQUATULATE, TestBean.class)).isFalse();
|
||||
@@ -115,7 +108,7 @@ public class ComposablePointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntersectionMethodMatcher() {
|
||||
void testIntersectionMethodMatcher() {
|
||||
ComposablePointcut pc = new ComposablePointcut();
|
||||
assertThat(pc.getMethodMatcher().matches(PointcutsTests.TEST_BEAN_ABSQUATULATE, TestBean.class)).isTrue();
|
||||
assertThat(pc.getMethodMatcher().matches(PointcutsTests.TEST_BEAN_GET_AGE, TestBean.class)).isTrue();
|
||||
@@ -132,7 +125,7 @@ public class ComposablePointcutTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsAndHashCode() throws Exception {
|
||||
void testEqualsAndHashCode() {
|
||||
ComposablePointcut pc1 = new ComposablePointcut();
|
||||
ComposablePointcut pc2 = new ComposablePointcut();
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -203,7 +203,7 @@ class ControlFlowPointcutTests {
|
||||
|
||||
// Will not be advised: not under MyComponent
|
||||
assertThat(proxy.getAge()).isEqualTo(target.getAge());
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(1 * evaluationFactor);
|
||||
assertThat(cflow.getEvaluations()).isEqualTo(evaluationFactor);
|
||||
assertThat(nop.getCount()).isEqualTo(0);
|
||||
|
||||
// Will be advised: the proxy is invoked under MyComponent#getAge
|
||||
|
||||
+9
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -47,14 +47,14 @@ import static org.mockito.Mockito.mock;
|
||||
class DelegatingIntroductionInterceptorTests {
|
||||
|
||||
@Test
|
||||
void testNullTarget() throws Exception {
|
||||
void testNullTarget() {
|
||||
// Shouldn't accept null target
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new DelegatingIntroductionInterceptor(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntroductionInterceptorWithDelegation() throws Exception {
|
||||
void testIntroductionInterceptorWithDelegation() {
|
||||
TestBean raw = new TestBean();
|
||||
assertThat(raw).isNotInstanceOf(TimeStamped.class);
|
||||
ProxyFactory factory = new ProxyFactory(raw);
|
||||
@@ -70,7 +70,7 @@ class DelegatingIntroductionInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntroductionInterceptorWithInterfaceHierarchy() throws Exception {
|
||||
void testIntroductionInterceptorWithInterfaceHierarchy() {
|
||||
TestBean raw = new TestBean();
|
||||
assertThat(raw).isNotInstanceOf(SubTimeStamped.class);
|
||||
ProxyFactory factory = new ProxyFactory(raw);
|
||||
@@ -86,7 +86,7 @@ class DelegatingIntroductionInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntroductionInterceptorWithSuperInterface() throws Exception {
|
||||
void testIntroductionInterceptorWithSuperInterface() {
|
||||
TestBean raw = new TestBean();
|
||||
assertThat(raw).isNotInstanceOf(TimeStamped.class);
|
||||
ProxyFactory factory = new ProxyFactory(raw);
|
||||
@@ -107,7 +107,7 @@ class DelegatingIntroductionInterceptorTests {
|
||||
final long t = 1001L;
|
||||
class Tester implements TimeStamped, ITester {
|
||||
@Override
|
||||
public void foo() throws Exception {
|
||||
public void foo() {
|
||||
}
|
||||
@Override
|
||||
public long getTimeStamp() {
|
||||
@@ -138,7 +138,7 @@ class DelegatingIntroductionInterceptorTests {
|
||||
@SuppressWarnings("serial")
|
||||
class TestII extends DelegatingIntroductionInterceptor implements TimeStamped, ITester {
|
||||
@Override
|
||||
public void foo() throws Exception {
|
||||
public void foo() {
|
||||
}
|
||||
@Override
|
||||
public long getTimeStamp() {
|
||||
@@ -177,9 +177,8 @@ class DelegatingIntroductionInterceptorTests {
|
||||
assertThat(o).isNotInstanceOf(TimeStamped.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@Test
|
||||
void testIntroductionInterceptorDoesntReplaceToString() throws Exception {
|
||||
void testIntroductionInterceptorDoesNotReplaceToString() {
|
||||
TestBean raw = new TestBean();
|
||||
assertThat(raw).isNotInstanceOf(TimeStamped.class);
|
||||
ProxyFactory factory = new ProxyFactory(raw);
|
||||
@@ -246,7 +245,7 @@ class DelegatingIntroductionInterceptorTests {
|
||||
|
||||
// Test when target implements the interface: should get interceptor by preference.
|
||||
@Test
|
||||
void testIntroductionMasksTargetImplementation() throws Exception {
|
||||
void testIntroductionMasksTargetImplementation() {
|
||||
final long t = 1001L;
|
||||
@SuppressWarnings("serial")
|
||||
class TestII extends DelegatingIntroductionInterceptor implements TimeStamped {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -34,7 +34,7 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class MethodMatchersTests {
|
||||
class MethodMatchersTests {
|
||||
|
||||
private static final Method TEST_METHOD = mock(Method.class);
|
||||
|
||||
@@ -56,19 +56,19 @@ public class MethodMatchersTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testDefaultMatchesAll() throws Exception {
|
||||
void testDefaultMatchesAll() {
|
||||
MethodMatcher defaultMm = MethodMatcher.TRUE;
|
||||
assertThat(defaultMm.matches(EXCEPTION_GETMESSAGE, Exception.class)).isTrue();
|
||||
assertThat(defaultMm.matches(ITESTBEAN_SETAGE, TestBean.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodMatcherTrueSerializable() throws Exception {
|
||||
void testMethodMatcherTrueSerializable() throws Exception {
|
||||
assertThat(MethodMatcher.TRUE).isSameAs(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingle() throws Exception {
|
||||
void testSingle() {
|
||||
MethodMatcher defaultMm = MethodMatcher.TRUE;
|
||||
assertThat(defaultMm.matches(EXCEPTION_GETMESSAGE, Exception.class)).isTrue();
|
||||
assertThat(defaultMm.matches(ITESTBEAN_SETAGE, TestBean.class)).isTrue();
|
||||
@@ -80,7 +80,7 @@ public class MethodMatchersTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testDynamicAndStaticMethodMatcherIntersection() throws Exception {
|
||||
void testDynamicAndStaticMethodMatcherIntersection() {
|
||||
MethodMatcher mm1 = MethodMatcher.TRUE;
|
||||
MethodMatcher mm2 = new TestDynamicMethodMatcherWhichMatches();
|
||||
MethodMatcher intersection = MethodMatchers.intersection(mm1, mm2);
|
||||
@@ -95,7 +95,7 @@ public class MethodMatchersTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticMethodMatcherUnion() throws Exception {
|
||||
void testStaticMethodMatcherUnion() {
|
||||
MethodMatcher getterMatcher = new StartsWithMatcher("get");
|
||||
MethodMatcher setterMatcher = new StartsWithMatcher("set");
|
||||
MethodMatcher union = MethodMatchers.union(getterMatcher, setterMatcher);
|
||||
@@ -107,7 +107,7 @@ public class MethodMatchersTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionEquals() {
|
||||
void testUnionEquals() {
|
||||
MethodMatcher first = MethodMatchers.union(MethodMatcher.TRUE, MethodMatcher.TRUE);
|
||||
MethodMatcher second = new ComposablePointcut(MethodMatcher.TRUE).union(new ComposablePointcut(MethodMatcher.TRUE)).getMethodMatcher();
|
||||
assertThat(first).isEqualTo(second);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class PointcutsTests {
|
||||
class PointcutsTests {
|
||||
|
||||
public static Method TEST_BEAN_SET_AGE;
|
||||
public static Method TEST_BEAN_GET_AGE;
|
||||
@@ -120,7 +120,7 @@ public class PointcutsTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void testTrue() {
|
||||
void testTrue() {
|
||||
assertThat(Pointcuts.matches(Pointcut.TRUE, TEST_BEAN_SET_AGE, TestBean.class, 6)).isTrue();
|
||||
assertThat(Pointcuts.matches(Pointcut.TRUE, TEST_BEAN_GET_AGE, TestBean.class)).isTrue();
|
||||
assertThat(Pointcuts.matches(Pointcut.TRUE, TEST_BEAN_ABSQUATULATE, TestBean.class)).isTrue();
|
||||
@@ -130,7 +130,7 @@ public class PointcutsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches() {
|
||||
void testMatches() {
|
||||
assertThat(Pointcuts.matches(allClassSetterPointcut, TEST_BEAN_SET_AGE, TestBean.class, 6)).isTrue();
|
||||
assertThat(Pointcuts.matches(allClassSetterPointcut, TEST_BEAN_GET_AGE, TestBean.class)).isFalse();
|
||||
assertThat(Pointcuts.matches(allClassSetterPointcut, TEST_BEAN_ABSQUATULATE, TestBean.class)).isFalse();
|
||||
@@ -143,7 +143,7 @@ public class PointcutsTests {
|
||||
* Should match all setters and getters on any class
|
||||
*/
|
||||
@Test
|
||||
public void testUnionOfSettersAndGetters() {
|
||||
void testUnionOfSettersAndGetters() {
|
||||
Pointcut union = Pointcuts.union(allClassGetterPointcut, allClassSetterPointcut);
|
||||
assertThat(Pointcuts.matches(union, TEST_BEAN_SET_AGE, TestBean.class, 6)).isTrue();
|
||||
assertThat(Pointcuts.matches(union, TEST_BEAN_GET_AGE, TestBean.class)).isTrue();
|
||||
@@ -151,7 +151,7 @@ public class PointcutsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnionOfSpecificGetters() {
|
||||
void testUnionOfSpecificGetters() {
|
||||
Pointcut union = Pointcuts.union(allClassGetAgePointcut, allClassGetNamePointcut);
|
||||
assertThat(Pointcuts.matches(union, TEST_BEAN_SET_AGE, TestBean.class, 6)).isFalse();
|
||||
assertThat(Pointcuts.matches(union, TEST_BEAN_GET_AGE, TestBean.class)).isTrue();
|
||||
@@ -175,7 +175,7 @@ public class PointcutsTests {
|
||||
* Second one matches all getters in the MyTestBean class. TestBean getters shouldn't pass.
|
||||
*/
|
||||
@Test
|
||||
public void testUnionOfAllSettersAndSubclassSetters() {
|
||||
void testUnionOfAllSettersAndSubclassSetters() {
|
||||
assertThat(Pointcuts.matches(myTestBeanSetterPointcut, TEST_BEAN_SET_AGE, TestBean.class, 6)).isFalse();
|
||||
assertThat(Pointcuts.matches(myTestBeanSetterPointcut, TEST_BEAN_SET_AGE, MyTestBean.class, 6)).isTrue();
|
||||
assertThat(Pointcuts.matches(myTestBeanSetterPointcut, TEST_BEAN_GET_AGE, TestBean.class)).isFalse();
|
||||
@@ -193,7 +193,7 @@ public class PointcutsTests {
|
||||
* it's the union of allClassGetAge and subclass getters
|
||||
*/
|
||||
@Test
|
||||
public void testIntersectionOfSpecificGettersAndSubclassGetters() {
|
||||
void testIntersectionOfSpecificGettersAndSubclassGetters() {
|
||||
assertThat(Pointcuts.matches(allClassGetAgePointcut, TEST_BEAN_GET_AGE, TestBean.class)).isTrue();
|
||||
assertThat(Pointcuts.matches(allClassGetAgePointcut, TEST_BEAN_GET_AGE, MyTestBean.class)).isTrue();
|
||||
assertThat(Pointcuts.matches(myTestBeanGetterPointcut, TEST_BEAN_GET_NAME, TestBean.class)).isFalse();
|
||||
@@ -239,7 +239,7 @@ public class PointcutsTests {
|
||||
* The intersection of these two pointcuts leaves nothing.
|
||||
*/
|
||||
@Test
|
||||
public void testSimpleIntersection() {
|
||||
void testSimpleIntersection() {
|
||||
Pointcut intersection = Pointcuts.intersection(allClassGetterPointcut, allClassSetterPointcut);
|
||||
assertThat(Pointcuts.matches(intersection, TEST_BEAN_SET_AGE, TestBean.class, 6)).isFalse();
|
||||
assertThat(Pointcuts.matches(intersection, TEST_BEAN_GET_AGE, TestBean.class)).isFalse();
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -36,14 +36,14 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class RegexpMethodPointcutAdvisorIntegrationTests {
|
||||
class RegexpMethodPointcutAdvisorIntegrationTests {
|
||||
|
||||
private static final Resource CONTEXT =
|
||||
qualifiedResource(RegexpMethodPointcutAdvisorIntegrationTests.class, "context.xml");
|
||||
|
||||
|
||||
@Test
|
||||
public void testSinglePattern() throws Throwable {
|
||||
void testSinglePattern() throws Throwable {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CONTEXT);
|
||||
ITestBean advised = (ITestBean) bf.getBean("settersAdvised");
|
||||
@@ -62,7 +62,7 @@ public class RegexpMethodPointcutAdvisorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplePatterns() throws Throwable {
|
||||
void testMultiplePatterns() throws Throwable {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CONTEXT);
|
||||
// This is a CGLIB proxy, so we can proxy it to the target class
|
||||
@@ -86,7 +86,7 @@ public class RegexpMethodPointcutAdvisorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws Throwable {
|
||||
void testSerialization() throws Throwable {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CONTEXT);
|
||||
// This is a CGLIB proxy, so we can proxy it to the target class
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -30,13 +30,13 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CommonsPool2TargetSourceProxyTests {
|
||||
class CommonsPool2TargetSourceProxyTests {
|
||||
|
||||
private static final Resource CONTEXT =
|
||||
qualifiedResource(CommonsPool2TargetSourceProxyTests.class, "context.xml");
|
||||
|
||||
@Test
|
||||
public void testProxy() throws Exception {
|
||||
void testProxy() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
|
||||
reader.loadBeanDefinitions(CONTEXT);
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -39,7 +39,7 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class HotSwappableTargetSourceTests {
|
||||
class HotSwappableTargetSourceTests {
|
||||
|
||||
/** Initial count value set in bean factory XML */
|
||||
private static final int INITIAL_COUNT = 10;
|
||||
@@ -68,7 +68,7 @@ public class HotSwappableTargetSourceTests {
|
||||
* Check it works like a normal invoker
|
||||
*/
|
||||
@Test
|
||||
public void testBasicFunctionality() {
|
||||
void testBasicFunctionality() {
|
||||
SideEffectBean proxied = (SideEffectBean) beanFactory.getBean("swappable");
|
||||
assertThat(proxied.getCount()).isEqualTo(INITIAL_COUNT);
|
||||
proxied.doWork();
|
||||
@@ -80,7 +80,7 @@ public class HotSwappableTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidSwaps() {
|
||||
void testValidSwaps() {
|
||||
SideEffectBean target1 = (SideEffectBean) beanFactory.getBean("target1");
|
||||
SideEffectBean target2 = (SideEffectBean) beanFactory.getBean("target2");
|
||||
|
||||
@@ -107,7 +107,7 @@ public class HotSwappableTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectsSwapToNull() {
|
||||
void testRejectsSwapToNull() {
|
||||
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
|
||||
assertThatIllegalArgumentException().as("Shouldn't be able to swap to invalid value").isThrownBy(() ->
|
||||
swapper.swap(null))
|
||||
@@ -117,7 +117,7 @@ public class HotSwappableTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws Exception {
|
||||
void testSerialization() throws Exception {
|
||||
SerializablePerson sp1 = new SerializablePerson();
|
||||
sp1.setName("Tony");
|
||||
SerializablePerson sp2 = new SerializablePerson();
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -28,10 +28,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class LazyCreationTargetSourceTests {
|
||||
class LazyCreationTargetSourceTests {
|
||||
|
||||
@Test
|
||||
public void testCreateLazy() {
|
||||
void testCreateLazy() {
|
||||
TargetSource targetSource = new AbstractLazyCreationTargetSource() {
|
||||
@Override
|
||||
protected Object createObject() {
|
||||
|
||||
+6
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -36,10 +36,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class PrototypeBasedTargetSourceTests {
|
||||
class PrototypeBasedTargetSourceTests {
|
||||
|
||||
@Test
|
||||
public void testSerializability() throws Exception {
|
||||
void testSerializability() throws Exception {
|
||||
MutablePropertyValues tsPvs = new MutablePropertyValues();
|
||||
tsPvs.add("targetBeanName", "person");
|
||||
RootBeanDefinition tsBd = new RootBeanDefinition(TestTargetSource.class);
|
||||
@@ -56,10 +56,8 @@ public class PrototypeBasedTargetSourceTests {
|
||||
|
||||
TestTargetSource cpts = (TestTargetSource) bf.getBean("ts");
|
||||
TargetSource serialized = SerializationTestUtils.serializeAndDeserialize(cpts);
|
||||
boolean condition = serialized instanceof SingletonTargetSource;
|
||||
assertThat(condition).as("Changed to SingletonTargetSource on deserialization").isTrue();
|
||||
SingletonTargetSource sts = (SingletonTargetSource) serialized;
|
||||
assertThat(sts.getTarget()).isNotNull();
|
||||
assertThat(serialized).isInstanceOfSatisfying(SingletonTargetSource.class,
|
||||
sts -> assertThat(sts.getTarget()).isNotNull());
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +70,7 @@ public class PrototypeBasedTargetSourceTests {
|
||||
* state can't prevent serialization from working
|
||||
*/
|
||||
@SuppressWarnings({"unused", "serial"})
|
||||
private TestBean thisFieldIsNotSerializable = new TestBean();
|
||||
private final TestBean thisFieldIsNotSerializable = new TestBean();
|
||||
|
||||
@Override
|
||||
public Object getTarget() {
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -30,7 +30,7 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class PrototypeTargetSourceTests {
|
||||
class PrototypeTargetSourceTests {
|
||||
|
||||
/** Initial count value set in bean factory XML */
|
||||
private static final int INITIAL_COUNT = 10;
|
||||
@@ -52,7 +52,7 @@ public class PrototypeTargetSourceTests {
|
||||
* With the singleton, there will be change.
|
||||
*/
|
||||
@Test
|
||||
public void testPrototypeAndSingletonBehaveDifferently() {
|
||||
void testPrototypeAndSingletonBehaveDifferently() {
|
||||
SideEffectBean singleton = (SideEffectBean) beanFactory.getBean("singleton");
|
||||
assertThat(singleton.getCount()).isEqualTo(INITIAL_COUNT);
|
||||
singleton.doWork();
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -31,7 +31,7 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class ThreadLocalTargetSourceTests {
|
||||
class ThreadLocalTargetSourceTests {
|
||||
|
||||
/** Initial count value set in bean factory XML */
|
||||
private static final int INITIAL_COUNT = 10;
|
||||
@@ -60,7 +60,7 @@ public class ThreadLocalTargetSourceTests {
|
||||
* with one another.
|
||||
*/
|
||||
@Test
|
||||
public void testUseDifferentManagedInstancesInSameThread() {
|
||||
void testUseDifferentManagedInstancesInSameThread() {
|
||||
SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment");
|
||||
assertThat(apartment.getCount()).isEqualTo(INITIAL_COUNT);
|
||||
apartment.doWork();
|
||||
@@ -72,7 +72,7 @@ public class ThreadLocalTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReuseInSameThread() {
|
||||
void testReuseInSameThread() {
|
||||
SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment");
|
||||
assertThat(apartment.getCount()).isEqualTo(INITIAL_COUNT);
|
||||
apartment.doWork();
|
||||
@@ -86,7 +86,7 @@ public class ThreadLocalTargetSourceTests {
|
||||
* Relies on introduction.
|
||||
*/
|
||||
@Test
|
||||
public void testCanGetStatsViaMixin() {
|
||||
void testCanGetStatsViaMixin() {
|
||||
ThreadLocalTargetSourceStats stats = (ThreadLocalTargetSourceStats) beanFactory.getBean("apartment");
|
||||
// +1 because creating target for stats call counts
|
||||
assertThat(stats.getInvocationCount()).isEqualTo(1);
|
||||
@@ -104,7 +104,7 @@ public class ThreadLocalTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewThreadHasOwnInstance() throws InterruptedException {
|
||||
void testNewThreadHasOwnInstance() throws InterruptedException {
|
||||
SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment");
|
||||
assertThat(apartment.getCount()).isEqualTo(INITIAL_COUNT);
|
||||
apartment.doWork();
|
||||
@@ -144,7 +144,7 @@ public class ThreadLocalTargetSourceTests {
|
||||
* Test for SPR-1442. Destroyed target should re-associated with thread and not throw NPE.
|
||||
*/
|
||||
@Test
|
||||
public void testReuseDestroyedTarget() {
|
||||
void testReuseDestroyedTarget() {
|
||||
ThreadLocalTargetSource source = (ThreadLocalTargetSource)this.beanFactory.getBean("threadLocalTs");
|
||||
|
||||
// try first time
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -27,13 +27,13 @@ import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
|
||||
* @author Rob Harrop
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class RefreshableTargetSourceTests {
|
||||
class RefreshableTargetSourceTests {
|
||||
|
||||
/**
|
||||
* Test what happens when checking for refresh but not refreshing object.
|
||||
*/
|
||||
@Test
|
||||
public void testRefreshCheckWithNonRefresh() throws Exception {
|
||||
void testRefreshCheckWithNonRefresh() throws Exception {
|
||||
CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource();
|
||||
ts.setRefreshCheckDelay(0);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class RefreshableTargetSourceTests {
|
||||
* Test what happens when checking for refresh and refresh occurs.
|
||||
*/
|
||||
@Test
|
||||
public void testRefreshCheckWithRefresh() throws Exception {
|
||||
void testRefreshCheckWithRefresh() throws Exception {
|
||||
CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource(true);
|
||||
ts.setRefreshCheckDelay(0);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class RefreshableTargetSourceTests {
|
||||
* Test what happens when no refresh occurs.
|
||||
*/
|
||||
@Test
|
||||
public void testWithNoRefreshCheck() throws Exception {
|
||||
void testWithNoRefreshCheck() {
|
||||
CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource(true);
|
||||
ts.setRefreshCheckDelay(-1);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user