Compare commits

..

45 Commits

Author SHA1 Message Date
Spring Builds 2aeddb4fcb Release v5.3.25 2023-01-11 11:05:48 +00:00
Juergen Hoeller 26cd33cb2b Upgrade to Reactor 2020.0.27
Includes HtmlUnit 2.69, AssertJ 3.24.1, Checkstyle 10.6

Closes gh-29798
2023-01-11 00:23:38 +01:00
Juergen Hoeller 2ee393ae71 Upgrade to Tomcat 9.0.70, Jetty 9.4.50, Netty 4.1.86, Undertow 2.2.22, HtmlUnit 2.67, Mockito 4.9, AssertJ 3.23.1, Checkstyle 10.5 2022-12-23 15:52:09 +01:00
Juergen Hoeller 0815d29e45 Defensive check for null returned from createConnection()
Closes gh-29706
2022-12-23 15:51:37 +01:00
Brian Clozel 777f01d786 Fix path within mapping when pattern contains ".*"
Prior to this commit, extracting the path within handler mapping would
result in "" if the matching path element would be a Regex and contain
".*". This could cause issues with resource handling if the handler
mapping pattern was similar to `"/folder/file.*.extension"`.

This commit introduces a new `isLiteral()` method in the `PathElement`
abstract class that expresses whether the path element can be compared
as a String for path matching or if it requires a more elaborate
matching process.

Using this method for extracting the path within handler mapping avoids
relying on wildcard count or other properties.

See gh-29712
Fixes gh-29716
2022-12-19 10:53:02 +01:00
Sam Brannen f8fea013fc Update Jakarta Mail info in ref docs
Closes gh-29708
2022-12-17 14:18:42 +01:00
Sam Brannen 916539178b Improve documentation for literals in SpEL expressions
Closes gh-29701
2022-12-16 14:51:48 +01:00
Sam Brannen fdf3bcc9d9 Remove obsolete AttributeMethods.hasOnlyValueAttribute() method
See gh-29685
2022-12-13 15:52:01 +01:00
Sam Brannen 5ddc984192 Support repeatable annotation containers with multiple attributes
Prior to this commit, there was a bug in the implementation of
StandardRepeatableContainers.computeRepeatedAnnotationsMethod() which
has existed since Spring Framework 5.2 (when
StandardRepeatableContainers was introduced). Specifically,
StandardRepeatableContainers ignored any repeatable container
annotation if it declared attributes other than `value()`. However,
Java permits any number of attributes in a repeatable container
annotation.

In addition, the changes made in conjunction with gh-20279 made the bug
in StandardRepeatableContainers apparent when using the
getMergedRepeatableAnnotations() or findMergedRepeatableAnnotations()
method in AnnotatedElementUtils, resulting in regressions for the
behavior of those two methods.

This commit fixes the regressions and bug by altering the logic in
StandardRepeatableContainers.computeRepeatedAnnotationsMethod() so that
it explicitly looks for the `value()` method and ignores any other
methods declared in a repeatable container annotation candidate.

See gh-29685
Closes gh-29686
2022-12-13 15:47:13 +01:00
Sam Brannen b2ce54e7f1 Revise RepeatableContainersTests 2022-12-13 15:47:13 +01:00
Juergen Hoeller 937ab5f4b2 Polishing (aligned with main) 2022-12-13 12:06:09 +01:00
Juergen Hoeller 064c618050 Drop SQLExceptionSubclassFactory and unify SQLStateSQLExceptionTranslator tests 2022-12-13 12:05:35 +01:00
Juergen Hoeller 8c80ec1138 Avoid NPE on BeanDescriptor access with SimpleBeanInfoFactory
Closes gh-29681

(cherry picked from commit d74191427e)
2022-12-13 11:43:43 +01:00
rstoyanchev 912fa7602a Improve invalid Content-Type handling in WebFlux
Closes gh-29565
2022-12-09 11:53:16 +00:00
rstoyanchev 525fc7a27e Optimize object creation PartialMatchHelper
Closes gh-29667
2022-12-09 11:27:03 +00:00
Sam Brannen 72285034ba Support arrays in AST string representations of SpEL expressions
Prior to this commit, SpEL's ConstructorReference did not provide
support for arrays when generating a string representation of the
internal AST. For example, 'new String[3]' was represented as 'new
String()' instead of 'new String[3]'.

This commit introduces support for standard array construction and array
construction with initializers in ConstructorReference's toStringAST()
implementation.

Closes gh-29666
2022-12-08 23:23:01 -05:00
Sam Brannen 933474000b Polishing 2022-12-08 18:45:02 -05:00
Sam Brannen 4e8aebcde7 Fix SpEL support for quotes within String literals
Prior to this commit, there were two bugs in the support for quotes
within String literals in SpEL expressions.

- Two double quotes ("") or two single quotes ('') were always replaced
  with one double quote or one single quote, respectively, regardless
  of which quote character was used to enclose the original String
  literal. This resulted in the loss of one of the double quotes when
  the String literal was enclosed in single quotes, and vice versa. For
  example, 'x "" y' became 'x " y'.

- A single quote which was properly escaped in a String literal
  enclosed within single quotes was not escaped in the AST string
  representation of the expression. For example, 'x '' y' became 'x ' y'.

This commit fixes both of these related issues in StringLiteral and
overhauls the structure of ParsingTests.

Closes gh-29604
Closes gh-28356
2022-12-07 16:03:49 -05:00
Sam Brannen dff5b1ff8e Polishing 2022-12-07 15:59:08 -05:00
Sam Brannen fa0a4a0a46 Improve Javadoc for SqlLobValue 2022-12-07 15:58:56 -05:00
Sam Brannen 41a6b7ec20 Update copyright headers for source code changed since August 2022
The changes in this commit were performed using the newly introduced
update_copyright_headers.sh script.
2022-12-03 17:23:21 -05:00
Sam Brannen cfaa4ba89c Apply update_copyright_headers.sh to staged files as well 2022-12-03 17:23:21 -05:00
Sam Brannen 598e9d586b Introduce update_copyright_headers.sh shell script
In order to automate maintenance of copyright headers in our source
code (especially when merging PRs from external contributors), this
commit introduces an update_copyright_headers.sh script (tested on
mac OS) that will update the copyright headers of all Java, Kotlin,
and Groovy source files that have been added or modified this year
(or at least as far back as the git log history supports it).

For example, running this script currently outputs the following.

Updating copyright headers in Java, Kotlin, and Groovy source code for year 2022
warning: log for 'main' only goes back to Tue, 16 Aug 2022 16:24:55 +0200
2022-12-03 17:23:21 -05:00
Sam Brannen 750a8b359f Stop using Mockito to spy() on JDK I/O streams
When running on JDK 16+, we are not able to spy() on JDK types. To
address this, this commit stops using Mockito to spy on JDK I/O streams
(such as ByteArrayInputStream and ByteArrayOutputStream).
2022-12-03 17:23:21 -05:00
Sam Brannen c838bcff3a Fix/Disable JMX & JNDI tests on JDK 16+ 2022-12-03 17:23:21 -05:00
Sam Brannen 109b00d24d Avoid use of deprecated Character/Double constructors in tests 2022-12-03 17:23:21 -05:00
Sam Brannen b6abf45a56 Introduce @Suite classes for individual modules 2022-12-03 16:32:27 -05:00
Juergen Hoeller 2d94b43faa Polishing 2022-12-02 11:15:03 +01:00
Sam Brannen f7f73439ab Modify MBeanTestUtils to work on JDK 17+ 2022-11-30 14:55:15 +01:00
Sam Brannen 5e55558a93 Fix SockJsServiceTests
See gh-29594
2022-11-28 17:12:08 +01:00
Sam Brannen eee50d3b8a Polishing 2022-11-28 17:06:22 +01:00
Sam Brannen 92af390ed2 Polish contribution
See gh-29594
2022-11-28 17:02:53 +01:00
Aashay Chapatwala 332d2a36bb Add title to SockJS iFrames for accessibility compliance
Closes gh-29594
2022-11-28 17:02:01 +01:00
Brian Clozel 92b2b828f5 Fix Java 17 test CI and remove Java 11 variant
This commit fixes configuration and runtime issues with the Java 17 test
CI variant and removes the Java 11 one, now that Spring Framework 5.3.x
is in maintenance mode.
2022-11-28 10:17:56 +01:00
Juergen Hoeller a11222f681 Polishing 2022-11-25 17:58:59 +01:00
Juergen Hoeller 7de8d81932 Explicit documentation notes on standard Java reflection
See gh-29531
2022-11-24 14:25:43 +01:00
Juergen Hoeller 0e33537d9d Consistent use of DefaultParameterNameResolver in tests 2022-11-23 11:17:58 +01:00
Sébastien Deleuze 00da70e26b Fix some typos in Kotlin WebClient example code
Closes gh-29542
2022-11-22 08:35:50 +01:00
Sam Brannen 72b44cebea Catch Error for SourceHttpMessageConverter in WebMvcConfigurationSupport
Prior to this commit, the addDefaultHttpMessageConverters() method in
WebMvcConfigurationSupport caught Throwable for SourceHttpMessageConverter
instantiation; whereas, the rest of the code base correctly catches Error
for SourceHttpMessageConverter instantiation (to handle errors such as
NoClassDefFoundError).

Throwable should not be caught since it can mask other categories of
failures (such as configuration errors).

This commit therefore switches to catching Error for SourceHttpMessageConverter
instantiation in WebMvcConfigurationSupport.

Closes gh-29537
2022-11-21 17:37:22 +01:00
Marten Deinum 1ee3777ac7 Fix link to Bean Utils Light Library in BeanUtils Javadoc
The URL for the BULL library has changed (not sure when, probably way back).

This updates it to the correct location.

Closes gh-29534, gh-29536
2022-11-21 17:25:50 +01:00
divcon cad6e8756d Fix link to WebFlux section in reference manual
Closes gh-29526
2022-11-19 14:51:03 +01:00
Sam Brannen c7bd685c54 Polishing 2022-11-19 14:49:26 +01:00
Sam Brannen 4f472d2ac4 Remove TODOs in WebFlux ref docs 2022-11-19 14:49:07 +01:00
Stephane Nicoll a18842b72b Fix link to WebFlux section
Closes gh-29517
2022-11-18 10:48:05 +01:00
Spring Builds a41f97bc2b Next development version (v5.3.25-SNAPSHOT) 2022-11-16 08:07:01 +00:00
3882 changed files with 85572 additions and 121780 deletions
-29
View File
@@ -1,29 +0,0 @@
name: Backport Bot
on:
issues:
types: [labeled]
push:
branches:
- '*.x'
permissions:
contents: read
jobs:
build:
permissions:
contents: read
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
+13 -17
View File
@@ -1,42 +1,37 @@
# Miscellaneous
*.java.hsp
*.sonarj
*.sw*
.DS_Store
.settings
.springBeans
bin
build.sh
integration-repo
ivy-cache
argfile*
activemq-data/
classes/
# Log files
jxl.log
jmx.log
derby.log
# Gradle artifacts
spring-test/test-output/
.gradle
.gradletasknamecache
argfile*
pom.xml
activemq-data/
classes/
/build
buildSrc/build
/spring-*/build
/spring-core/graalvm/build
/spring-core/kotlin-coroutines/build
/framework-bom/build
/framework-docs/build
/integration-tests/build
/src/asciidoc/build
spring-test/test-output/
# Maven artifacts
pom.xml
target/
# Eclipse artifacts, including WTP generated manifests
bin
.classpath
.project
.settings
.springBeans
spring-*/src/main/java/META-INF/MANIFEST.MF
# IDEA artifacts and output dirs
@@ -47,6 +42,7 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
out
test-output
atlassian-ide-plugin.xml
.gradletasknamecache
# VS Code
.vscode/
.vscode/
+4 -5
View File
@@ -10,11 +10,10 @@ Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
Chris Beams <cbeams@vmware.com>
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
Arjen Poutsma <apoutsma@vmware.com>
Arjen Poutsma <apoutsma@vmware.com> <apoutsma@pivotal.io>
Arjen Poutsma <apoutsma@vmware.com> <apoutsma@gopivotal.com>
Arjen Poutsma <apoutsma@vmware.com> <poutsma@mac.com>
Oliver Drotbohm <odrotbohm@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
+1 -1
View File
@@ -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.5-librca
java=8.0.352-librca
+12 -16
View File
@@ -6,7 +6,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
* [Code of Conduct](#code-of-conduct)
* [How to Contribute](#how-to-contribute)
* [Ask questions](#ask-questions)
* [Discuss](#discuss)
* [Create an Issue](#create-an-issue)
* [Issue Lifecycle](#issue-lifecycle)
* [Submit a Pull Request](#submit-a-pull-request)
@@ -22,10 +22,11 @@ Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
### How to Contribute
#### Ask questions
#### Discuss
If you have a question, check Stack Overflow using
[this list of tags](https://stackoverflow.com/questions/tagged/spring+or+spring-mvc+or+spring-aop+or+spring-jdbc+or+spring-transactions+or+spring-annotations+or+spring-jms+or+spring-el+or+spring-test+or+spring+or+spring-remoting+or+spring-orm+or+spring-jmx+or+spring-cache+or+spring-webflux?tab=Newest). Find an existing discussion, or start a new one if necessary.
[this list of tags](https://stackoverflow.com/questions/tagged/spring+or+spring-mvc+or+spring-aop+or+spring-jdbc+or+spring-transactions+or+spring-annotations+or+spring-jms+or+spring-el+or+spring-test+or+spring+or+spring-remoting+or+spring-orm+or+spring-jmx+or+spring-cache+or+spring-webflux?tab=Newest).
Find an existing discussion, or start a new one if necessary.
If you believe there is an issue, search through
[existing issues](https://github.com/spring-projects/spring-framework/issues) trying a
@@ -38,18 +39,14 @@ decision.
Reporting an issue or making a feature request is a great way to contribute. Your feedback
and the conversations that result from it provide a continuous flow of ideas. However,
before creating a ticket, please take the time to [ask and research](#ask-questions) first.
before creating a ticket, please take the time to [discuss and research](#discuss) first.
If you create an issue after a discussion on Stack Overflow, please provide a description
If creating an issue after a discussion on Stack Overflow, please provide a description
in the issue instead of simply referring to Stack Overflow. The issue tracker is an
important place of record for design discussions and should be self-sufficient.
Once you're ready, create an issue on [GitHub](https://github.com/spring-projects/spring-framework/issues).
Many issues are caused by subtle behavior, typos, and unintended configuration.
Creating a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example)
(starting with https://start.spring.io for example) of the problem helps the team
quickly triage your issue and get to the core of the problem.
Once you're ready, create an issue on
[GitHub](https://github.com/spring-projects/spring-framework/issues).
#### Issue Lifecycle
@@ -66,7 +63,7 @@ follow-up reports will need to be created as new issues with a fresh description
#### Submit a Pull Request
1. If you have not previously done so, please sign the
[Contributor License Agreement](https://cla.spring.io/sign/spring). You will be reminded
[Contributor License Agreement](https://cla.pivotal.io/sign/spring). You will be reminded
automatically when you submit the PR.
1. Should you create an issue first? No, just create the pull request and use the
@@ -123,13 +120,12 @@ define the source file coding standards we use along with some IDEA editor setti
### Reference Docs
The reference documentation is in the [framework-docs/src/docs/asciidoc](framework-docs/src/docs/asciidoc) directory, in
The reference documentation is in the [src/docs/asciidoc](src/docs/asciidoc) directory, in
[Asciidoctor](https://asciidoctor.org/) format. For trivial changes, you may be able to browse,
edit source files, and submit directly from GitHub.
When making changes locally, execute `./gradlew :framework-docs:asciidoctor` and then browse the result under
`framework-docs/build/docs/ref-docs/html5/index.html`.
When making changes locally, execute `./gradlew asciidoctor` and then browse the result under
`build/docs/ref-docs/html5/index.html`.
Asciidoctor also supports live editing. For more details see
[AsciiDoc Tooling](https://docs.asciidoctor.org/asciidoctor/latest/tooling/).
+2 -2
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](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".
@@ -14,7 +14,7 @@ For access to artifacts or a distribution zip, see the [Spring Framework Artifac
## Documentation
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and [source](framework-docs/src/docs/asciidoc)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and [source](src/docs/asciidoc)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
[API reference](https://docs.spring.io/spring-framework/docs/current/javadoc-api/). There are also [guides and tutorials](https://spring.io/guides) across Spring projects.
## Micro-Benchmarks
+323 -57
View File
@@ -1,39 +1,297 @@
plugins {
id 'io.spring.dependency-management' version '1.0.11.RELEASE' apply false
id 'io.spring.nohttp' version '0.0.10'
id 'io.freefair.aspectj' version '6.5.0.3' apply false
// kotlinVersion is managed in gradle.properties
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka' version '1.7.20'
id 'org.asciidoctor.jvm.convert' version '3.3.2' apply false
id 'org.asciidoctor.jvm.pdf' version '3.3.2' apply false
id 'io.freefair.aspectj' version '6.2.0' apply false
id 'org.jetbrains.dokka' version '1.6.10' apply false
id 'org.jetbrains.kotlin.jvm' version '1.5.32' apply false
id 'org.jetbrains.kotlin.plugin.serialization' version "1.5.32" apply false
id 'org.asciidoctor.jvm.convert' version '3.3.2'
id 'org.asciidoctor.jvm.pdf' version '3.3.2'
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id 'de.undercouch.download' version '5.1.0'
id 'me.champeau.jmh' version '0.6.6' apply false
id 'com.github.ben-manes.versions' version '0.39.0'
id 'com.github.johnrengelman.shadow' version '7.0.0' apply false
id 'de.undercouch.download' version '4.1.2'
id 'me.champeau.jmh' version "0.6.6" apply false
}
ext {
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
javaProjects = subprojects - project(":framework-bom") - project(":framework-platform")
javaProjects = subprojects - project(":framework-bom")
withoutJclOverSlf4j = {
exclude group: "org.slf4j", name: "jcl-over-slf4j"
}
}
configure(allprojects) { project ->
repositories {
mavenCentral()
maven {
url "https://repo.spring.io/milestone"
content {
// Netty 5 optional support
includeGroup 'io.projectreactor.netty'
apply plugin: "io.spring.dependency-management"
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
mavenBom "io.netty:netty-bom:4.1.86.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.27"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.50.v20221201"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
mavenBom "org.junit:junit-bom:5.8.2"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.19.0') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
entry 'log4j-slf4j-impl'
}
dependency "org.slf4j:slf4j-api:1.7.36"
dependency("com.google.code.findbugs:findbugs:3.0.1") {
exclude group: "dom4j", name: "dom4j"
}
dependency "com.google.code.findbugs:jsr305:3.0.2"
dependencySet(group: 'org.aspectj', version: '1.9.7') {
entry 'aspectjrt'
entry 'aspectjtools'
entry 'aspectjweaver'
}
dependencySet(group: 'org.codehaus.groovy', version: '3.0.9') {
entry 'groovy'
entry 'groovy-jsr223'
entry 'groovy-templates' // requires findbugs for warning-free compilation
entry 'groovy-test'
entry 'groovy-xml'
}
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.1.5"
dependency "io.smallrye.reactive:mutiny:1.8.0"
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.3.1"
dependency("com.fasterxml.woodstox:woodstox-core:6.2.8") {
exclude group: "stax", name: "stax-api"
}
dependency "com.google.code.gson:gson:2.8.9"
dependency "com.google.protobuf:protobuf-java-util:3.19.3"
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
dependency("com.thoughtworks.xstream:xstream:1.4.19") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
dependency "org.apache.johnzon:johnzon-jsonb:1.2.18"
dependency("org.codehaus.jettison:jettison:1.3.8") {
exclude group: "stax", name: "stax-api"
}
dependencySet(group: 'org.jibx', version: '1.3.3') {
entry 'jibx-bind'
entry 'jibx-run'
}
dependency "org.ogce:xpp3:1.1.6"
dependency "org.yaml:snakeyaml:1.30"
dependency "com.h2database:h2:2.1.214"
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
dependency "com.github.librepdf:openpdf:1.3.30"
dependency "com.rometools:rome:1.18.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.4"
dependency "net.sf.jopt-simple:jopt-simple:5.0.4"
dependencySet(group: 'org.apache.activemq', version: '5.16.2') {
entry 'activemq-broker'
entry('activemq-kahadb-store') {
exclude group: "org.springframework", name: "spring-context"
}
entry 'activemq-stomp'
}
dependency "org.apache.bcel:bcel:6.0"
dependency "org.apache.commons:commons-pool2:2.9.0"
dependencySet(group: 'org.apache.derby', version: '10.14.2.0') {
entry 'derby'
entry 'derbyclient'
}
dependency "org.apache.poi:poi-ooxml:4.1.2"
dependency "org.apache-extras.beanshell:bsh:2.0b6"
dependency "org.freemarker:freemarker:2.3.31"
dependency "org.hsqldb:hsqldb:2.5.2"
dependency "org.quartz-scheduler:quartz:2.3.2"
dependency "org.codehaus.fabric3.api:commonj:1.1.0"
dependency "net.sf.ehcache:ehcache:2.10.6"
dependency "org.ehcache:jcache:1.0.1"
dependency "org.ehcache:ehcache:3.4.0"
dependency "org.hibernate:hibernate-core:5.4.33.Final"
dependency "org.hibernate:hibernate-validator:6.2.5.Final"
dependency "org.webjars:webjars-locator-core:0.48"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.70') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.70') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.22.Final') {
entry 'undertow-core'
entry('undertow-servlet') {
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
exclude group: "org.jboss.spec.javax.annotation", name: "jboss-annotations-api_1.3_spec"
}
entry('undertow-websockets-jsr') {
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
}
}
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.13"
dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3'
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.apache.httpcomponents:httpasyncclient:4.1.5") {
exclude group: "commons-logging", name: "commons-logging"
}
dependencySet(group: 'com.squareup.okhttp3', version: '3.14.9') {
entry 'okhttp'
entry 'mockwebserver'
}
dependency "org.jruby:jruby:9.2.20.1"
dependency "org.python:jython-standalone:2.7.1"
dependency "org.mozilla:rhino:1.7.11"
dependency "commons-fileupload:commons-fileupload:1.4"
dependency "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
dependency("org.dom4j:dom4j:2.1.3") {
exclude group: "jaxen", name: "jaxen"
exclude group: "net.java.dev.msv", name: "xsdlib"
exclude group: "pull-parser", name: "pull-parser"
exclude group: "xpp3", name: "xpp3"
}
dependency("jaxen:jaxen:1.2.0") {
exclude group: "dom4j", name: "dom4j"
}
dependency("junit:junit:4.13.2") {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
dependency("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1") {
exclude group: "junit", name: "junit"
}
dependency "org.testng:testng:7.4.0"
dependency "org.junit.support:testng-engine:1.0.4"
dependency "org.hamcrest:hamcrest:2.2"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.24.1"
dependencySet(group: 'org.xmlunit', version: '2.9.0') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '4.9.0') { // spring-beans tests fail with 4.10+
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
entry 'mockito-junit-jupiter'
}
dependency "io.mockk:mockk:1.12.1"
dependency("net.sourceforge.htmlunit:htmlunit:2.69.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.67.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
exclude group: "commons-logging", name: "commons-logging"
exclude group: "io.netty", name: "netty"
}
dependency "org.skyscreamer:jsonassert:1.5.0"
dependency "com.jayway.jsonpath:json-path:2.7.0"
dependency "org.bouncycastle:bcpkix-jdk18on:1.71"
dependencySet(group: 'org.apache.tiles', version: '3.0.8') {
entry 'tiles-api'
entry('tiles-core', withoutJclOverSlf4j)
entry('tiles-servlet', withoutJclOverSlf4j)
entry('tiles-jsp', withoutJclOverSlf4j)
entry('tiles-el', withoutJclOverSlf4j)
entry('tiles-extras') {
exclude group: "org.springframework", name: "spring-web"
exclude group: "org.slf4j", name: "jcl-over-slf4j"
}
}
dependency("org.apache.taglibs:taglibs-standard-jstlel:1.2.5") {
exclude group: "org.apache.taglibs", name: "taglibs-standard-spec"
}
dependency "com.ibm.websphere:uow:6.0.2.17"
dependency "com.jamonapi:jamon:2.82"
dependency "joda-time:joda-time:2.10.13"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10"
dependency "org.javamoney:moneta:1.3"
dependency "com.sun.activation:javax.activation:1.2.0"
dependency "com.sun.mail:javax.mail:1.6.2"
dependencySet(group: 'com.sun.xml.bind', version: '2.3.0.1') {
entry 'jaxb-core'
entry 'jaxb-impl'
entry 'jaxb-xjc'
}
dependency "javax.activation:javax.activation-api:1.2.0"
dependency "javax.annotation:javax.annotation-api:1.3.2"
dependency "javax.cache:cache-api:1.1.0"
dependency "javax.ejb:javax.ejb-api:3.2"
dependency "javax.el:javax.el-api:3.0.1-b04"
dependency "javax.enterprise.concurrent:javax.enterprise.concurrent-api:1.0"
dependency "javax.faces:javax.faces-api:2.2"
dependency "javax.inject:javax.inject:1"
dependency "javax.inject:javax.inject-tck:1"
dependency "javax.interceptor:javax.interceptor-api:1.2.2"
dependency "javax.jms:javax.jms-api:2.0.1"
dependency "javax.json:javax.json-api:1.1.4"
dependency "javax.json.bind:javax.json.bind-api:1.0"
dependency "javax.mail:javax.mail-api:1.6.2"
dependency "javax.money:money-api:1.0.3"
dependency "javax.resource:javax.resource-api:1.7.1"
dependency "javax.servlet:javax.servlet-api:4.0.1"
dependency "javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02"
dependency "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1"
dependency "javax.transaction:javax.transaction-api:1.3"
dependency "javax.validation:validation-api:2.0.1.Final"
dependency "javax.websocket:javax.websocket-api:1.1"
dependency "javax.xml.bind:jaxb-api:2.3.1"
dependency "javax.xml.ws:jaxws-api:2.3.1"
dependency "org.eclipse.persistence:javax.persistence:2.2.0"
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
// is not available on Maven Central
dependency "org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea"
dependency "org.glassfish:javax.el:3.0.1-b08"
dependency "org.glassfish.main:javax.jws:4.0-b33"
dependency "org.glassfish.tyrus:tyrus-container-servlet:1.13.1"
}
maven { url "https://repo.spring.io/libs-spring-framework-build" }
if (version.contains('-')) {
maven { url "https://repo.spring.io/milestone" }
generatedPomCustomization {
enabled = false
}
if (version.endsWith('-SNAPSHOT')) {
maven { url "https://repo.spring.io/snapshot" }
resolutionStrategy {
cacheChangingModulesFor 0, "seconds"
}
repositories {
mavenCentral()
maven { url "https://repo.spring.io/libs-spring-framework-build" }
}
}
configurations.all {
@@ -50,17 +308,27 @@ configure([rootProject] + javaProjects) { project ->
apply plugin: "java"
apply plugin: "java-test-fixtures"
apply plugin: "checkstyle"
apply plugin: 'org.springframework.build.conventions'
apply plugin: 'org.springframework.build.compile'
apply from: "${rootDir}/gradle/toolchains.gradle"
apply from: "${rootDir}/gradle/ide.gradle"
configurations {
dependencyManagement {
canBeConsumed = false
canBeResolved = false
visible = false
pluginManager.withPlugin("kotlin") {
apply plugin: "org.jetbrains.dokka"
apply from: "${rootDir}/gradle/docs-dokka.gradle"
compileKotlin {
kotlinOptions {
languageVersion = "1.3"
apiVersion = "1.3"
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings", "-Xopt-in=kotlin.RequiresOptIn"]
allWarningsAsErrors = true
}
}
compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
}
}
matching { it.name.endsWith("Classpath") }.all { it.extendsFrom(dependencyManagement) }
}
test {
@@ -69,29 +337,14 @@ configure([rootProject] + javaProjects) { project ->
systemProperty("java.awt.headless", "true")
systemProperty("testGroups", project.properties.get("testGroups"))
systemProperty("io.netty.leakDetection.level", "paranoid")
systemProperty("io.netty5.leakDetectionLevel", "paranoid")
systemProperty("io.netty5.leakDetection.targetRecords", "32")
systemProperty("io.netty5.buffer.lifecycleTracingEnabled", "true")
systemProperty("io.netty5.buffer.leakDetectionEnabled", "true")
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED"])
}
checkstyle {
toolVersion = "10.4"
toolVersion = "10.6.0"
configDirectory.set(rootProject.file("src/checkstyle"))
}
tasks.named("checkstyleMain").configure {
maxHeapSize = "1g"
}
tasks.named("checkstyleTest").configure {
maxHeapSize = "1g"
}
dependencies {
dependencyManagement(enforcedPlatform(dependencies.project(path: ":framework-platform")))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.junit.platform:junit-platform-suite-api")
@@ -113,29 +366,29 @@ configure([rootProject] + javaProjects) { project ->
}
ext.javadocLinks = [
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.oracle.com/javase/8/docs/api/",
"https://docs.oracle.com/javaee/7/api/",
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/",
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/",
"https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/",
"https://tiles.apache.org/tiles-request/apidocs/",
"https://tiles.apache.org/framework/apidocs/",
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
"https://www.ehcache.org/apidocs/2.10.4/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.10/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.10/",
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
"https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/",
"https://projectreactor.io/docs/test/release/api/",
"https://junit.org/junit4/javadoc/4.13.2/",
// TODO Uncomment link to JUnit 5 docs once we have sorted out
// the following warning in the build.
//
// warning: The code being documented uses packages in the unnamed module, but the packages defined in https://junit.org/junit5/docs/5.9.1/api/ are in named modules.
//
// "https://junit.org/junit5/docs/5.9.1/api/",
// Disabling linking to JUnit 5.8.2, since the `package-list` file no longer exists due to
// https://github.com/junit-team/junit5/commit/67ad4e545518b0ce2b0e7c96df31a669866d5003.
// "https://junit.org/junit5/docs/5.8.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/",
"https://r2dbc.io/spec/0.8.5.RELEASE/api/",
// The external Javadoc link for JSR 305 must come last to ensure that types from
// JSR 250 (such as @PostConstruct) are still supported. This is due to the fact
// that JSR 250 and JSR 305 both define types in javax.annotation, which results
@@ -152,14 +405,17 @@ configure(moduleProjects) { project ->
configure(rootProject) {
description = "Spring Framework"
apply plugin: "kotlin"
apply plugin: "io.spring.nohttp"
apply plugin: 'org.springframework.build.api-diff'
apply from: "${rootDir}/gradle/publications.gradle"
apply from: "${rootDir}/gradle/docs.gradle"
nohttp {
source.exclude "**/test-output/**"
allowlistFile = project.file("src/nohttp/allowlist.lines")
def rootPath = file(rootDir).toPath()
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/spring-core-test" + "${rootDir}/buildSrc" + "${rootDir}/framework-docs"
projectDirs.forEach { dir ->
[ 'bin', 'build', 'out', '.settings' ]
.collect { rootPath.relativize(new File(dir, it).toPath()) }
@@ -174,4 +430,14 @@ configure(rootProject) {
maxHeapSize = "1g"
}
publishing {
publications {
mavenJava(MavenPublication) {
artifact docsZip
artifact schemaZip
artifact distZip
}
}
}
}
+2 -42
View File
@@ -5,12 +5,9 @@ They are declared in the `build.gradle` file in this folder.
## Build Conventions
The `org.springframework.build.conventions` plugin applies all conventions to the Framework build:
* Configuring the Java compiler, see `JavaConventions`
* Configuring the Kotlin compiler, see `KotlinConventions`
* Configuring testing in the build with `TestConventions`
### Compiler conventions
The `org.springframework.build.compile` plugin applies the Java compiler conventions to the build.
## Build Plugins
@@ -36,40 +33,3 @@ current working version with. You can generate the reports for all modules or a
```
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
### RuntimeHints Java Agent
The `spring-core-test` project module contributes the `RuntimeHintsAgent` Java agent.
The `RuntimeHintsAgentPlugin` Gradle plugin creates a dedicated `"runtimeHintsTest"` test task for each project.
This task will detect and execute [tests tagged](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-gradle)
with the `"RuntimeHintsTests"` [JUnit tag](https://junit.org/junit5/docs/current/user-guide/#running-tests-tags).
In the Spring Framework test suite, those are usually annotated with the `@EnabledIfRuntimeHintsAgent` annotation.
By default, the agent will instrument all classes located in the `"org.springframework"` package, as they are loaded.
The `RuntimeHintsAgentExtension` allows to customize this using a DSL:
```groovy
// this applies the `RuntimeHintsAgentPlugin` to the project
plugins {
id 'org.springframework.build.runtimehints-agent'
}
// You can configure the agent to include and exclude packages from the instrumentation process.
runtimeHintsAgent {
includedPackages = ["org.springframework", "io.spring"]
excludedPackages = ["org.example"]
}
dependencies {
// to use the test infrastructure, the project should also depend on the "spring-core-test" module
testImplementation(project(":spring-core-test"))
}
```
With this configuration, `./gradlew runtimeHintsTest` will run all tests instrumented by this java agent.
The global `./gradlew check` task depends on `runtimeHintsTest`.
NOTE: the "spring-core-test" module doesn't shade "spring-core" by design, so the agent should never instrument
code that doesn't have "spring-core" on its classpath.
+3 -19
View File
@@ -7,20 +7,8 @@ repositories {
gradlePluginPortal()
}
ext {
def propertiesFile = new File(new File("$projectDir").parentFile, "gradle.properties")
propertiesFile.withInputStream {
def properties = new Properties()
properties.load(it)
set("kotlinVersion", properties["kotlinVersion"])
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
}
gradlePlugin {
@@ -29,17 +17,13 @@ gradlePlugin {
id = "org.springframework.build.api-diff"
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
}
conventionsPlugin {
id = "org.springframework.build.conventions"
implementationClass = "org.springframework.build.ConventionsPlugin"
compileConventionsPlugin {
id = "org.springframework.build.compile"
implementationClass = "org.springframework.build.compile.CompilerConventionsPlugin"
}
optionalDependenciesPlugin {
id = "org.springframework.build.optional-dependencies"
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
}
runtimeHintsAgentPlugin {
id = "org.springframework.build.runtimehints-agent"
implementationClass = "org.springframework.build.hint.RuntimeHintsAgentPlugin"
}
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
/**
* Plugin to apply conventions to projects that are part of Spring Framework's build.
* Conventions are applied in response to various plugins being applied.
*
* When the {@link JavaBasePlugin} is applied, the conventions in {@link TestConventions}
* are applied.
* When the {@link JavaBasePlugin} is applied, the conventions in {@link JavaConventions}
* are applied.
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
* are applied.
*
* @author Brian Clozel
*/
public class ConventionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
new JavaConventions().apply(project);
new KotlinConventions().apply(project);
new TestConventions().apply(project);
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.Project;
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
/**
* @author Brian Clozel
*/
public class KotlinConventions {
void apply(Project project) {
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
}
private void configure(KotlinCompile compile) {
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
kotlinOptions.setApiVersion("1.7");
kotlinOptions.setLanguageVersion("1.7");
kotlinOptions.setJvmTarget("17");
kotlinOptions.setAllWarningsAsErrors(true);
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
}
}
@@ -1,56 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.testing.Test;
import org.gradle.testretry.TestRetryPlugin;
import org.gradle.testretry.TestRetryTaskExtension;
/**
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
* plugin is applied:
* <ul>
* <li>The {@link TestRetryPlugin Test Retry} plugins is applied so that flaky tests
* are retried 3 times when running on the CI.
* </ul>
*
* @author Brian Clozel
* @author Andy Wilkinson
*/
class TestConventions {
void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, (java) -> configureTestConventions(project));
}
private void configureTestConventions(Project project) {
project.getPlugins().apply(TestRetryPlugin.class);
project.getTasks().withType(Test.class,
(test) -> project.getPlugins().withType(TestRetryPlugin.class, (testRetryPlugin) -> {
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
testRetry.getFailOnPassedAfterRetry().set(true);
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
}));
}
private boolean isCi() {
return Boolean.parseBoolean(System.getenv("CI"));
}
}
@@ -16,7 +16,6 @@
package org.springframework.build.api;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@@ -24,7 +23,6 @@ import java.util.List;
import me.champeau.gradle.japicmp.JapicmpPlugin;
import me.champeau.gradle.japicmp.JapicmpTask;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@@ -34,12 +32,10 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
* and create tasks for all subprojects, diffing the public API one by one
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
* reports for the API diff between the baseline version and the current one for all modules.
@@ -50,16 +46,12 @@ import org.slf4j.LoggerFactory;
*/
public class ApiDiffPlugin implements Plugin<Project> {
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
public static final String TASK_NAME = "apiDiff";
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
@Override
public void apply(Project project) {
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
@@ -71,24 +63,16 @@ public class ApiDiffPlugin implements Plugin<Project> {
private void applyApiDiffConventions(Project project) {
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
project.subprojects(subProject -> {
if (subProject.getName().startsWith("spring-")) {
createApiDiffTask(baselineVersion, subProject);
}
});
project.subprojects(subProject -> createApiDiffTask(baselineVersion, subProject));
}
private void createApiDiffTask(String baselineVersion, Project project) {
if (isProjectEligible(project)) {
// Add Spring Milestone repository for generating diffs against previous milestones
project.getRootProject()
.getRepositories()
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
apiDiff.setDescription("Generates an API diff report with japicmp");
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
apiDiff.setOldClasspath(project.files(createBaselineConfiguration(baselineVersion, project)));
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
apiDiff.setNewClasspath(getRuntimeClassPath(project));
@@ -114,16 +98,7 @@ public class ApiDiffPlugin implements Plugin<Project> {
String baseline = String.join(":",
project.getGroup().toString(), project.getName(), baselineVersion);
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
try {
// eagerly resolve the baseline configuration to check whether this is a new Spring module
baselineConfiguration.resolve();
return baselineConfiguration;
}
catch (GradleException exception) {
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
}
return project.getRootProject().getConfigurations().detachedConfiguration();
return project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
}
private Configuration getRuntimeClassPath(Project project) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.build;
package org.springframework.build.compile;
import java.util.ArrayList;
import java.util.Arrays;
@@ -22,8 +22,9 @@ import java.util.List;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.compile.JavaCompile;
/**
@@ -33,7 +34,7 @@ import org.gradle.api.tasks.compile.JavaCompile;
* @author Sam Brannen
* @author Sebastien Deleuze
*/
public class JavaConventions {
public class CompilerConventionsPlugin implements Plugin<Project> {
private static final List<String> COMPILER_ARGS;
@@ -58,8 +59,9 @@ public class JavaConventions {
"-Xlint:-deprecation", "-Xlint:-unchecked"));
}
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, javaPlugin -> applyJavaCompileConventions(project));
project.getPlugins().withType(JavaLibraryPlugin.class, javaPlugin -> applyJavaCompileConventions(project));
}
/**
@@ -68,6 +70,7 @@ public class JavaConventions {
* @param project the current project
*/
private void applyJavaCompileConventions(Project project) {
JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class);
project.getTasks().withType(JavaCompile.class)
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
.forEach(compileTask -> {
@@ -1,53 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.hint;
import java.util.Collections;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.SetProperty;
/**
* Entry point to the DSL extension for the {@link RuntimeHintsAgentPlugin} Gradle plugin.
* @author Brian Clozel
*/
public class RuntimeHintsAgentExtension {
private final SetProperty<String> includedPackages;
private final SetProperty<String> excludedPackages;
public RuntimeHintsAgentExtension(ObjectFactory objectFactory) {
this.includedPackages = objectFactory.setProperty(String.class).convention(Collections.singleton("org.springframework"));
this.excludedPackages = objectFactory.setProperty(String.class).convention(Collections.emptySet());
}
public SetProperty<String> getIncludedPackages() {
return this.includedPackages;
}
public SetProperty<String> getExcludedPackages() {
return this.excludedPackages;
}
String asJavaAgentArgument() {
StringBuilder builder = new StringBuilder();
this.includedPackages.get().forEach(packageName -> builder.append('+').append(packageName).append(','));
this.excludedPackages.get().forEach(packageName -> builder.append('-').append(packageName).append(','));
return builder.toString();
}
}
@@ -1,56 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.hint;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.testing.Test;
/**
* {@link Plugin} that configures the {@code RuntimeHints} Java agent to test tasks.
*
* @author Brian Clozel
*/
public class RuntimeHintsAgentPlugin implements Plugin<Project> {
public static final String RUNTIMEHINTS_TEST_TASK = "runtimeHintsTest";
private static final String EXTENSION_NAME = "runtimeHintsAgent";
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
RuntimeHintsAgentExtension agentExtension = project.getExtensions().create(EXTENSION_NAME,
RuntimeHintsAgentExtension.class, project.getObjects());
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
test.useJUnitPlatform(options -> {
options.includeTags("RuntimeHintsTests");
});
test.include("**/*Tests.class", "**/*Test.class");
test.systemProperty("java.awt.headless", "true");
});
project.afterEvaluate(p -> {
Jar jar = project.getRootProject().project("spring-core-test").getTasks().withType(Jar.class).named("jar").get();
agentTest.jvmArgs("-javaagent:" + jar.getArchiveFile().get().getAsFile() + "=" + agentExtension.asJavaAgentArgument());
});
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
});
}
}
@@ -19,9 +19,12 @@ package org.springframework.build.optional;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.attributes.Usage;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
/**
* A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new
@@ -44,7 +47,7 @@ public class OptionalDependenciesPlugin implements Plugin<Project> {
optional.setCanBeConsumed(false);
optional.setCanBeResolved(false);
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class)
SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class)
.getSourceSets();
sourceSets.all((sourceSet) -> {
project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(optional);
@@ -1,175 +0,0 @@
package org.springframework.build.shadow;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.component.ModuleComponentSelector;
import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
import org.gradle.api.artifacts.result.ArtifactResolutionResult;
import org.gradle.api.artifacts.result.ComponentArtifactsResult;
import org.gradle.api.artifacts.result.DependencyResult;
import org.gradle.api.artifacts.result.ResolutionResult;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCopyDetails;
import org.gradle.api.file.FileTree;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.gradle.jvm.JvmLibrary;
import org.gradle.language.base.artifact.SourcesArtifact;
/**
* Gradle task to add source from shadowed jars into our own source jars.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class ShadowSource extends DefaultTask {
private final DirectoryProperty outputDirectory = getProject().getObjects().directoryProperty();
private List<Configuration> configurations = new ArrayList<>();
private final List<Relocation> relocations = new ArrayList<>();
@Classpath
@Optional
public List<Configuration> getConfigurations() {
return this.configurations;
}
public void setConfigurations(List<Configuration> configurations) {
this.configurations = configurations;
}
@Nested
public List<Relocation> getRelocations() {
return this.relocations;
}
public void relocate(String pattern, String destination) {
this.relocations.add(new Relocation(pattern, destination));
}
@OutputDirectory
DirectoryProperty getOutputDirectory() {
return this.outputDirectory;
}
@TaskAction
void syncSourceJarFiles() {
sync(getSourceJarFiles());
}
private List<File> getSourceJarFiles() {
List<File> sourceJarFiles = new ArrayList<>();
for (Configuration configuration : this.configurations) {
ResolutionResult resolutionResult = configuration.getIncoming().getResolutionResult();
resolutionResult.getRootComponent().get().getDependencies().forEach(dependency -> {
Set<ComponentArtifactsResult> artifactsResults = resolveSourceArtifacts(dependency);
for (ComponentArtifactsResult artifactResult : artifactsResults) {
artifactResult.getArtifacts(SourcesArtifact.class).forEach(sourceArtifact -> {
sourceJarFiles.add(((ResolvedArtifactResult) sourceArtifact).getFile());
});
}
});
}
return Collections.unmodifiableList(sourceJarFiles);
}
private Set<ComponentArtifactsResult> resolveSourceArtifacts(DependencyResult dependency) {
ModuleComponentSelector componentSelector = (ModuleComponentSelector) dependency.getRequested();
ArtifactResolutionQuery query = getProject().getDependencies().createArtifactResolutionQuery()
.forModule(componentSelector.getGroup(), componentSelector.getModule(), componentSelector.getVersion());
return executeQuery(query).getResolvedComponents();
}
@SuppressWarnings("unchecked")
private ArtifactResolutionResult executeQuery(ArtifactResolutionQuery query) {
return query.withArtifacts(JvmLibrary.class, SourcesArtifact.class).execute();
}
private void sync(List<File> sourceJarFiles) {
getProject().sync(spec -> {
spec.into(this.outputDirectory);
spec.eachFile(this::relocateFile);
spec.filter(this::transformContent);
spec.exclude("META-INF/**");
spec.setIncludeEmptyDirs(false);
sourceJarFiles.forEach(sourceJar -> spec.from(zipTree(sourceJar)));
});
}
private void relocateFile(FileCopyDetails details) {
String path = details.getPath();
for (Relocation relocation : this.relocations) {
path = relocation.relocatePath(path);
}
details.setPath(path);
}
private String transformContent(String content) {
for (Relocation relocation : this.relocations) {
content = relocation.transformContent(content);
}
return content;
}
private FileTree zipTree(File sourceJar) {
return getProject().zipTree(sourceJar);
}
/**
* A single relocation.
*/
static class Relocation {
private final String pattern;
private final String pathPattern;
private final String destination;
private final String pathDestination;
Relocation(String pattern, String destination) {
this.pattern = pattern;
this.pathPattern = pattern.replace('.', '/');
this.destination = destination;
this.pathDestination = destination.replace('.', '/');
}
@Input
public String getPattern() {
return this.pattern;
}
@Input
public String getDestination() {
return this.destination;
}
String relocatePath(String path) {
return path.replace(this.pathPattern, this.pathDestination);
}
public String transformContent(String content) {
return content.replaceAll("\\b" + this.pattern, this.destination);
}
}
}
+2 -2
View File
@@ -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-5.3.x[Spring Framework 5.3.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-5.3.x -c ci/pipeline.yml -l ci/parameters.yml
----
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
+2 -4
View File
@@ -2,11 +2,9 @@ FROM ubuntu:focal-20220922
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh
RUN ./setup.sh java8
ENV JAVA_HOME /opt/openjdk/java17
ENV JAVA_HOME /opt/openjdk/java8
ENV JDK17 /opt/openjdk/java17
ENV JDK18 /opt/openjdk/java18
ENV JDK19 /opt/openjdk/java19
ENV PATH $JAVA_HOME/bin:$PATH
+4 -7
View File
@@ -2,14 +2,11 @@
set -e
case "$1" in
java8)
echo "https://github.com/bell-sw/Liberica/releases/download/8u345+1/bellsoft-jdk8u345+1-linux-amd64.tar.gz"
;;
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.5+8/bellsoft-jdk17.0.5+8-linux-amd64.tar.gz"
;;
java18)
echo "https://github.com/bell-sw/Liberica/releases/download/18.0.2.1%2B1/bellsoft-jdk18.0.2.1+1-linux-amd64.tar.gz"
;;
java19)
echo "https://github.com/bell-sw/Liberica/releases/download/19.0.1%2B11/bellsoft-jdk19.0.1+11-linux-amd64.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.4+8/bellsoft-jdk17.0.4+8-linux-amd64.tar.gz"
;;
*)
echo $"Unknown java version"
+1 -1
View File
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
mkdir -p /opt/openjdk
pushd /opt/openjdk > /dev/null
for jdk in java17 java18 java19
for jdk in java8 java17
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
+2 -2
View File
@@ -3,8 +3,8 @@ github-repo-name: "spring-projects/spring-framework"
sonatype-staging-profile: "org.springframework"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "main"
milestone: "6.0.x"
branch: "5.3.x"
milestone: "5.3.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
+26 -62
View File
@@ -81,6 +81,13 @@ resources:
icon: github
source:
<<: *git-repo-resource-source
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: ci-images-git-repo
type: git
icon: github
@@ -94,13 +101,6 @@ resources:
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@@ -125,22 +125,14 @@ resources:
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk18-build
- name: repo-status-jdk17-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk18-build
- name: repo-status-jdk19-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk19-build
context: jdk17-build
- name: slack-alert
type: slack-notification
icon: slack
@@ -218,26 +210,26 @@ jobs:
threads: 8
artifact_set:
- include:
- "/**/framework-docs-*.zip"
- "/**/spring-*.zip"
properties:
"zip.name": "spring-framework"
"zip.displayname": "Spring Framework"
"zip.deployed": "false"
- include:
- "/**/framework-docs-*-docs.zip"
- "/**/spring-*-docs.zip"
properties:
"zip.type": "docs"
- include:
- "/**/framework-docs-*-dist.zip"
- "/**/spring-*-dist.zip"
properties:
"zip.type": "dist"
- include:
- "/**/framework-docs-*-schema.zip"
- "/**/spring-*-schema.zip"
properties:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk18-build
- name: jdk17-build
serial: true
public: true
plan:
@@ -245,53 +237,25 @@ jobs:
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk18-build
- put: repo-status-jdk17-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 18
<<: *build-project-task-params
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 17
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk18-build
- put: repo-status-jdk17-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk18-build
params: { state: "success", commit: "git-repo" }
- name: jdk19-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk19-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 19
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk19-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk19-build
- put: repo-status-jdk17-build
params: { state: "success", commit: "git-repo" }
- name: build-pull-requests
serial: true
@@ -471,7 +435,7 @@ jobs:
groups:
- name: "builds"
jobs: ["build", "jdk18-build", "jdk19-build"]
jobs: ["build", "jdk17-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
+1 -1
View File
@@ -4,6 +4,6 @@ set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK18 \
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
popd > /dev/null
+1
View File
@@ -1,5 +1,6 @@
#!/bin/bash
source $(dirname $0)/common.sh
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
+2 -3
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
tag: 0.10.0
tag: 0.9.1
registry_mirror:
host: ((registry-mirror-host))
username: ((registry-mirror-username))
@@ -28,5 +28,4 @@ run:
cat > /root/.docker/config.json <<EOF
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
EOF
build
build
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.8'
tag: '0.0.7'
inputs:
- name: git-repo
- name: artifactory-repo
@@ -1,292 +0,0 @@
[[aot]]
= Ahead of Time Optimizations
This chapter covers Spring's Ahead of Time (AOT) optimizations.
For AOT support specific to integration tests, see <<testing.adoc#testcontext-aot, Ahead of Time Support for Tests>>.
[[aot-introduction]]
== Introduction to Ahead of Time Optimizations
Spring's support for AOT optimizations is meant to inspect an `ApplicationContext` at build time and apply decisions and discovery logic that usually happens at runtime.
Doing so allows building an application startup arrangement that is more straightforward and focused on a fixed set of features based mainly on the classpath and the `Environment`.
Applying such optimizations early implies the following restrictions:
* The classpath is fixed and fully defined at build time.
* 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.
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
A Spring AOT processed application typically generates:
* Java source code
* Bytecode (usually for dynamic proxies)
* {api-spring-framework}/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 offer 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 {api-spring-framework}/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`.
For instance, a core implementation iterates over all candidate bean definitions and generates the necessary code to restore the state of the `BeanFactory`.
Once this process completes, the `GenerationContext` has been updated with the generated code, resources, and classes that are necessary for the application to run.
The `RuntimeHints` instance can also be used to generate the relevant GraalVM native image configuration files.
`ApplicationContextAotGenerator#processAheadOfTime` returns the class name of the `ApplicationContextInitializer` entry point that allows the context to be started with AOT optimizations.
Those steps are covered in more details in the sections below.
[[aot-refresh]]
== Refresh for AOT Processing
Refresh for AOT processing is supported on all `GenericApplicationContext` implementations.
An application context is created with any number of entry points, usually in the form of `@Configuration`-annotated classes.
Let's look at a basic example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration(proxyBeanMethods=false)
@ComponentScan
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
public class MyApplication {
}
----
Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling.
Refresh for AOT processing only applies a subset of what happens with a <<beans-introduction,regular `refresh`>>.
AOT processing can be triggered as follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
GenericApplicationContext applicationContext = new AnnotatedConfigApplicationContext();
context.register(MyApplication.class);
context.refreshForAotProcessing();
----
In this mode, <<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.
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:
* `MergedBeanDefinitionPostProcessor` implementations post-process bean definitions to extract additional settings, such as `init` and `destroy` methods.
* `SmartInstantiationAwareBeanPostProcessor` implementations determine a more precise bean type if necessary.
This makes sure to create any proxy that is required at runtime.
One this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that would be created at runtime.
[[aot-bean-factory-initialization-contributions]]
== Bean Factory Initialization AOT Contributions
Components that want to participate in this step can implement the {api-spring-framework}/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.
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` 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.
[NOTE]
====
If a bean implements the `BeanFactoryInitializationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing.
We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle.
If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized.
====
[[aot-bean-registration-contributions]]
=== Bean Registration AOT Contributions
A core `BeanFactoryInitializationAotProcessor` implementation is responsible for collecting the necessary contributions for each candidate `BeanDefinition`.
It does so using a dedicated `BeanRegistrationAotProcessor`.
This interface is used as follows:
* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior.
For instance <<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.
Typically used when the bean definition needs to be tuned for specific features of the core framework.
[NOTE]
====
If a bean implements the `BeanRegistrationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing.
We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle.
If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized.
====
If no `BeanRegistrationAotProcessor` handles a particular registered bean, a default implementation processes it.
This is the default behavior, since tuning the generated code for a bean definition should be restricted to corner cases.
Taking our previous example, let's assume that `DataSourceConfiguration` is as follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {
@Bean
public SimpleDataSource dataSource() {
return new SimpleDataSource();
}
}
----
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
The AOT engine will convert the configuration class above to code similar to the following:
[source,java,indent=0,role="primary"]
.Java
----
/**
* Bean definitions for {@link DataSourceConfiguration}
*/
public class DataSourceConfiguration__BeanDefinitions {
/**
* Get the bean definition for 'dataSourceConfiguration'
*/
public static BeanDefinition getDataSourceConfigurationBeanDefinition() {
Class<?> beanType = DataSourceConfiguration.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(DataSourceConfiguration::new);
return beanDefinition;
}
/**
* Get the bean instance supplier for 'dataSource'.
*/
private static BeanInstanceSupplier<SimpleDataSource> getDataSourceInstanceSupplier() {
return BeanInstanceSupplier.<SimpleDataSource>forFactoryMethod(DataSourceConfiguration.class, "dataSource")
.withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource());
}
/**
* Get the bean definition for 'dataSource'
*/
public static BeanDefinition getDataSourceBeanDefinition() {
Class<?> beanType = SimpleDataSource.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier());
return beanDefinition;
}
}
----
NOTE: The exact code generated may differ depending on the exact nature of your bean definitions.
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible.
There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`.
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
[[aot-hints]]
== Runtime Hints
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.
Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.
The {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
The following example makes sure that `config/app.properties` can be loaded from the classpath at runtime within a native image:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
runtimeHints.resources().registerPattern("config/app.properties");
----
A number of contracts are handled automatically during AOT processing.
For instance, the return type of a `@Controller` method is inspected, and relevant reflection hints are added if Spring detects that the type should be serialized (typically to JSON).
For cases that the core container cannot infer, you can register such hints programmatically.
A number of convenient annotations are also provided for common use cases.
[[aot-hints-import-runtime-hints]]
=== `@ImportRuntimeHints`
`RuntimeHintsRegistrar` implementations allow you to get a callback to the `RuntimeHints` instance managed by the AOT engine.
Implementations of this interface can be registered using `@ImportRuntimeHints` on any Spring bean or `@Bean` factory method.
`RuntimeHintsRegistrar` implementations are detected and invoked at build time.
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Component
@ImportRuntimeHints(MyComponentRuntimeHints.class)
public class MyComponent {
// ...
private static class MyComponentRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// ...
}
}
}
----
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.
[[aot-hints-reflective]]
=== `@Reflective`
{api-spring-framework}/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.
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
`@Reflective` annotation.
Library authors can reuse this annotation for their own purposes.
If components other than Spring beans need to be processed, a `BeanFactoryInitializationAotProcessor` can detect the relevant types and use `ReflectiveRuntimeHintsRegistrar` to process them.
[[aot-hints-register-reflection-for-binding]]
=== `@RegisterReflectionForBinding`
{api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types.
A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body.
`@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required.
The following example registers `Account` for serialization.
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Component
public class OrderService {
@RegisterReflectionForBinding(Account.class)
public void process(Order order) {
// ...
}
}
----
@@ -1,25 +0,0 @@
:noheader:
= Spring Framework Documentation
include::overview.adoc[leveloffset=+1]
include::core.adoc[leveloffset=+1]
include::testing.adoc[leveloffset=+1]
include::data-access.adoc[leveloffset=+1]
include::web.adoc[leveloffset=+1]
include::web-reactive.adoc[leveloffset=+1]
include::integration.adoc[leveloffset=+1]
include::languages.adoc[leveloffset=+1]
include::appendix.adoc[leveloffset=+1]
Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg,
Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin
Vervaet, Portia Tung, Ben Hale, Adrian Colyer, John Lewis, Costin Leau, Mark Fisher, Sam
Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clement, Dave
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
Copyright © 2002 - 2022 VMware, Inc. All Rights Reserved.
Copies of this document may be made for your own use and for distribution to others,
provided that you do not charge any fee for such copies and further provided that each
copy contains this Copyright Notice, whether distributed in print or electronically.
@@ -1,148 +0,0 @@
plugins {
id 'java-platform'
}
javaPlatform {
allowDependencies()
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.14.0"))
api(platform("io.micrometer:micrometer-bom:1.10.0"))
api(platform("io.netty:netty-bom:4.1.85.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.5"))
api(platform("org.apache.logging.log4j:log4j-bom:2.19.0"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.12"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
api(platform("org.junit:junit-bom:5.9.1"))
api(platform("org.mockito:mockito-bom:4.8.1"))
constraints {
api("com.fasterxml.woodstox:woodstox-core:6.4.0")
api("com.fasterxml:aalto-xml:1.3.1")
api("com.github.ben-manes.caffeine:caffeine:3.1.1")
api("com.github.librepdf:openpdf:1.3.30")
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.9.1")
api("com.google.protobuf:protobuf-java-util:3.21.5")
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
api("com.h2database:h2:2.1.214")
api("com.jayway.jsonpath:json-path:2.7.0")
api("com.rometools:rome:1.18.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
api("com.squareup.okhttp3:okhttp:3.14.9")
api("com.sun.activation:jakarta.activation:2.0.1")
api("com.sun.mail:jakarta.mail:2.0.1")
api("com.sun.xml.bind:jaxb-core:3.0.2")
api("com.sun.xml.bind:jaxb-impl:3.0.2")
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.0.2")
api("com.thoughtworks.xstream:xstream:1.4.19")
api("commons-io:commons-io:2.11.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1")
api("info.picocli:picocli:4.6.3")
api("io.micrometer:context-propagation:1.0.0")
api("io.mockk:mockk:1.12.1")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
api("io.projectreactor.tools:blockhound:1.0.6.RELEASE")
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.5")
api("io.smallrye.reactive:mutiny:1.8.0")
api("io.undertow:undertow-core:2.3.0.Final")
api("io.undertow:undertow-servlet:2.3.0.Final")
api("io.undertow:undertow-websockets-jsr:2.3.0.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")
api("jakarta.ejb:jakarta.ejb-api:4.0.0")
api("jakarta.el:jakarta.el-api:4.0.0")
api("jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:2.0.0")
api("jakarta.faces:jakarta.faces-api:3.0.0")
api("jakarta.inject:jakarta.inject-api:2.0.0")
api("jakarta.inject:jakarta.inject-tck:2.0.1")
api("jakarta.interceptor:jakarta.interceptor-api:2.0.0")
api("jakarta.jms:jakarta.jms-api:3.0.0")
api("jakarta.json.bind:jakarta.json.bind-api:2.0.0")
api("jakarta.json:jakarta.json-api:2.0.1")
api("jakarta.mail:jakarta.mail-api:2.0.1")
api("jakarta.persistence:jakarta.persistence-api:3.0.0")
api("jakarta.resource:jakarta.resource-api:2.0.0")
api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0")
api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0")
api("jakarta.servlet:jakarta.servlet-api:6.0.0")
api("jakarta.transaction:jakarta.transaction-api:2.0.1")
api("jakarta.validation:jakarta.validation-api:3.0.2")
api("jakarta.websocket:jakarta.websocket-api:2.1.0")
api("jakarta.websocket:jakarta.websocket-client-api:2.1.0")
api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")
api("javax.cache:cache-api:1.1.1")
api("javax.money:money-api:1.1")
api("jaxen:jaxen:1.2.0")
api("junit:junit:4.13.2")
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("net.sourceforge.htmlunit:htmlunit:2.66.0")
api("org.apache-extras.beanshell:bsh:2.0b6")
api("org.apache.activemq:activemq-broker:5.16.2")
api("org.apache.activemq:activemq-kahadb-store:5.16.2")
api("org.apache.activemq:activemq-stomp:5.16.2")
api("org.apache.commons:commons-pool2:2.9.0")
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.1.3")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3")
api("org.apache.poi:poi-ooxml:5.2.2")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.1")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.1")
api("org.apache.tomcat:tomcat-util:10.1.1")
api("org.apache.tomcat:tomcat-websocket:10.1.1")
api("org.aspectj:aspectjrt:1.9.9.1")
api("org.aspectj:aspectjtools:1.9.9.1")
api("org.aspectj:aspectjweaver:1.9.9.1")
api("org.assertj:assertj-core:3.23.1")
api("org.awaitility:awaitility:3.1.6")
api("org.bouncycastle:bcpkix-jdk18on:1.71")
api("org.codehaus.jettison:jettison:1.3.8")
api("org.dom4j:dom4j:2.1.3")
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.7")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.4.0")
api("org.ehcache:jcache:1.0.1")
api("org.freemarker:freemarker:2.3.31")
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
// is not available on Maven Central
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
api("org.glassfish.tyrus:tyrus-container-servlet:2.0.1")
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.0")
api("org.hamcrest:hamcrest:2.2")
api("org.hibernate:hibernate-core-jakarta:5.6.12.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.0")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.3.8.0")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.11")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.1")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit-driver:2.66.0")
api("org.seleniumhq.selenium:selenium-java:3.141.59")
api("org.skyscreamer:jsonassert:1.5.0")
api("org.slf4j:slf4j-api:2.0.3")
api("org.testng:testng:7.6.1")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.48")
api("org.xmlunit:xmlunit-assertj:2.9.0")
api("org.xmlunit:xmlunit-matchers:2.9.0")
api("org.yaml:snakeyaml:1.30")
}
}
+2 -6
View File
@@ -1,9 +1,5 @@
version=6.0.0
org.gradle.caching=true
version=5.3.25
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
kotlinVersion=1.7.20
kotlin.stdlib.default.dependency=false
+1 -1
View File
@@ -20,7 +20,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
url.set(new URL("https://javadoc.io/doc/org.hamcrest/hamcrest/2.1/"))
}
externalDocumentationLink {
url.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/"))
url.set(new URL("https://javadoc.io/doc/javax.servlet/javax.servlet-api/latest/"))
}
externalDocumentationLink {
url.set(new URL("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"))
@@ -1,30 +1,17 @@
description = "Spring Framework Docs"
apply plugin: 'kotlin'
apply plugin: 'org.asciidoctor.jvm.convert'
apply plugin: 'org.asciidoctor.jvm.pdf'
apply from: "${rootDir}/gradle/publications.gradle"
configurations {
asciidoctorExtensions
}
jar {
enabled = false
}
javadoc {
enabled = false
asciidoctorExt
}
dependencies {
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3"
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.5.0")
}
repositories {
maven {
url "https://repo.spring.io/release"
mavenContent {
includeGroup "io.spring.asciidoctor"
}
}
}
@@ -55,11 +42,15 @@ task api(type: Javadoc) {
author = true
header = rootProject.description
use = true
overview = "framework-docs/src/docs/api/overview.html"
overview = "src/docs/api/overview.html"
stylesheetFile = file("src/docs/api/stylesheet.css")
splitIndex = true
links(project.ext.javadocLinks)
addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint
addBooleanOption('Werror', true) // fail build on Javadoc warnings
addBooleanOption('Xwerror', true) // fail build on Javadoc warnings
if (JavaVersion.current().isJava9Compatible()) {
addBooleanOption('html5', true)
}
}
source moduleProjects.collect { project ->
project.sourceSets.main.allJava
@@ -71,27 +62,49 @@ task api(type: Javadoc) {
/**
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
*/
rootProject.tasks.dokkaHtmlMultiModule.configure {
dependsOn {
tasks.getByName("api")
pluginManager.withPlugin("kotlin") {
tasks.dokkaHtmlMultiModule.configure {
dependsOn {
tasks.getByName("api")
}
moduleName.set("spring-framework")
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
}
moduleName.set("spring-framework")
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
}
task downloadResources(type: Download) {
def version = "0.2.5"
src "https://repo.spring.io/release/io/spring/docresources/" +
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
dest project.file("$buildDir/docs/spring-doc-resources.zip")
onlyIfModified true
useETag "all"
}
task extractDocResources(type: Copy, dependsOn: downloadResources) {
from project.zipTree(downloadResources.dest);
into "$buildDir/docs/spring-docs-resources/"
}
asciidoctorj {
def docRoot = 'https://docs.spring.io'
def docsSpringFramework = "${docRoot}/spring-framework/docs/${project.version}"
version = '2.4.3'
version = '2.4.1'
fatalWarnings ".*"
options doctype: 'book', eruby: 'erubis'
attributes([
icons: 'font',
idprefix: '',
idseparator: '-',
docinfo: 'shared,private-header', // https://docs.asciidoctor.org/asciidoctor/latest/docinfo/
revnumber: project.version,
sectanchors: '',
sectnums: '',
'source-highlighter': 'highlight.js',
highlightjsdir: 'js/highlight',
'highlightjs-theme': 'googlecode',
stylesdir: 'css/',
stylesheet: 'stylesheet.css',
'spring-version': project.version,
'spring-framework-main-code': 'https://github.com/spring-projects/spring-framework/tree/main',
'doc-root': docRoot,
@@ -101,24 +114,22 @@ asciidoctorj {
}
/**
* Generate the Spring Framework Reference documentation from
* "src/docs/asciidoc" in "build/docs/ref-docs/html5".
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
* in "build/docs/ref-docs/html5".
*/
asciidoctor {
baseDirFollowsSourceDir()
configurations "asciidoctorExtensions"
configurations 'asciidoctorExt'
sources {
include '*.adoc'
}
outputDir "$buildDir/docs/ref-docs/html5"
outputOptions {
backends "spring-html"
}
logDocuments = true
resources {
from(sourceDir) {
include 'images/*.png'
include 'images/*.png', 'css/**', 'js/**'
}
from extractDocResources
}
}
@@ -128,21 +139,18 @@ asciidoctor {
*/
asciidoctorPdf {
baseDirFollowsSourceDir()
configurations 'asciidoctorExtensions'
configurations 'asciidoctorExt'
sources {
include 'spring-framework.adocbook'
include '*.adoc'
}
outputDir "$buildDir/docs/ref-docs/pdf"
forkOptions {
jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED"]
}
logDocuments = true
}
/**
* Zip all docs (API and reference) into a single archive
*/
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', rootProject.tasks.dokkaHtmlMultiModule]) {
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokkaHtmlMultiModule']) {
group = "Distribution"
description = "Builds -${archiveClassifier} archive containing api and reference " +
"for deployment at https://docs.spring.io/spring-framework/docs/."
@@ -161,7 +169,7 @@ task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', root
from ("$asciidoctorPdf.outputDir") {
into "reference/pdf"
}
from (rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
from (dokkaHtmlMultiModule.outputDirectory) {
into "kdoc-api"
}
}
@@ -240,14 +248,3 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
}
distZip.mustRunAfter moduleProjects.check
publishing {
publications {
mavenJava(MavenPublication) {
artifact docsZip
artifact schemaZip
artifact distZip
}
}
}
+4 -4
View File
@@ -4,8 +4,8 @@ import org.gradle.plugins.ide.eclipse.model.SourceFolder
apply plugin: 'eclipse'
eclipse.jdt {
sourceCompatibility = 17
targetCompatibility = 17
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
// Replace classpath entries with project dependencies (GRADLE-1116)
@@ -30,7 +30,7 @@ eclipse.classpath.file.whenMerged { classpath ->
}
// Remove any remaining direct depencencies on JARs in the build/libs folder
// except the repack JARs.
// except Spring's spring-cglib-repack and spring-objenesis-repack JARs.
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
}
@@ -64,7 +64,7 @@ eclipse.classpath.file.whenMerged {
// so that they can see test fixtures.
// https://github.com/melix/jmh-gradle-plugin/issues/157
eclipse.classpath.file.whenMerged {
entries.findAll { it.path =~ /src\/jmh\/(java|kotlin|resources)/ }.each {
entries.findAll { it.path =~ /src\/jmh\/(java|resources)/ }.each {
it.entryAttributes['test'] = 'true'
}
}
+2 -7
View File
@@ -1,5 +1,5 @@
apply plugin: 'java-library'
apply plugin: 'org.springframework.build.conventions'
apply plugin: 'org.springframework.build.compile'
apply plugin: 'org.springframework.build.optional-dependencies'
// Uncomment the following for Shadow support in the jmhJar block.
// Currently commented out due to ZipException: archive is not a ZIP archive
@@ -13,11 +13,6 @@ dependencies {
jmh 'net.sf.jopt-simple:jopt-simple'
}
pluginManager.withPlugin("kotlin") {
apply plugin: "org.jetbrains.dokka"
apply from: "${rootDir}/gradle/docs-dokka.gradle"
}
jmh {
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
}
@@ -49,7 +44,7 @@ jar {
manifest.attributes["Created-By"] =
"${System.getProperty("java.version")} (${System.getProperty("java.specification.vendor")})"
from("${rootDir}/framework-docs/src/docs/dist") {
from("${rootDir}/src/docs/dist") {
include "license.txt"
include "notice.txt"
into "META-INF"
+49 -21
View File
@@ -5,13 +5,13 @@
* One can choose the toolchain to use for compiling the MAIN sources and/or compiling
* and running the TEST sources. These options apply to Java, Kotlin and Groovy sources
* when available.
* {@code "./gradlew check -PmainToolchain=17 -PtestToolchain=18"} will use:
* {@code "./gradlew check -PmainToolchain=8 -PtestToolchain=11"} will use:
* <ul>
* <li>a JDK17 toolchain for compiling the main SourceSet
* <li>a JDK18 toolchain for compiling and running the test SourceSet
* <li>a JDK8 toolchain for compiling the main SourceSet
* <li>a JDK11 toolchain for compiling and running the test SourceSet
* </ul>
*
* By default, the build will fall back to using the current JDK and 17 language level for all sourceSets.
* By default, the build will fall back to using the current JDK and 1.8 language level for all sourceSets.
*
* Gradle will automatically detect JDK distributions in well-known locations.
* The following command will list the detected JDKs on the host.
@@ -21,11 +21,11 @@
*
* We can also configure ENV variables and let Gradle know about them:
* {@code
* $ echo JDK17
* /opt/openjdk/java17
* $ echo JDK18
* /opt/openjdk/java18
* $ ./gradlew -Porg.gradle.java.installations.fromEnv=JDK17,JDK18 check
* $ echo JDK11
* /opt/openjdk/java11
* $ echo JDK15
* /opt/openjdk/java15
* $ ./gradlew -Porg.gradle.java.installations.fromEnv=JDK11,JDK15 check
* }
*
* @author Brian Clozel
@@ -44,7 +44,7 @@ def mainToolchainLanguageVersion() {
if (mainToolchainConfigured()) {
return JavaLanguageVersion.of(project.mainToolchain.toString())
}
return JavaLanguageVersion.of(17)
return JavaLanguageVersion.of(8)
}
def testToolchainLanguageVersion() {
@@ -64,9 +64,9 @@ plugins.withType(JavaPlugin) {
}
}
else {
// Fallback to JDK17
// Fallback to JDK8
java {
sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_1_8
}
}
// Configure a specific Java Toolchain for compiling and running tests if the 'testToolchain' property is defined
@@ -81,29 +81,57 @@ plugins.withType(JavaPlugin) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = testLanguageVersion
}
if(testLanguageVersion == JavaLanguageVersion.of(17)) {
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED"])
}
}
}
}
plugins.withType(GroovyPlugin) {
// Fallback to JDK17
// Fallback to JDK8
if (!mainToolchainConfigured()) {
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_1_8
}
}
}
pluginManager.withPlugin("kotlin") {
// Fallback to JDK17
compileKotlin {
kotlinOptions {
jvmTarget = '17'
// Configure the Kotlin compiler if the 'mainToolchain' property is defined
if (mainToolchainConfigured()) {
def mainLanguageVersion = mainToolchainLanguageVersion()
def compiler = javaToolchains.compilerFor {
languageVersion = mainLanguageVersion
}
// See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm
def javaVersion = mainLanguageVersion.toString() == '8' ? '1.8' : mainLanguageVersion.toString()
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
// Compile the test classes with the same version, 'testToolchain' will override if defined
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath
}
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = '17'
else {
// Fallback to JDK8
compileKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = '1.8'
}
}
}
}
Binary file not shown.
+4 -5
View File
@@ -3,7 +3,7 @@
This document will guide you through the process of importing the Spring Framework
projects into Eclipse or the Spring Tool Suite (_STS_). It is recommended that you
have a recent version of Eclipse. As a bare minimum you will need Eclipse with full Java
17 support and Eclipse Buildship.
8 support, Eclipse Buildship, and the Groovy plugin.
The following instructions have been tested against [STS](https://spring.io/tools) 4.12.0
([download](https://github.com/spring-projects/sts4/wiki/Previous-Versions#spring-tools-4120-changelog))
@@ -12,14 +12,13 @@ The instructions should work with the latest Eclipse distribution as long as you
[Buildship](https://marketplace.eclipse.org/content/buildship-gradle-integration). Note
that STS 4 comes with Buildship preinstalled.
If you are using Eclipse 4.21, you will need to install
[Java 17 Support for Eclipse 2021-09 (4.21)](https://marketplace.eclipse.org/content/java-17-support-eclipse-2021-09-421)
from the Eclipse Marketplace.
## Steps
_When instructed to execute `./gradlew` from the command line, be sure to execute it within your locally cloned `spring-framework` working directory._
1. Install the [Groovy Development Tools](https://marketplace.eclipse.org/content/groovy-development-tools).
1. Switch to Groovy 3.0 in Eclipse (Preferences &#8594; Groovy &#8594; Compiler &#8594; Switch to 3.0...).
- If you encounter build errors stating something similar to _"Groovy: compiler mismatch: project level is 2.5, workspace level is 3.0"_, change the Groovy compiler version to 3.0 for each affected project.
1. Ensure that the _Forbidden reference (access rule)_ in Eclipse is set to `Info`
(Preferences &#8594; Java &#8594; Compiler &#8594; Errors/Warnings &#8594; Deprecated and restricted API &#8594; Forbidden reference (access rule)).
1. Optionally install the [Kotlin Plugin for Eclipse](https://marketplace.eclipse.org/content/kotlin-plugin-eclipse) if you need to execute Kotlin-based tests or develop Kotlin extensions.
+4 -9
View File
@@ -1,7 +1,3 @@
plugins {
id 'org.springframework.build.runtimehints-agent'
}
description = "Spring Integration Tests"
dependencies {
@@ -9,7 +5,6 @@ dependencies {
testImplementation(project(":spring-beans"))
testImplementation(project(":spring-context"))
testImplementation(project(":spring-core"))
testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-aop")))
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
@@ -20,12 +15,12 @@ dependencies {
testImplementation(project(":spring-test"))
testImplementation(project(":spring-tx"))
testImplementation(project(":spring-web"))
testImplementation("jakarta.inject:jakarta.inject-api")
testImplementation("jakarta.resource:jakarta.resource-api")
testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("javax.inject:javax.inject")
testImplementation("javax.resource:javax.resource-api")
testImplementation("javax.servlet:javax.servlet-api")
testImplementation("org.aspectj:aspectjweaver")
testImplementation("org.hsqldb:hsqldb")
testImplementation("org.hibernate:hibernate-core-jakarta")
testImplementation("org.hibernate:hibernate-core")
}
normalization {
@@ -20,7 +20,8 @@ import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import jakarta.servlet.ServletException;
import javax.servlet.ServletException;
import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
@@ -1,297 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aot;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.ResourceBundle;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.aot.agent.HintType;
import org.springframework.aot.agent.MethodReference;
import org.springframework.aot.agent.RecordedInvocation;
import org.springframework.aot.agent.RecordedInvocationsListener;
import org.springframework.aot.agent.RecordedInvocationsPublisher;
import org.springframework.aot.agent.RuntimeHintsAgent;
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link RuntimeHintsAgent}.
*
* @author Brian Clozel
*/
@EnabledIfRuntimeHintsAgent
public class RuntimeHintsAgentTests {
private static final ClassLoader classLoader = ClassLoader.getSystemClassLoader();
private static Constructor<String> defaultConstructor;
private static Method toStringMethod;
private static Method privateGreetMethod;
@BeforeAll
public static void classSetup() throws NoSuchMethodException {
defaultConstructor = String.class.getConstructor();
toStringMethod = String.class.getMethod("toString");
privateGreetMethod = PrivateClass.class.getDeclaredMethod("greet");
}
@ParameterizedTest
@MethodSource("instrumentedReflectionMethods")
void shouldInstrumentReflectionMethods(Runnable runnable, MethodReference methodReference) {
RecordingSession session = RecordingSession.record(runnable);
assertThat(session.recordedInvocations()).hasSize(1);
RecordedInvocation invocation = session.recordedInvocations().findFirst().get();
assertThat(invocation.getMethodReference()).isEqualTo(methodReference);
assertThat(invocation.getStackFrames()).first().matches(frame -> frame.getClassName().equals(RuntimeHintsAgentTests.class.getName()));
}
private static Stream<Arguments> instrumentedReflectionMethods() {
return Stream.of(
Arguments.of((Runnable) () -> {
try {
Class.forName("java.lang.String");
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Class.class, "forName")),
Arguments.of((Runnable) () -> String.class.getClasses(), MethodReference.of(Class.class, "getClasses")),
Arguments.of((Runnable) () -> {
try {
String.class.getConstructor();
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Class.class, "getConstructor")),
Arguments.of((Runnable) () -> String.class.getConstructors(), MethodReference.of(Class.class, "getConstructors")),
Arguments.of((Runnable) () -> String.class.getDeclaredClasses(), MethodReference.of(Class.class, "getDeclaredClasses")),
Arguments.of((Runnable) () -> {
try {
String.class.getDeclaredConstructor();
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Class.class, "getDeclaredConstructor")),
Arguments.of((Runnable) () -> String.class.getDeclaredConstructors(), MethodReference.of(Class.class, "getDeclaredConstructors")),
Arguments.of((Runnable) () -> {
try {
String.class.getDeclaredField("value");
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Class.class, "getDeclaredField")),
Arguments.of((Runnable) () -> String.class.getDeclaredFields(), MethodReference.of(Class.class, "getDeclaredFields")),
Arguments.of((Runnable) () -> {
try {
String.class.getDeclaredMethod("toString");
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Class.class, "getDeclaredMethod")),
Arguments.of((Runnable) () -> String.class.getDeclaredMethods(), MethodReference.of(Class.class, "getDeclaredMethods")),
Arguments.of((Runnable) () -> {
try {
String.class.getField("value");
}
catch (NoSuchFieldException e) {
}
}, MethodReference.of(Class.class, "getField")),
Arguments.of((Runnable) () -> String.class.getFields(), MethodReference.of(Class.class, "getFields")),
Arguments.of((Runnable) () -> {
try {
String.class.getMethod("toString");
}
catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Class.class, "getMethod")),
Arguments.of((Runnable) () -> String.class.getMethods(), MethodReference.of(Class.class, "getMethods")),
Arguments.of((Runnable) () -> {
try {
classLoader.loadClass("java.lang.String");
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}, MethodReference.of(ClassLoader.class, "loadClass")),
Arguments.of((Runnable) () -> {
try {
defaultConstructor.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Constructor.class, "newInstance")),
Arguments.of((Runnable) () -> {
try {
toStringMethod.invoke("");
}
catch (Exception e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Method.class, "invoke")),
Arguments.of((Runnable) () -> {
try {
privateGreetMethod.invoke(new PrivateClass());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}, MethodReference.of(Method.class, "invoke"))
);
}
@ParameterizedTest
@MethodSource("instrumentedResourceBundleMethods")
void shouldInstrumentResourceBundleMethods(Runnable runnable, MethodReference methodReference) {
RecordingSession session = RecordingSession.record(runnable);
assertThat(session.recordedInvocations(HintType.RESOURCE_BUNDLE)).hasSize(1);
RecordedInvocation resolution = session.recordedInvocations(HintType.RESOURCE_BUNDLE).findFirst().get();
assertThat(resolution.getMethodReference()).isEqualTo(methodReference);
assertThat(resolution.getStackFrames()).first().matches(frame -> frame.getClassName().equals(RuntimeHintsAgentTests.class.getName()));
}
private static Stream<Arguments> instrumentedResourceBundleMethods() {
return Stream.of(
Arguments.of((Runnable) () -> {
try {
ResourceBundle.getBundle("testBundle");
}
catch (Throwable exc) {
}
},
MethodReference.of(ResourceBundle.class, "getBundle"))
);
}
@ParameterizedTest
@MethodSource("instrumentedResourcePatternMethods")
void shouldInstrumentResourcePatternMethods(Runnable runnable, MethodReference methodReference) {
RecordingSession session = RecordingSession.record(runnable);
assertThat(session.recordedInvocations(HintType.RESOURCE_PATTERN)).hasSize(1);
RecordedInvocation resolution = session.recordedInvocations(HintType.RESOURCE_PATTERN).findFirst().get();
assertThat(resolution.getMethodReference()).isEqualTo(methodReference);
assertThat(resolution.getStackFrames()).first().matches(frame -> frame.getClassName().equals(RuntimeHintsAgentTests.class.getName()));
}
private static Stream<Arguments> instrumentedResourcePatternMethods() {
return Stream.of(
Arguments.of((Runnable) () -> RuntimeHintsAgentTests.class.getResource("sample.txt"),
MethodReference.of(Class.class, "getResource")),
Arguments.of((Runnable) () -> RuntimeHintsAgentTests.class.getResourceAsStream("sample.txt"),
MethodReference.of(Class.class, "getResourceAsStream")),
Arguments.of((Runnable) () -> classLoader.getResource("sample.txt"),
MethodReference.of(ClassLoader.class, "getResource")),
Arguments.of((Runnable) () -> classLoader.getResourceAsStream("sample.txt"),
MethodReference.of(ClassLoader.class, "getResourceAsStream")),
Arguments.of((Runnable) () -> {
try {
classLoader.getResources("sample.txt");
}
catch (IOException e) {
}
},
MethodReference.of(ClassLoader.class, "getResources")),
Arguments.of((Runnable) () -> {
try {
RuntimeHintsAgentTests.class.getModule().getResourceAsStream("sample.txt");
}
catch (IOException e) {
}
},
MethodReference.of(Module.class, "getResourceAsStream")),
Arguments.of((Runnable) () -> classLoader.resources("sample.txt"),
MethodReference.of(ClassLoader.class, "resources"))
);
}
@Test
void shouldInstrumentStaticMethodHandle() {
RecordingSession session = RecordingSession.record(ClassLoader.class::getClasses);
assertThat(session.recordedInvocations(HintType.REFLECTION)).hasSize(1);
RecordedInvocation resolution = session.recordedInvocations(HintType.REFLECTION).findFirst().get();
assertThat(resolution.getMethodReference()).isEqualTo(MethodReference.of(Class.class, "getClasses"));
assertThat(resolution.getStackFrames()).first().extracting(StackWalker.StackFrame::getClassName)
.isEqualTo(RuntimeHintsAgentTests.class.getName() + "$RecordingSession");
}
static class RecordingSession implements RecordedInvocationsListener {
final Deque<RecordedInvocation> recordedInvocations = new ArrayDeque<>();
static RecordingSession record(Runnable action) {
RecordingSession session = new RecordingSession();
RecordedInvocationsPublisher.addListener(session);
try {
action.run();
}
finally {
RecordedInvocationsPublisher.removeListener(session);
}
return session;
}
@Override
public void onInvocation(RecordedInvocation invocation) {
this.recordedInvocations.addLast(invocation);
}
Stream<RecordedInvocation> recordedInvocations() {
return this.recordedInvocations.stream();
}
Stream<RecordedInvocation> recordedInvocations(HintType hintType) {
return recordedInvocations().filter(invocation -> invocation.getHintType() == hintType);
}
}
private static class PrivateClass {
@SuppressWarnings("unused")
private String greet() {
return "hello";
}
}
}
@@ -1,57 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aot.test;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
import static org.assertj.core.api.Assertions.assertThat;
@EnabledIfRuntimeHintsAgent
class ReflectionInvocationsTests {
@Test
void sampleTest() {
RuntimeHints hints = new RuntimeHints();
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
SampleReflection sample = new SampleReflection();
sample.sample(); // does Method[] methods = String.class.getMethods();
});
assertThat(invocations).match(hints);
}
@Test
void multipleCallsTest() {
RuntimeHints hints = new RuntimeHints();
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
hints.reflection().registerType(Integer.class,MemberCategory.INTROSPECT_PUBLIC_METHODS);
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
SampleReflection sample = new SampleReflection();
sample.multipleCalls(); // does Method[] methods = String.class.getMethods(); methods = Integer.class.getMethods();
});
assertThat(invocations).match(hints);
}
}
@@ -1,40 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aot.test;
import java.lang.reflect.Method;
/**
* @author Brian Clozel
* @since 6.0
*/
public class SampleReflection {
@SuppressWarnings("unused")
public void sample() {
String value = "Sample";
Method[] methods = String.class.getMethods();
}
@SuppressWarnings("unused")
public void multipleCalls() {
String value = "Sample";
Method[] methods = String.class.getMethods();
methods = Integer.class.getMethods();
}
}
@@ -21,8 +21,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import javax.inject.Named;
import javax.inject.Singleton;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -307,18 +308,19 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
scanner.setIncludeAnnotationConfig(false);
scanner.setScopeMetadataResolver(definition -> {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition annDef) {
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
for (String type : annDef.getMetadata().getAnnotationTypes()) {
if (type.equals(jakarta.inject.Singleton.class.getName())) {
if (type.equals(javax.inject.Singleton.class.getName())) {
metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON);
break;
}
else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(jakarta.inject.Scope.class.getName())) {
else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) {
metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase());
metadata.setScopedProxyMode(scopedProxyMode);
break;
}
else if (type.startsWith("jakarta.inject")) {
else if (type.startsWith("javax.inject")) {
metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
}
}
@@ -385,14 +387,14 @@ class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@jakarta.inject.Scope
@javax.inject.Scope
public @interface RequestScoped {
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@jakarta.inject.Scope
@javax.inject.Scope
public @interface SessionScoped {
}
@@ -41,6 +41,9 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jca.context.ResourceAdapterApplicationContext;
import org.springframework.jca.support.SimpleBootstrapContext;
import org.springframework.jca.work.SimpleTaskWorkManager;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
import org.springframework.mock.web.MockServletConfig;
@@ -532,6 +535,22 @@ public class EnvironmentSystemIntegrationTests {
assertThat(environment.getProperty("pSysProps1")).isEqualTo("pSysProps1Value");
}
@Test
void resourceAdapterApplicationContext() {
ResourceAdapterApplicationContext ctx = new ResourceAdapterApplicationContext(new SimpleBootstrapContext(new SimpleTaskWorkManager()));
assertHasStandardEnvironment(ctx);
registerEnvironmentBeanDefinition(ctx);
ctx.setEnvironment(prodEnv);
ctx.refresh();
assertHasEnvironment(ctx, prodEnv);
assertEnvironmentBeanRegistered(ctx);
assertEnvironmentAwareInvoked(ctx, prodEnv);
}
@Test
void abstractApplicationContextValidatesRequiredPropertiesOnRefresh() {
{
@@ -41,7 +41,7 @@
<props>
<prop key="setA*">PROPAGATION_REQUIRED</prop>
<prop key="rollbackOnly">PROPAGATION_REQUIRED</prop>
<prop key="echoException">PROPAGATION_REQUIRED,+jakarta.servlet.ServletException,-java.lang.Exception</prop>
<prop key="echoException">PROPAGATION_REQUIRED,+javax.servlet.ServletException,-java.lang.Exception</prop>
</props>
</property>
</bean>
-3
View File
@@ -18,7 +18,6 @@ include "spring-context"
include "spring-context-indexer"
include "spring-context-support"
include "spring-core"
include "spring-core-test"
include "spring-expression"
include "spring-instrument"
include "spring-jcl"
@@ -35,8 +34,6 @@ include "spring-webflux"
include "spring-webmvc"
include "spring-websocket"
include "framework-bom"
include "framework-docs"
include "framework-platform"
include "integration-tests"
rootProject.name = "spring"
+1 -1
View File
@@ -5,7 +5,7 @@ dependencies {
api(project(":spring-core"))
optional("org.aspectj:aspectjweaver")
optional("org.apache.commons:commons-pool2")
testImplementation(project(":spring-core-test"))
optional("com.jamonapi:jamon")
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
testFixturesImplementation(testFixtures(project(":spring-beans")))
@@ -78,9 +78,10 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
*/
public static JoinPoint currentJoinPoint() {
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
if (!(mi instanceof ProxyMethodInvocation pmi)) {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
if (jp == null) {
jp = new MethodInvocationProceedingJoinPoint(pmi);
@@ -261,7 +262,7 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
public void setArgumentNamesFromStringArray(String... args) {
this.argumentNames = new String[args.length];
for (int i = 0; i < args.length; i++) {
this.argumentNames[i] = args[i].strip();
this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);
if (!isVariableName(this.argumentNames[i])) {
throw new IllegalArgumentException(
"'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +
@@ -713,9 +714,10 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
if (this == other) {
return true;
}
if (!(other instanceof AdviceExcludingMethodMatcher otherMm)) {
if (!(other instanceof AdviceExcludingMethodMatcher)) {
return false;
}
AdviceExcludingMethodMatcher otherMm = (AdviceExcludingMethodMatcher) other;
return this.adviceMethod.equals(otherMm.adviceMethod);
}
@@ -158,7 +158,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
/** The pointcut expression associated with the advice, as a simple String. */
@Nullable
private final String pointcutExpression;
private String pointcutExpression;
private boolean raiseExceptions;
@@ -486,7 +486,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
String[] tokens = StringUtils.tokenizeToStringArray(argsSpec, ",");
for (int i = 0; i < tokens.length; i++) {
tokens[i] = tokens[i].strip();
tokens[i] = StringUtils.trimWhitespace(tokens[i]);
String varName = maybeExtractVariableName(tokens[i]);
if (varName != null) {
varNames.add(varName);
@@ -759,10 +759,10 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
*/
private static class PointcutBody {
private final int numTokensConsumed;
private int numTokensConsumed;
@Nullable
private final String text;
private String text;
public PointcutBody(int tokens, @Nullable String text) {
this.numTokensConsumed = tokens;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2020 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.
@@ -63,9 +63,10 @@ public class AspectJAroundAdvice extends AbstractAspectJAdvice implements Method
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation pmi)) {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
return invokeAdviceMethod(pjp, jpm, null, null);
@@ -523,9 +523,10 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
if (this == other) {
return true;
}
if (!(other instanceof AspectJExpressionPointcut otherPc)) {
if (!(other instanceof AspectJExpressionPointcut)) {
return false;
}
AspectJExpressionPointcut otherPc = (AspectJExpressionPointcut) other;
return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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.
@@ -97,9 +97,10 @@ public class AspectJPointcutAdvisor implements PointcutAdvisor, Ordered {
if (this == other) {
return true;
}
if (!(other instanceof AspectJPointcutAdvisor otherAdvisor)) {
if (!(other instanceof AspectJPointcutAdvisor)) {
return false;
}
AspectJPointcutAdvisor otherAdvisor = (AspectJPointcutAdvisor) other;
return this.advice.equals(otherAdvisor.advice);
}
@@ -214,7 +214,8 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
private String resolveExpression(A annotation) {
for (String attributeName : EXPRESSION_ATTRIBUTES) {
Object val = AnnotationUtils.getValue(annotation, attributeName);
if (val instanceof String str) {
if (val instanceof String) {
String str = (String) val;
if (!str.isEmpty()) {
return str;
}
@@ -101,22 +101,24 @@ public class AspectMetadata implements Serializable {
this.ajType = ajType;
switch (this.ajType.getPerClause().getKind()) {
case SINGLETON -> {
case SINGLETON:
this.perClausePointcut = Pointcut.TRUE;
}
case PERTARGET, PERTHIS -> {
return;
case PERTARGET:
case PERTHIS:
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
ajexp.setLocation(aspectClass.getName());
ajexp.setExpression(findPerClause(aspectClass));
ajexp.setPointcutDeclarationScope(aspectClass);
this.perClausePointcut = ajexp;
}
case PERTYPEWITHIN -> {
return;
case PERTYPEWITHIN:
// Works with a type pattern
this.perClausePointcut = new ComposablePointcut(new TypePatternClassFilter(findPerClause(aspectClass)));
}
default -> throw new AopConfigException(
"PerClause " + ajType.getPerClause().getKind() + " not supported by Spring AOP for " + aspectClass);
return;
default:
throw new AopConfigException(
"PerClause " + ajType.getPerClause().getKind() + " not supported by Spring AOP for " + aspectClass);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2018 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.
@@ -220,18 +220,21 @@ final class InstantiationModelAwarePointcutAdvisorImpl
}
else {
switch (aspectJAnnotation.getAnnotationType()) {
case AtPointcut, AtAround -> {
case AtPointcut:
case AtAround:
this.isBeforeAdvice = false;
this.isAfterAdvice = false;
}
case AtBefore -> {
break;
case AtBefore:
this.isBeforeAdvice = true;
this.isAfterAdvice = false;
}
case AtAfter, AtAfterReturning, AtAfterThrowing -> {
break;
case AtAfter:
case AtAfterReturning:
case AtAfterThrowing:
this.isBeforeAdvice = false;
this.isAfterAdvice = true;
}
break;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -261,36 +261,42 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
AbstractAspectJAdvice springAdvice;
switch (aspectJAnnotation.getAnnotationType()) {
case AtPointcut -> {
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
}
case AtAround -> springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
case AtBefore -> springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
case AtAfter -> springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
case AtAfterReturning -> {
case AtAround:
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
}
case AtAfterThrowing -> {
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
}
default -> throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
break;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}
// Now to configure the advice...
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -100,8 +100,8 @@ public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProx
// TODO: Consider optimization by caching the list of the aspect names
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {
if (advisor instanceof AspectJPointcutAdvisor pointcutAdvisor &&
pointcutAdvisor.getAspectName().equals(beanName)) {
if (advisor instanceof AspectJPointcutAdvisor &&
((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
return true;
}
}
@@ -143,12 +143,13 @@ public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProx
Advice advice = this.advisor.getAdvice();
StringBuilder sb = new StringBuilder(ClassUtils.getShortName(advice.getClass()));
boolean appended = false;
if (this.advisor instanceof Ordered ordered) {
sb.append(": order = ").append(ordered.getOrder());
if (this.advisor instanceof Ordered) {
sb.append(": order = ").append(((Ordered) this.advisor).getOrder());
appended = true;
}
if (advice instanceof AbstractAspectJAdvice ajAdvice) {
if (advice instanceof AbstractAspectJAdvice) {
sb.append(!appended ? ": " : ", ");
AbstractAspectJAdvice ajAdvice = (AbstractAspectJAdvice) advice;
sb.append("aspect name = ");
sb.append(ajAdvice.getAspectName());
sb.append(", declaration order = ");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2016 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.
@@ -59,7 +59,8 @@ class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element includeElement) {
if (node instanceof Element) {
Element includeElement = (Element) node;
TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
valueHolder.setSource(parserContext.extractSource(includeElement));
includePatterns.add(valueHolder);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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.
@@ -93,7 +93,7 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
private static final int POINTCUT_INDEX = 1;
private static final int ASPECT_INSTANCE_FACTORY_INDEX = 2;
private final ParseState parseState = new ParseState();
private ParseState parseState = new ParseState();
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2012 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,8 @@ class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator {
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
boolean proxyTargetClass = true;
if (node instanceof Element ele) {
if (node instanceof Element) {
Element ele = (Element) node;
if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
proxyTargetClass = Boolean.parseBoolean(ele.getAttribute(PROXY_TARGET_CLASS));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2017 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.
@@ -58,7 +58,7 @@ public class SimpleBeanFactoryAwareAspectInstanceFactory implements AspectInstan
/**
* Look up the aspect bean from the {@link BeanFactory} and return it.
* Look up the aspect bean from the {@link BeanFactory} and returns it.
* @see #setAspectBeanName
*/
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
@@ -34,8 +33,7 @@ import org.springframework.lang.Nullable;
* @since 3.2
*/
@SuppressWarnings("serial")
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor {
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
@Nullable
protected Advisor advisor;
@@ -60,27 +58,8 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
@Override
public Class<?> determineBeanType(Class<?> beanClass, String beanName) {
if (this.advisor != null && isEligible(beanClass)) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
proxyFactory.setTargetClass(beanClass);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
return proxyFactory.getProxyClass(classLoader);
}
return beanClass;
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
@@ -90,7 +69,8 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
return bean;
}
if (bean instanceof Advised advised) {
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
if (this.beforeExistingAdvisors) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -72,14 +72,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
/** Package-protected to allow direct access for efficiency. */
@SuppressWarnings("serial")
TargetSource targetSource = EMPTY_TARGET_SOURCE;
/** Whether the Advisors are already filtered for the specific target class. */
private boolean preFiltered = false;
/** The AdvisorChainFactory to use. */
@SuppressWarnings("serial")
AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
/** Cache with Method as key and advisor chain List as value. */
@@ -89,14 +87,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
* Interfaces to be implemented by the proxy. Held in List to keep the order
* of registration, to create JDK proxy with specified order of interfaces.
*/
@SuppressWarnings("serial")
private List<Class<?>> interfaces = new ArrayList<>();
/**
* List of Advisors. If an Advice is added, it will be wrapped
* in an Advisor before being added to this List.
*/
@SuppressWarnings("serial")
private List<Advisor> advisors = new ArrayList<>();
@@ -287,7 +283,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
Advisor advisor = this.advisors.remove(index);
if (advisor instanceof IntroductionAdvisor ia) {
if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
// We need to remove introduction interfaces.
for (Class<?> ifc : ia.getInterfaces()) {
removeInterface(ifc);
@@ -334,8 +331,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
if (!CollectionUtils.isEmpty(advisors)) {
for (Advisor advisor : advisors) {
if (advisor instanceof IntroductionAdvisor introductionAdvisor) {
validateIntroductionAdvisor(introductionAdvisor);
if (advisor instanceof IntroductionAdvisor) {
validateIntroductionAdvisor((IntroductionAdvisor) advisor);
}
Assert.notNull(advisor, "Advisor must not be null");
this.advisors.add(advisor);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2012 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.
@@ -52,13 +52,4 @@ public interface AopProxy {
*/
Object getProxy(@Nullable ClassLoader classLoader);
/**
* Determine the proxy class.
* @param classLoader the class loader to create the proxy class with
* (or {@code null} for the low-level proxy facility's default)
* @return the proxy class
* @since 6.0
*/
Class<?> getProxyClass(@Nullable ClassLoader classLoader);
}
@@ -33,11 +33,11 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* Utility methods for AOP proxy factories.
*
* <p>Mainly for internal use within the AOP framework.
* Mainly for internal use within the AOP framework.
*
* <p>See {@link org.springframework.aop.support.AopUtils} for a collection of
* generic AOP utility methods which do not depend on AOP framework internals.
@@ -49,6 +49,11 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class AopProxyUtils {
// JDK 17 Class.isSealed() method available?
@Nullable
private static final Method isSealedMethod = ClassUtils.getMethodIfAvailable(Class.class, "isSealed");
/**
* Obtain the singleton target object behind the given proxy, if any.
* @param candidate the (potential) proxy to check
@@ -60,10 +65,10 @@ public abstract class AopProxyUtils {
*/
@Nullable
public static Object getSingletonTarget(Object candidate) {
if (candidate instanceof Advised advised) {
TargetSource targetSource = advised.getTargetSource();
if (targetSource instanceof SingletonTargetSource singleTargetSource) {
return singleTargetSource.getTarget();
if (candidate instanceof Advised) {
TargetSource targetSource = ((Advised) candidate).getTargetSource();
if (targetSource instanceof SingletonTargetSource) {
return ((SingletonTargetSource) targetSource).getTarget();
}
}
return null;
@@ -83,8 +88,8 @@ public abstract class AopProxyUtils {
Assert.notNull(candidate, "Candidate object must not be null");
Object current = candidate;
Class<?> result = null;
while (current instanceof TargetClassAware targetClassAware) {
result = targetClassAware.getTargetClass();
while (current instanceof TargetClassAware) {
result = ((TargetClassAware) current).getTargetClass();
current = getSingletonTarget(current);
}
if (result == null) {
@@ -93,46 +98,6 @@ public abstract class AopProxyUtils {
return result;
}
/**
* Complete the set of interfaces that are typically required in a JDK dynamic
* proxy generated by Spring AOP.
* <p>Specifically, {@link SpringProxy}, {@link Advised}, and {@link DecoratingProxy}
* will be appended to the set of user-specified interfaces.
* <p>This method can be useful when registering
* {@linkplain org.springframework.aot.hint.ProxyHints proxy hints} for Spring's
* AOT support, as demonstrated in the following example which uses this method
* via a {@code static} import.
* <pre class="code">
* RuntimeHints hints = ...
* hints.proxies().registerJdkProxy(completeJdkProxyInterfaces(MyInterface.class));
* </pre>
* @param userInterfaces the set of user-specified interfaces implemented by
* the component to be proxied
* @return the complete set of interfaces that the proxy should implement
* @throws IllegalArgumentException if a supplied {@code Class} is {@code null},
* is not an {@linkplain Class#isInterface() interface}, or is a
* {@linkplain Class#isSealed() sealed} interface
* @since 6.0
* @see SpringProxy
* @see Advised
* @see DecoratingProxy
* @see org.springframework.aot.hint.RuntimeHints#proxies()
* @see org.springframework.aot.hint.ProxyHints#registerJdkProxy(Class...)
*/
public static Class<?>[] completeJdkProxyInterfaces(Class<?>... userInterfaces) {
List<Class<?>> completedInterfaces = new ArrayList<>(userInterfaces.length + 3);
for (Class<?> ifc : userInterfaces) {
Assert.notNull(ifc, "'userInterfaces' must not contain null values");
Assert.isTrue(ifc.isInterface() && !ifc.isSealed(),
() -> ifc.getName() + " must be a non-sealed interface");
completedInterfaces.add(ifc);
}
completedInterfaces.add(SpringProxy.class);
completedInterfaces.add(Advised.class);
completedInterfaces.add(DecoratingProxy.class);
return completedInterfaces.toArray(Class<?>[]::new);
}
/**
* Determine the complete set of interfaces to proxy for the given AOP configuration.
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
@@ -178,7 +143,7 @@ public abstract class AopProxyUtils {
List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
for (Class<?> ifc : specifiedInterfaces) {
// Only non-sealed interfaces are actually eligible for JDK proxying (on JDK 17)
if (!ifc.isSealed()) {
if (isSealedMethod == null || Boolean.FALSE.equals(ReflectionUtils.invokeMethod(isSealedMethod, ifc))) {
proxiedInterfaces.add(ifc);
}
}
@@ -126,6 +126,9 @@ class CglibAopProxy implements AopProxy, Serializable {
*/
public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
this.advisedDispatcher = new AdvisedDispatcher(this.advised);
}
@@ -150,20 +153,11 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
public Object getProxy() {
return buildProxy(null, false);
return getProxy(null);
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
return buildProxy(classLoader, false);
}
@Override
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return (Class<?>) buildProxy(classLoader, true);
}
private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
if (logger.isTraceEnabled()) {
logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
}
@@ -196,7 +190,6 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
@@ -210,7 +203,7 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setCallbackTypes(types);
// Generate the proxy class and create a proxy instance.
return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
@@ -223,11 +216,6 @@ class CglibAopProxy implements AopProxy, Serializable {
}
}
protected Class<?> createProxyClass(Enhancer enhancer) {
enhancer.setInterceptDuringConstruction(false);
return enhancer.createClass();
}
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
enhancer.setInterceptDuringConstruction(false);
enhancer.setCallbacks(callbacks);
@@ -387,6 +375,22 @@ class CglibAopProxy implements AopProxy, Serializable {
return false;
}
/**
* Invoke the given method with a CGLIB MethodProxy if possible, falling back
* to a plain reflection invocation in case of a fast-class generation failure.
*/
@Nullable
private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
return methodProxy.invoke(target, args);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
}
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@@ -420,9 +424,10 @@ class CglibAopProxy implements AopProxy, Serializable {
/**
* Method interceptor used for static targets with no advice chain. The call is
* passed directly back to the target. Used when the proxy needs to be exposed
* and it can't be determined that the method won't return {@code this}.
* Method interceptor used for static targets with no advice chain. The call
* is passed directly back to the target. Used when the proxy needs to be
* exposed and it can't be determined that the method won't return
* {@code this}.
*/
private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {
@@ -436,7 +441,7 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.target, method, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
}
@@ -461,7 +466,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.target, method, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
@@ -489,7 +494,7 @@ class CglibAopProxy implements AopProxy, Serializable {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object target = this.targetSource.getTarget();
try {
Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -519,7 +524,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object target = this.targetSource.getTarget();
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -690,13 +695,13 @@ class CglibAopProxy implements AopProxy, Serializable {
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty()) {
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
retVal = invokeMethod(target, method, argsToUse, methodProxy);
}
else {
// We need to create a method invocation...
@@ -738,11 +743,17 @@ class CglibAopProxy implements AopProxy, Serializable {
*/
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
@Nullable
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
Object[] arguments, @Nullable Class<?> targetClass,
List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
// Only use method proxy for public methods not derived from java.lang.Object
this.methodProxy = (isMethodProxyCompatible(method) ? methodProxy : null);
}
@Override
@@ -770,6 +781,35 @@ class CglibAopProxy implements AopProxy, Serializable {
}
}
}
/**
* Gives a marginal performance improvement versus using reflection to
* invoke the target when invoking public methods.
*/
@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.methodProxy != null) {
try {
return this.methodProxy.invoke(this.target, this.arguments);
}
catch (CodeGenerationException ex) {
logFastClassGenerationFailure(this.method);
}
}
return super.invokeJoinpoint();
}
static boolean isMethodProxyCompatible(Method method) {
return (Modifier.isPublic(method.getModifiers()) &&
method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method));
}
static void logFastClassGenerationFailure(Method method) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to generate CGLIB fast class for method: " + method);
}
}
}
@@ -919,9 +959,10 @@ class CglibAopProxy implements AopProxy, Serializable {
if (this == other) {
return true;
}
if (!(other instanceof ProxyCallbackFilter otherCallbackFilter)) {
if (!(other instanceof ProxyCallbackFilter)) {
return false;
}
ProxyCallbackFilter otherCallbackFilter = (ProxyCallbackFilter) other;
AdvisedSupport otherAdvised = otherCallbackFilter.advised;
if (this.advised.isFrozen() != otherAdvised.isFrozen()) {
return false;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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.
@@ -60,8 +60,9 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ
Boolean hasIntroductions = null;
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
@@ -89,7 +90,8 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ
}
}
}
else if (advisor instanceof IntroductionAdvisor ia) {
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
@@ -109,7 +111,8 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ
*/
private static boolean hasMatchingIntroductions(Advisor[] advisors, Class<?> actualClass) {
for (Advisor advisor : advisors) {
if (advisor instanceof IntroductionAdvisor ia) {
if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (ia.getClassFilter().matches(actualClass)) {
return true;
}
@@ -20,6 +20,7 @@ import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
import org.springframework.util.ClassUtils;
/**
@@ -53,7 +54,8 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -104,6 +104,9 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
*/
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
@@ -123,12 +126,6 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
@SuppressWarnings("deprecation")
@Override
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return Proxy.getProxyClass(classLoader, this.proxiedInterfaces);
}
/**
* Finds any {@link #equals} or {@link #hashCode} method that may be defined
* on the supplied set of interfaces.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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.
@@ -52,11 +52,6 @@ class ObjenesisCglibAopProxy extends CglibAopProxy {
}
@Override
protected Class<?> createProxyClass(Enhancer enhancer) {
return enhancer.createClass();
}
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -86,7 +86,7 @@ public class ProxyCreatorSupport extends AdvisedSupport {
/**
* Remove the given AdvisedSupportListener from this proxy configuration.
* @param listener the listener to remove
* @param listener the listener to deregister
*/
public void removeListener(AdvisedSupportListener listener) {
Assert.notNull(listener, "AdvisedSupportListener must not be null");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2016 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.
@@ -110,17 +110,6 @@ public class ProxyFactory extends ProxyCreatorSupport {
return createAopProxy().getProxy(classLoader);
}
/**
* Determine the proxy class according to the settings in this factory.
* @param classLoader the class loader to create the proxy class with
* (or {@code null} for the low-level proxy facility's default)
* @return the proxy class
* @since 6.0
*/
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxyClass(classLoader);
}
/**
* Create a new proxy for the given interface and interceptor.
@@ -274,9 +274,19 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
return this.singletonInstance.getClass();
}
}
// This might be incomplete since it potentially misses introduced interfaces
// from Advisors that will be lazily retrieved via setInterceptorNames.
return createAopProxy().getProxyClass(this.proxyClassLoader);
Class<?>[] ifcs = getProxiedInterfaces();
if (ifcs.length == 1) {
return ifcs[0];
}
else if (ifcs.length > 1) {
return createCompositeInterface(ifcs);
}
else if (this.targetName != null && this.beanFactory != null) {
return this.beanFactory.getType(this.targetName);
}
else {
return getTargetClass();
}
}
@Override
@@ -285,6 +295,19 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
}
/**
* Create a composite interface Class for the given interfaces,
* implementing the given interfaces in one single Class.
* <p>The default implementation builds a JDK proxy class for the
* given interfaces.
* @param interfaces the interfaces to merge
* @return the merged interface as Class
* @see java.lang.reflect.Proxy#getProxyClass
*/
protected Class<?> createCompositeInterface(Class<?>[] interfaces) {
return ClassUtils.createCompositeInterface(interfaces, this.proxyClassLoader);
}
/**
* Return the singleton instance of this class's proxy object,
* lazily creating it if it hasn't been created already.
@@ -414,11 +437,12 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
// Materialize interceptor chain from bean names.
for (String name : this.interceptorNames) {
if (name.endsWith(GLOBAL_SUFFIX)) {
if (!(this.beanFactory instanceof ListableBeanFactory lbf)) {
if (!(this.beanFactory instanceof ListableBeanFactory)) {
throw new AopConfigException(
"Can only use global advisors or interceptors with a ListableBeanFactory");
}
addGlobalAdvisors(lbf, name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
addGlobalAdvisors((ListableBeanFactory) this.beanFactory,
name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
}
else {
@@ -452,16 +476,17 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
Advisor[] advisors = getAdvisors();
List<Advisor> freshAdvisors = new ArrayList<>(advisors.length);
for (Advisor advisor : advisors) {
if (advisor instanceof PrototypePlaceholderAdvisor ppa) {
if (advisor instanceof PrototypePlaceholderAdvisor) {
PrototypePlaceholderAdvisor pa = (PrototypePlaceholderAdvisor) advisor;
if (logger.isDebugEnabled()) {
logger.debug("Refreshing bean named '" + ppa.getBeanName() + "'");
logger.debug("Refreshing bean named '" + pa.getBeanName() + "'");
}
// Replace the placeholder with a fresh prototype instance resulting from a getBean lookup
if (this.beanFactory == null) {
throw new IllegalStateException("No BeanFactory available anymore (probably due to " +
"serialization) - cannot resolve prototype advisor '" + ppa.getBeanName() + "'");
"serialization) - cannot resolve prototype advisor '" + pa.getBeanName() + "'");
}
Object bean = this.beanFactory.getBean(ppa.getBeanName());
Object bean = this.beanFactory.getBean(pa.getBeanName());
Advisor refreshedAdvisor = namedBeanToAdvisor(bean);
freshAdvisors.add(refreshedAdvisor);
}
@@ -533,7 +558,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
logger.debug("Refreshing target with name '" + this.targetName + "'");
}
Object target = this.beanFactory.getBean(this.targetName);
return (target instanceof TargetSource targetSource ? targetSource : new SingletonTargetSource(target));
return (target instanceof TargetSource ? (TargetSource) target : new SingletonTargetSource(target));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2019 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.
@@ -165,9 +165,11 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher dm) {
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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.
@@ -58,9 +58,10 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se
if (adviceObject instanceof Advisor) {
return (Advisor) adviceObject;
}
if (!(adviceObject instanceof Advice advice)) {
if (!(adviceObject instanceof Advice)) {
throw new UnknownAdviceTypeException(adviceObject);
}
Advice advice = (Advice) adviceObject;
if (advice instanceof MethodInterceptor) {
// So well-known it doesn't even need an adapter.
return new DefaultPointcutAdvisor(advice);
@@ -38,7 +38,6 @@ import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ProxyProcessorSupport;
import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry;
import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
@@ -232,30 +231,6 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
return this.proxyTypes.get(cacheKey);
}
@Override
public Class<?> determineBeanType(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName);
Class<?> proxyType = this.proxyTypes.get(cacheKey);
if (proxyType == null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
}
else {
targetSource = EmptyTargetSource.forClass(beanClass);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
proxyType = createProxyClass(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxyType);
}
}
return (proxyType != null ? proxyType : beanClass);
}
@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) {
@@ -461,18 +436,6 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
}
private Class<?> createProxyClass(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
return (Class<?>) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true);
}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
@@ -514,7 +477,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
return proxyFactory.getProxy(classLoader);
}
/**
@@ -25,6 +25,7 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* Auto proxy creator that identifies beans to proxy via a list of names.
@@ -68,7 +69,7 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
Assert.notEmpty(beanNames, "'beanNames' must not be empty");
this.beanNames = new ArrayList<>(beanNames.length);
for (String mappedName : beanNames) {
this.beanNames.add(mappedName.strip());
this.beanNames.add(StringUtils.trimWhitespace(mappedName));
}
}
@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -33,15 +34,14 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.support.TaskExecutorAdapter;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.function.SingletonSupplier;
/**
@@ -57,7 +57,6 @@ import org.springframework.util.function.SingletonSupplier;
* @author Chris Beams
* @author Juergen Hoeller
* @author Stephane Nicoll
* @author He Bo
* @since 3.1.2
*/
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
@@ -82,8 +81,6 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
@Nullable
private BeanFactory beanFactory;
@Nullable
private StringValueResolver embeddedValueResolver;
/**
* Create a new instance with a default {@link AsyncUncaughtExceptionHandler}.
@@ -154,14 +151,12 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
this.embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory);
}
}
/**
* Determine the specific executor to use when executing the given method.
* Should preferably return an {@link AsyncListenableTaskExecutor} implementation.
* @return the executor to use (or {@code null}, but just if no default executor is available)
*/
@Nullable
@@ -170,9 +165,6 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
if (executor == null) {
Executor targetExecutor;
String qualifier = getExecutorQualifier(method);
if (this.embeddedValueResolver != null && StringUtils.hasLength(qualifier)) {
qualifier = this.embeddedValueResolver.resolveStringValue(qualifier);
}
if (StringUtils.hasLength(qualifier)) {
targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
}
@@ -182,8 +174,8 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
if (targetExecutor == null) {
return null;
}
executor = (targetExecutor instanceof AsyncTaskExecutor ?
(AsyncTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
this.executors.put(method, executor);
}
return executor;
@@ -192,7 +184,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
/**
* Return the qualifier or bean name of the executor to be used when executing the
* given async method, typically specified in the form of an annotation attribute.
* <p>Returning an empty string or {@code null} indicates that no specific executor has
* Returning an empty string or {@code null} indicates that no specific executor has
* been specified and that the {@linkplain #setExecutor(Executor) default executor}
* should be used.
* @param method the method to inspect for executor qualifier metadata
@@ -221,7 +213,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
/**
* Retrieve or build a default executor for this advice instance.
* <p>An executor returned from here will be cached for further use.
* An executor returned from here will be cached for further use.
* <p>The default implementation searches for a unique {@link TaskExecutor} bean
* in the context, or for an {@link Executor} bean named "taskExecutor" otherwise.
* If neither of the two is resolvable, this implementation will return {@code null}.
@@ -279,25 +271,27 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
* @return the execution result (potentially a corresponding {@link Future} handle)
*/
@Nullable
@SuppressWarnings("deprecation")
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return executor.submitCompletable(task);
return CompletableFuture.supplyAsync(() -> {
try {
return task.call();
}
catch (Throwable ex) {
throw new CompletionException(ex);
}
}, executor);
}
else if (org.springframework.util.concurrent.ListenableFuture.class.isAssignableFrom(returnType)) {
return ((org.springframework.core.task.AsyncListenableTaskExecutor) executor).submitListenable(task);
else if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
}
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else if (void.class == returnType) {
else {
executor.submit(task);
return null;
}
else {
throw new IllegalArgumentException(
"Invalid return type for async method (only Future and void supported): " + returnType);
}
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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,7 +47,7 @@ import org.springframework.util.ClassUtils;
* target method needs to implement the same signature, it will have to return
* a temporary Future handle that just passes the return value through
* (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult}
* or EJB's {@code jakarta.ejb.AsyncResult}).
* or EJB 3.1's {@code javax.ejb.AsyncResult}).
*
* <p>When the return type is {@code java.util.concurrent.Future}, any exception thrown
* during the execution can be accessed and managed by the caller. With {@code void}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2020 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.
@@ -299,7 +299,7 @@ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor {
Object target = methodInvocation.getThis();
Assert.state(target != null, "Target must not be null");
StringBuilder output = new StringBuilder();
StringBuffer output = new StringBuffer();
while (matcher.find()) {
String match = matcher.group();
if (PLACEHOLDER_METHOD_NAME.equals(match)) {
@@ -341,15 +341,15 @@ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor {
/**
* Adds the {@code String} representation of the method return value
* to the supplied {@code StringBuilder}. Correctly handles
* to the supplied {@code StringBuffer}. Correctly handles
* {@code null} and {@code void} results.
* @param methodInvocation the {@code MethodInvocation} that returned the value
* @param matcher the {@code Matcher} containing the matched placeholder
* @param output the {@code StringBuilder} to write output to
* @param output the {@code StringBuffer} to write output to
* @param returnValue the value returned by the method invocation.
*/
private void appendReturnValue(
MethodInvocation methodInvocation, Matcher matcher, StringBuilder output, @Nullable Object returnValue) {
MethodInvocation methodInvocation, Matcher matcher, StringBuffer output, @Nullable Object returnValue) {
if (methodInvocation.getMethod().getReturnType() == void.class) {
matcher.appendReplacement(output, "void");
@@ -370,9 +370,9 @@ public class CustomizableTraceInterceptor extends AbstractTraceInterceptor {
* @param methodInvocation the {@code MethodInvocation} being logged.
* Arguments will be retrieved from the corresponding {@code Method}.
* @param matcher the {@code Matcher} containing the state of the output
* @param output the {@code StringBuilder} containing the output
* @param output the {@code StringBuffer} containing the output
*/
private void appendArgumentTypes(MethodInvocation methodInvocation, Matcher matcher, StringBuilder output) {
private void appendArgumentTypes(MethodInvocation methodInvocation, Matcher matcher, StringBuffer output) {
Class<?>[] argumentTypes = methodInvocation.getMethod().getParameterTypes();
String[] argumentTypeShortNames = new String[argumentTypes.length];
for (int i = 0; i < argumentTypeShortNames.length; i++) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2020 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.
@@ -68,9 +68,10 @@ public abstract class ExposeBeanNameAdvisors {
* @throws IllegalStateException if the bean name has not been exposed
*/
public static String getBeanName(MethodInvocation mi) throws IllegalStateException {
if (!(mi instanceof ProxyMethodInvocation pmi)) {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalArgumentException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
String beanName = (String) pmi.getUserAttribute(BEAN_NAME_ATTRIBUTE);
if (beanName == null) {
throw new IllegalStateException("Cannot get bean name; not set on MethodInvocation: " + mi);
@@ -112,9 +113,10 @@ public abstract class ExposeBeanNameAdvisors {
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation pmi)) {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
pmi.setUserAttribute(BEAN_NAME_ATTRIBUTE, this.beanName);
return mi.proceed();
}
@@ -136,9 +138,10 @@ public abstract class ExposeBeanNameAdvisors {
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation pmi)) {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
pmi.setUserAttribute(BEAN_NAME_ATTRIBUTE, this.beanName);
return super.invoke(mi);
}
@@ -0,0 +1,144 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.interceptor;
import com.jamonapi.MonKey;
import com.jamonapi.MonKeyImp;
import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;
import com.jamonapi.utils.Misc;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
/**
* Performance monitor interceptor that uses <b>JAMon</b> library to perform the
* performance measurement on the intercepted method and output the stats.
* In addition, it tracks/counts exceptions thrown by the intercepted method.
* The stack traces can be viewed in the JAMon web application.
*
* <p>This code is inspired by Thierry Templier's blog.
*
* @author Dmitriy Kopylenko
* @author Juergen Hoeller
* @author Rob Harrop
* @author Steve Souza
* @since 1.1.3
* @see com.jamonapi.MonitorFactory
* @see PerformanceMonitorInterceptor
*/
@SuppressWarnings("serial")
public class JamonPerformanceMonitorInterceptor extends AbstractMonitoringInterceptor {
private boolean trackAllInvocations = false;
/**
* Create a new JamonPerformanceMonitorInterceptor with a static logger.
*/
public JamonPerformanceMonitorInterceptor() {
}
/**
* Create a new JamonPerformanceMonitorInterceptor with a dynamic or static logger,
* according to the given flag.
* @param useDynamicLogger whether to use a dynamic logger or a static logger
* @see #setUseDynamicLogger
*/
public JamonPerformanceMonitorInterceptor(boolean useDynamicLogger) {
setUseDynamicLogger(useDynamicLogger);
}
/**
* Create a new JamonPerformanceMonitorInterceptor with a dynamic or static logger,
* according to the given flag.
* @param useDynamicLogger whether to use a dynamic logger or a static logger
* @param trackAllInvocations whether to track all invocations that go through
* this interceptor, or just invocations with trace logging enabled
* @see #setUseDynamicLogger
*/
public JamonPerformanceMonitorInterceptor(boolean useDynamicLogger, boolean trackAllInvocations) {
setUseDynamicLogger(useDynamicLogger);
setTrackAllInvocations(trackAllInvocations);
}
/**
* Set whether to track all invocations that go through this interceptor,
* or just invocations with trace logging enabled.
* <p>Default is "false": Only invocations with trace logging enabled will
* be monitored. Specify "true" to let JAMon track all invocations,
* gathering statistics even when trace logging is disabled.
*/
public void setTrackAllInvocations(boolean trackAllInvocations) {
this.trackAllInvocations = trackAllInvocations;
}
/**
* Always applies the interceptor if the "trackAllInvocations" flag has been set;
* else just kicks in if the log is enabled.
* @see #setTrackAllInvocations
* @see #isLogEnabled
*/
@Override
protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger) {
return (this.trackAllInvocations || isLogEnabled(logger));
}
/**
* Wraps the invocation with a JAMon Monitor and writes the current
* performance statistics to the log (if enabled).
* @see com.jamonapi.MonitorFactory#start
* @see com.jamonapi.Monitor#stop
*/
@Override
protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable {
String name = createInvocationTraceName(invocation);
MonKey key = new MonKeyImp(name, name, "ms.");
Monitor monitor = MonitorFactory.start(key);
try {
return invocation.proceed();
}
catch (Throwable ex) {
trackException(key, ex);
throw ex;
}
finally {
monitor.stop();
if (!this.trackAllInvocations || isLogEnabled(logger)) {
writeToLog(logger, "JAMon performance statistics for method [" + name + "]:\n" + monitor);
}
}
}
/**
* Count the thrown exception and put the stack trace in the details portion of the key.
* This will allow the stack trace to be viewed in the JAMon web application.
*/
protected void trackException(MonKey key, Throwable ex) {
String stackTrace = "stackTrace=" + Misc.getExceptionTrace(ex);
key.setDetails(stackTrace);
// Specific exception counter. Example: java.lang.RuntimeException
MonitorFactory.add(new MonKeyImp(ex.getClass().getName(), stackTrace, "Exception"), 1);
// General exception counter which is a total for all exceptions thrown
MonitorFactory.add(new MonKeyImp(MonitorFactory.EXCEPTIONS_LABEL, stackTrace, "Exception"), 1);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2017 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,6 +31,7 @@ import org.springframework.util.StopWatch;
* @author Dmitriy Kopylenko
* @author Rob Harrop
* @see org.springframework.util.StopWatch
* @see JamonPerformanceMonitorInterceptor
*/
@SuppressWarnings("serial")
public class PerformanceMonitorInterceptor extends AbstractMonitoringInterceptor {
@@ -1,171 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.scope;
import java.lang.reflect.Executable;
import java.util.function.Predicate;
import javax.lang.model.element.Modifier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.lang.Nullable;
/**
* {@link BeanRegistrationAotProcessor} for {@link ScopedProxyFactoryBean}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 6.0
*/
class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private static final Log logger = LogFactory.getLog(ScopedProxyBeanRegistrationAotProcessor.class);
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanType = registeredBean.getBeanType().toClass();
if (beanType.equals(ScopedProxyFactoryBean.class)) {
String targetBeanName = getTargetBeanName(registeredBean.getMergedBeanDefinition());
BeanDefinition targetBeanDefinition =
getTargetBeanDefinition(registeredBean.getBeanFactory(), targetBeanName);
if (targetBeanDefinition == null) {
logger.warn("Could not handle " + ScopedProxyFactoryBean.class.getSimpleName() +
": no target bean definition found with name " + targetBeanName);
return null;
}
return BeanRegistrationAotContribution.withCustomCodeFragments(codeFragments ->
new ScopedProxyBeanRegistrationCodeFragments(codeFragments, registeredBean,
targetBeanName, targetBeanDefinition));
}
return null;
}
@Nullable
private String getTargetBeanName(BeanDefinition beanDefinition) {
Object value = beanDefinition.getPropertyValues().get("targetBeanName");
return (value instanceof String ? (String) value : null);
}
@Nullable
private BeanDefinition getTargetBeanDefinition(ConfigurableBeanFactory beanFactory,
@Nullable String targetBeanName) {
if (targetBeanName != null && beanFactory.containsBean(targetBeanName)) {
return beanFactory.getMergedBeanDefinition(targetBeanName);
}
return null;
}
private static class ScopedProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean";
private final RegisteredBean registeredBean;
private final String targetBeanName;
private final BeanDefinition targetBeanDefinition;
ScopedProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments delegate,
RegisteredBean registeredBean, String targetBeanName, BeanDefinition targetBeanDefinition) {
super(delegate);
this.registeredBean = registeredBean;
this.targetBeanName = targetBeanName;
this.targetBeanDefinition = targetBeanDefinition;
}
@Override
public ClassName getTarget(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
return ClassName.get(this.targetBeanDefinition.getResolvableType().toClass());
}
@Override
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
return super.generateNewBeanDefinitionCode(generationContext,
this.targetBeanDefinition.getResolvableType(), beanRegistrationCode);
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(
beanDefinition);
processedBeanDefinition
.setTargetType(this.targetBeanDefinition.getResolvableType());
processedBeanDefinition.getPropertyValues()
.removePropertyValue("targetBeanName");
return super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, processedBeanDefinition, attributeFilter);
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods()
.add("getScopedProxyInstance", method -> {
method.addJavadoc(
"Create the scoped proxy bean instance for '$L'.",
this.registeredBean.getBeanName());
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
method.returns(ScopedProxyFactoryBean.class);
method.addParameter(RegisteredBean.class,
REGISTERED_BEAN_PARAMETER_NAME);
method.addStatement("$T factory = new $T()",
ScopedProxyFactoryBean.class,
ScopedProxyFactoryBean.class);
method.addStatement("factory.setTargetBeanName($S)",
this.targetBeanName);
method.addStatement(
"factory.setBeanFactory($L.getBeanFactory())",
REGISTERED_BEAN_PARAMETER_NAME);
method.addStatement("return factory");
});
return CodeBlock.of("$T.of($L)", InstanceSupplier.class,
generatedMethod.toMethodReference().toCodeBlock());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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.
@@ -85,9 +85,11 @@ public class ScopedProxyFactoryBean extends ProxyConfig
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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.
@@ -80,8 +80,8 @@ public abstract class ScopedProxyUtils {
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition abd) {
proxyDefinition.copyQualifiersFrom(abd);
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// The target bean should be ignored in favor of the scoped proxy.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2017 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.
@@ -52,8 +52,8 @@ public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordere
return this.order;
}
Advice advice = getAdvice();
if (advice instanceof Ordered ordered) {
return ordered.getOrder();
if (advice instanceof Ordered) {
return ((Ordered) advice).getOrder();
}
return Ordered.LOWEST_PRECEDENCE;
}
@@ -69,9 +69,10 @@ public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordere
if (this == other) {
return true;
}
if (!(other instanceof PointcutAdvisor otherAdvisor)) {
if (!(other instanceof PointcutAdvisor)) {
return false;
}
PointcutAdvisor otherAdvisor = (PointcutAdvisor) other;
return (ObjectUtils.nullSafeEquals(getAdvice(), otherAdvisor.getAdvice()) &&
ObjectUtils.nullSafeEquals(getPointcut(), otherAdvisor.getPointcut()));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2018 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,6 +24,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Abstract base regular expression pointcut bean. JavaBean properties are:
@@ -80,7 +81,7 @@ public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPo
Assert.notEmpty(patterns, "'patterns' must not be empty");
this.patterns = new String[patterns.length];
for (int i = 0; i < patterns.length; i++) {
this.patterns[i] = patterns[i].strip();
this.patterns[i] = StringUtils.trimWhitespace(patterns[i]);
}
initPatternRepresentation(this.patterns);
}
@@ -110,7 +111,7 @@ public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPo
Assert.notEmpty(excludedPatterns, "'excludedPatterns' must not be empty");
this.excludedPatterns = new String[excludedPatterns.length];
for (int i = 0; i < excludedPatterns.length; i++) {
this.excludedPatterns[i] = excludedPatterns[i].strip();
this.excludedPatterns[i] = StringUtils.trimWhitespace(excludedPatterns[i]);
}
initExcludedPatternRepresentation(this.excludedPatterns);
}
@@ -199,9 +200,10 @@ public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPo
if (this == other) {
return true;
}
if (!(other instanceof AbstractRegexpMethodPointcut otherPointcut)) {
if (!(other instanceof AbstractRegexpMethodPointcut)) {
return false;
}
AbstractRegexpMethodPointcut otherPointcut = (AbstractRegexpMethodPointcut) other;
return (Arrays.equals(this.patterns, otherPointcut.patterns) &&
Arrays.equals(this.excludedPatterns, otherPointcut.excludedPatterns));
}
@@ -284,7 +284,8 @@ public abstract class AopUtils {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor pca) {
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2019 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,10 +46,8 @@ public class ComposablePointcut implements Pointcut, Serializable {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = -2743223737633663832L;
@SuppressWarnings("serial")
private ClassFilter classFilter;
@SuppressWarnings("serial")
private MethodMatcher methodMatcher;
@@ -190,9 +188,10 @@ public class ComposablePointcut implements Pointcut, Serializable {
if (this == other) {
return true;
}
if (!(other instanceof ComposablePointcut otherPointcut)) {
if (!(other instanceof ComposablePointcut)) {
return false;
}
ComposablePointcut otherPointcut = (ComposablePointcut) other;
return (this.classFilter.equals(otherPointcut.classFilter) &&
this.methodMatcher.equals(otherPointcut.methodMatcher));
}
@@ -128,9 +128,10 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
if (this == other) {
return true;
}
if (!(other instanceof ControlFlowPointcut that)) {
if (!(other instanceof ControlFlowPointcut)) {
return false;
}
ControlFlowPointcut that = (ControlFlowPointcut) other;
return (this.clazz.equals(that.clazz)) && ObjectUtils.nullSafeEquals(this.methodName, that.methodName);
}
@@ -155,9 +155,10 @@ public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFil
if (this == other) {
return true;
}
if (!(other instanceof DefaultIntroductionAdvisor otherAdvisor)) {
if (!(other instanceof DefaultIntroductionAdvisor)) {
return false;
}
DefaultIntroductionAdvisor otherAdvisor = (DefaultIntroductionAdvisor) other;
return (this.advice.equals(otherAdvisor.advice) && this.interfaces.equals(otherAdvisor.interfaces));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2017 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.
@@ -61,9 +61,9 @@ public class DelegatePerTargetObjectIntroductionInterceptor extends Introduction
*/
private final Map<Object, Object> delegateMap = new WeakHashMap<>();
private final Class<?> defaultImplType;
private Class<?> defaultImplType;
private final Class<?> interfaceType;
private Class<?> interfaceType;
public DelegatePerTargetObjectIntroductionInterceptor(Class<?> defaultImplType, Class<?> interfaceType) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 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.
@@ -94,8 +94,8 @@ public abstract class MethodMatchers {
*/
public static boolean matches(MethodMatcher mm, Method method, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(mm, "MethodMatcher must not be null");
return (mm instanceof IntroductionAwareMethodMatcher iamm ?
iamm.matches(method, targetClass, hasIntroductions) :
return (mm instanceof IntroductionAwareMethodMatcher ?
((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) :
mm.matches(method, targetClass));
}
@@ -146,9 +146,10 @@ public abstract class MethodMatchers {
if (this == other) {
return true;
}
if (!(other instanceof UnionMethodMatcher that)) {
if (!(other instanceof UnionMethodMatcher)) {
return false;
}
UnionMethodMatcher that = (UnionMethodMatcher) other;
return (this.mm1.equals(that.mm1) && this.mm2.equals(that.mm2));
}
@@ -222,7 +223,8 @@ public abstract class MethodMatchers {
}
ClassFilter otherCf1 = ClassFilter.TRUE;
ClassFilter otherCf2 = ClassFilter.TRUE;
if (other instanceof ClassFilterAwareUnionMethodMatcher cfa) {
if (other instanceof ClassFilterAwareUnionMethodMatcher) {
ClassFilterAwareUnionMethodMatcher cfa = (ClassFilterAwareUnionMethodMatcher) other;
otherCf1 = cfa.cf1;
otherCf2 = cfa.cf2;
}
@@ -310,9 +312,10 @@ public abstract class MethodMatchers {
if (this == other) {
return true;
}
if (!(other instanceof IntersectionMethodMatcher that)) {
if (!(other instanceof IntersectionMethodMatcher)) {
return false;
}
IntersectionMethodMatcher that = (IntersectionMethodMatcher) other;
return (this.mm1.equals(that.mm1) && this.mm2.equals(that.mm2));
}
@@ -71,9 +71,10 @@ public class AnnotationClassFilter implements ClassFilter {
if (this == other) {
return true;
}
if (!(other instanceof AnnotationClassFilter otherCf)) {
if (!(other instanceof AnnotationClassFilter)) {
return false;
}
AnnotationClassFilter otherCf = (AnnotationClassFilter) other;
return (this.annotationType.equals(otherCf.annotationType) && this.checkInherited == otherCf.checkInherited);
}
@@ -124,9 +124,10 @@ public class AnnotationMatchingPointcut implements Pointcut {
if (this == other) {
return true;
}
if (!(other instanceof AnnotationMatchingPointcut otherPointcut)) {
if (!(other instanceof AnnotationMatchingPointcut)) {
return false;
}
AnnotationMatchingPointcut otherPointcut = (AnnotationMatchingPointcut) other;
return (this.classFilter.equals(otherPointcut.classFilter) &&
this.methodMatcher.equals(otherPointcut.methodMatcher));
}
@@ -187,9 +188,10 @@ public class AnnotationMatchingPointcut implements Pointcut {
if (this == obj) {
return true;
}
if (!(obj instanceof AnnotationCandidateClassFilter that)) {
if (!(obj instanceof AnnotationCandidateClassFilter)) {
return false;
}
AnnotationCandidateClassFilter that = (AnnotationCandidateClassFilter) obj;
return this.annotationType.equals(that.annotationType);
}
@@ -93,9 +93,10 @@ public class AnnotationMethodMatcher extends StaticMethodMatcher {
if (this == other) {
return true;
}
if (!(other instanceof AnnotationMethodMatcher otherMm)) {
if (!(other instanceof AnnotationMethodMatcher)) {
return false;
}
AnnotationMethodMatcher otherMm = (AnnotationMethodMatcher) other;
return (this.annotationType.equals(otherMm.annotationType) && this.checkInherited == otherMm.checkInherited);
}
@@ -54,7 +54,7 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
/** Logger available to subclasses. */
protected final transient Log logger = LogFactory.getLog(getClass());
protected final Log logger = LogFactory.getLog(getClass());
/** Name of the target bean we will create on each invocation. */
private String targetBeanName;
@@ -66,7 +66,6 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
* BeanFactory that owns this TargetSource. We need to hold onto this
* reference so that we can create new prototype instances as necessary.
*/
@SuppressWarnings("serial")
private BeanFactory beanFactory;

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