diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
new file mode 100644
index 000000000000..405a2b306592
--- /dev/null
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -0,0 +1,10 @@
+name: "Validate Gradle Wrapper"
+on: [push, pull_request]
+
+jobs:
+ validation:
+ name: "Validation"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: gradle/wrapper-validation-action@v1
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e0f58be1e0ff..2295a0928826 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,8 +7,8 @@ First off, thank you for taking the time to contribute! :+1: :tada:
* [Code of Conduct](#code-of-conduct)
* [How to Contribute](#how-to-contribute)
* [Discuss](#discuss)
- * [Create a Ticket](#create-a-ticket)
- * [Ticket Lifecycle](#ticket-lifecycle)
+ * [Create an Issue](#create-an-issue)
+ * [Issue Lifecycle](#issue-lifecycle)
* [Submit a Pull Request](#submit-a-pull-request)
* [Build from Source](#build-from-source)
* [Source Code Style](#source-code-style)
@@ -24,70 +24,71 @@ Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
#### Discuss
-If you have a question, check StackOverflow using
-[this list of tags](https://spring.io/questions), organized by Spring project.
-Find an existing discussion or start a new one if necessary.
+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.
-If you suspect an issue, perform a search in the
-[GitHub issue tracker](https://github.com/spring-projects/spring-framework/issues), using a few different keywords.
-When you find related issues and discussions, prior or current, it helps you to learn and
-it helps us to make a decision.
+If you believe there is an issue, search through
+[existing issues](https://github.com/spring-projects/spring-framework/issues) trying a
+few different ways to find discussions, past or current, that are related to the issue.
+Reading those discussions helps you to learn about the issue, and helps us to make a
+decision.
-#### Create a Ticket
-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.
+#### Create an Issue
-Before you create a ticket, please take the time to [research first](#discuss).
+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 [discuss and research](#discuss) first.
-If creating a ticket after a discussion on StackOverflow, please provide a self-sufficient description in the ticket, independent of the details on StackOverflow. We understand this is extra work but the issue tracker is an important place of record for design discussions and decisions that can often be referenced long after the fix version, for example to revisit decisions, to understand the origin of a feature, and so on.
+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.
-When ready create a ticket in the [GitHub issue tracker](https://github.com/spring-projects/spring-framework/issues).
+Once you're ready, create an issue on
+[GitHub](https://github.com/spring-projects/spring-framework/issues).
-#### Ticket Lifecycle
+#### Issue Lifecycle
When an issue is first created, it is flagged `waiting-for-triage` waiting for a team
-member to triage it. Within a day or two, the issue will then be reviewed and the team
-may ask for further information if needed. Based on the findings, the issue is either
-assigned a fix version or declined.
+member to triage it. Once the issue has been reviewed, the team may ask for further
+information if needed, and based on the findings, the issue is either assigned a target
+milestone or is closed with a specific status.
-When a fix is ready, the issue is closed and may still be re-opened. Once a fix is
-released, the issue can't be reopened. If necessary, you will need to create a new,
-related ticket with a fresh description.
+When a fix is ready, the issue is closed and may still be re-opened until the fix is
+released. After that the issue will typically no longer be reopened. In rare cases if the
+issue was not at all fixed, the issue may be re-opened. In most cases however any
+follow-up reports will need to be created as new issues with a fresh description.
#### Submit a Pull Request
-You can contribute a source code change by submitting a pull request.
-
1. If you have not previously done so, please sign the
-[Contributor License Agreement](https://cla.pivotal.io/sign/spring). You will also be reminded
-automatically when you submit a pull request.
+[Contributor License Agreement](https://cla.pivotal.io/sign/spring). You will be reminded
+automatically when you submit the PR.
-1. Should you create a ticket first? The answer is no. Just create the pull request and use
-the description to provide context and motivation, as you would for an issue. If you want
-to start a discussion first or have already created an issue, once a pull request is created,
-we will close the issue as superseded by the pull request, and the discussion of the issue
-will continue under the pull request.
+1. Should you create an issue first? No, just create the pull request and use the
+description to provide context and motivation, as you would for an issue. If you want
+to start a discussion first or have already created an issue, once a pull request is
+created, we will close the issue as superseded by the pull request, and the discussion
+about the issue will continue under the pull request.
1. Always check out the `master` branch and submit pull requests against it
(for target version see [settings.gradle](settings.gradle)).
Backports to prior versions will be considered on a case-by-case basis and reflected as
the fix version in the issue tracker.
-1. Use short branch names, preferably based on the GitHub issue (e.g. `22276`), or
-otherwise using succinct, lower-case, dash (-) delimited names, such as `fix-warnings`.
-
1. Choose the granularity of your commits consciously and squash commits that represent
multiple edits or corrections of the same logical change. See
[Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History)
-for an overview of streamlining commit history.
+for an overview of streamlining the commit history.
1. Format commit messages using 55 characters for the subject line, 72 characters per line
for the description, followed by the issue fixed, e.g. `Closes gh-22276`. See the
[Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines)
-for best practices around commit messages and use `git log` to see some examples.
+for best practices around commit messages, and use `git log` to see some examples.
-1. List the GitHub issue number in the PR description.
+1. If there is a prior issue, reference the GitHub issue number in the description of the
+pull request.
If accepted, your contribution may be heavily modified as needed prior to merging.
You will likely retain author attribution for your Git commits granted that the bulk of
@@ -115,15 +116,15 @@ source code into your IDE.
The wiki pages
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
-defines the source file coding standards we use along with some IDEA editor settings we customize.
+define the source file coding standards we use along with some IDEA editor settings we customize.
### Reference Docs
-The reference documentation is in the [src/docs/asciidoc](src/docs/asciidoc) directory and, 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, use `./gradlew asciidoctor` and then browse the result under
+When making changes locally, execute `./gradlew asciidoctor` and then browse the result under
`build/asciidoc/html5/index.html`.
Asciidoctor also supports live editing. For more details read
diff --git a/README.md b/README.md
index 737a4b6d3ba2..53a82b78e8b1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Spring Framework [](https://build.spring.io/browse/SPR)
+# Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.2.x?groups=Build")
-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 is often referred to simply as "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".
Spring provides everything required beyond the Java programming language for creating enterprise applications for a wide range of scenarios and architectures. Please read the [Overview](https://docs.spring.io/spring/docs/current/spring-framework-reference/overview.html#spring-introduction) section as reference for a more complete introduction.
diff --git a/SECURITY.md b/SECURITY.md
index 8ed0ff412377..038a36b56539 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -8,4 +8,4 @@ wiki page.
## Reporting a Vulnerability
-Please see https://pivotal.io/security.
+Please see https://spring.io/security-policy.
diff --git a/build.gradle b/build.gradle
index 3b28c31172c1..524e93f0a66d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,35 +1,18 @@
-buildscript {
- dependencies {
- classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.16'
- classpath 'io.spring.asciidoctor:spring-asciidoctor-extensions:0.1.3.RELEASE'
- }
-}
-
plugins {
- id 'io.spring.dependency-management' version '1.0.8.RELEASE' apply false
- id 'org.jetbrains.kotlin.jvm' version '1.3.50' apply false
- id 'org.jetbrains.dokka' version '0.9.18' apply false
- id 'org.asciidoctor.convert' version '1.5.8'
- id 'io.spring.nohttp' version '0.0.3.RELEASE'
- id 'de.undercouch.download' version '4.0.0'
- id 'com.gradle.build-scan' version '2.4.2'
- id "com.jfrog.artifactory" version '4.9.8' apply false
- id "io.freefair.aspectj" version "4.1.1" apply false
- id "com.github.ben-manes.versions" version "0.24.0"
-}
-
-if (System.getenv('GRADLE_ENTERPRISE_URL')) {
- apply from: "$rootDir/gradle/build-scan-user-data.gradle"
- buildScan {
- captureTaskInputFiles = true
- obfuscation {
- ipAddresses { addresses -> addresses.collect { address -> '0.0.0.0'} }
- }
- publishAlways()
- server = System.getenv('GRADLE_ENTERPRISE_URL')
- }
+ id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
+ id 'io.spring.ge.conventions' version '0.0.7'
+ id 'io.spring.nohttp' version '0.0.5.RELEASE'
+ id "io.freefair.aspectj" version '4.1.6' apply false
+ id 'org.jetbrains.dokka' version '0.10.1' apply false
+ id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
+ id 'org.asciidoctor.jvm.convert' version '2.4.0'
+ id 'org.asciidoctor.jvm.pdf' version '2.4.0'
+ id "com.github.ben-manes.versions" version '0.28.0'
+ id 'com.gradle.build-scan' version '3.2'
+ id 'de.undercouch.download' version '4.1.1'
}
+apply from: "$rootDir/gradle/build-scan-user-data.gradle"
ext {
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
javaProjects = subprojects - project(":framework-bom")
@@ -43,32 +26,31 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
- mavenBom "com.fasterxml.jackson:jackson-bom:2.10.0"
- mavenBom "io.netty:netty-bom:4.1.43.Final"
- mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR1"
- mavenBom "io.rsocket:rsocket-bom:1.0.0-RC5"
- mavenBom "org.eclipse.jetty:jetty-bom:9.4.21.v20190926"
- mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.50"
- mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.3.2"
- mavenBom "org.junit:junit-bom:5.5.2"
+ mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5"
+ mavenBom "io.netty:netty-bom:4.1.51.Final"
+ mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR25"
+ mavenBom "io.rsocket:rsocket-bom:1.0.4"
+ mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723"
+ mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
+ mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.3.5"
+ mavenBom "org.junit:junit-bom:5.6.3"
}
dependencies {
- dependencySet(group: 'org.apache.logging.log4j', version: '2.12.1') {
+ dependencySet(group: 'org.apache.logging.log4j', version: '2.17.1') {
entry 'log4j-api'
entry 'log4j-core'
- entry 'log4j-slf4j-impl'
entry 'log4j-jul'
+ entry 'log4j-slf4j-impl'
}
- dependency "org.slf4j:slf4j-api:1.7.28"
-
+ dependency "org.slf4j:slf4j-api:1.7.32"
dependency "com.google.code.findbugs:jsr305:3.0.2"
- dependencySet(group: 'org.aspectj', version: '1.9.4') {
+ dependencySet(group: 'org.aspectj', version: '1.9.7') {
entry 'aspectjrt'
entry 'aspectjtools'
entry 'aspectjweaver'
}
- dependencySet(group: 'org.codehaus.groovy', version: '2.5.8') {
+ dependencySet(group: 'org.codehaus.groovy', version: '2.5.15') {
entry 'groovy'
entry 'groovy-jsr223'
entry 'groovy-templates'
@@ -78,37 +60,38 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
- dependency "io.reactivex.rxjava2:rxjava:2.2.13"
+ dependency "io.reactivex.rxjava2:rxjava:2.2.21"
+ dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
- dependency "com.caucho:hessian:4.0.62"
+ dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.2.2"
- dependency("com.fasterxml.woodstox:woodstox-core:5.2.0") {
+ dependency("com.fasterxml.woodstox:woodstox-core:6.2.3") {
exclude group: "stax", name: "stax-api"
}
dependency "com.google.code.gson:gson:2.8.6"
- dependency "com.google.protobuf:protobuf-java-util:3.10.0"
+ dependency "com.google.protobuf:protobuf-java-util:3.11.4"
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
dependency("com.thoughtworks.xstream:xstream:1.4.11.1") {
exclude group: "xpp3", name: "xpp3_min"
exclude group: "xmlpull", name: "xmlpull"
}
- dependency "org.apache.johnzon:johnzon-jsonb:1.2.1"
+ dependency "org.apache.johnzon:johnzon-jsonb:1.2.10"
dependency("org.codehaus.jettison:jettison:1.3.8") {
exclude group: "stax", name: "stax-api"
}
- dependencySet(group: 'org.jibx', version: '1.3.1') {
+ 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.25"
+ dependency "org.yaml:snakeyaml:1.27"
dependency "com.h2database:h2:1.4.200"
- dependency "com.github.ben-manes.caffeine:caffeine:2.8.0"
- dependency "com.github.librepdf:openpdf:1.3.11"
+ dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
+ dependency "com.github.librepdf:openpdf:1.3.25"
dependency "com.rometools:rome:1.12.2"
dependency "commons-io:commons-io:2.5"
- dependency "io.vavr:vavr:0.10.0"
+ dependency "io.vavr:vavr:0.10.3"
dependency "net.sf.jopt-simple:jopt-simple:5.0.4"
dependencySet(group: 'org.apache.activemq', version: '5.8.0') {
entry 'activemq-broker'
@@ -118,37 +101,37 @@ configure(allprojects) { project ->
entry 'activemq-stomp'
}
dependency "org.apache.bcel:bcel:6.0"
- dependency "org.apache.commons:commons-pool2:2.6.0"
+ dependency "org.apache.commons:commons-pool2:2.8.1"
dependencySet(group: 'org.apache.derby', version: '10.14.2.0') {
entry 'derby'
entry 'derbyclient'
}
- dependency "org.apache.poi:poi-ooxml:4.1.1"
- dependency "org.beanshell:bsh:2.0b5"
- dependency "org.freemarker:freemarker:2.3.29"
- dependency "org.hsqldb:hsqldb:2.5.0"
+ dependency "org.apache.poi:poi-ooxml:4.1.2"
+ dependency "org.apache-extras.beanshell:bsh:2.0b6"
+ dependency "org.freemarker:freemarker:2.3.30"
+ dependency "org.hsqldb:hsqldb:2.5.1"
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.8.Final"
- dependency "org.hibernate:hibernate-validator:6.0.17.Final"
- dependency "org.webjars:webjars-locator-core:0.42"
+ dependency "org.hibernate:hibernate-core:5.4.28.Final"
+ dependency "org.hibernate:hibernate-validator:6.1.7.Final"
+ dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
- dependencySet(group: 'org.apache.tomcat', version: '9.0.27') {
+ dependencySet(group: 'org.apache.tomcat', version: '9.0.37') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
}
}
- dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.27') {
+ dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.37') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
- dependencySet(group: 'io.undertow', version: '2.0.27.Final') {
+ dependencySet(group: 'io.undertow', version: '2.0.32.Final') {
entry 'undertow-core'
entry('undertow-websockets-jsr') {
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
@@ -159,21 +142,21 @@ configure(allprojects) { project ->
}
}
- dependencySet(group: 'com.squareup.okhttp3', version: '3.14.3') {
+ dependencySet(group: 'com.squareup.okhttp3', version: '3.14.9') {
entry 'okhttp'
entry 'mockwebserver'
}
- dependency("org.apache.httpcomponents:httpclient:4.5.10") {
+ dependency("org.apache.httpcomponents:httpclient:4.5.13") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") {
exclude group: "commons-logging", name: "commons-logging"
}
- dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.0.3"
+ dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.4"
- dependency "org.jruby:jruby:9.2.7.0"
+ dependency "org.jruby:jruby:9.2.11.1"
dependency "org.python:jython-standalone:2.7.1"
- dependency "org.mozilla:rhino:1.7.10"
+ dependency "org.mozilla:rhino:1.7.11"
dependency "commons-fileupload:commons-fileupload:1.4"
dependency "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
@@ -195,26 +178,26 @@ configure(allprojects) { project ->
}
dependency "org.testng:testng:6.14.3"
dependency "org.hamcrest:hamcrest:2.1"
- dependency "org.awaitility:awaitility:3.1.3"
- dependency "org.assertj:assertj-core:3.14.0"
+ dependency "org.awaitility:awaitility:3.1.6"
+ dependency "org.assertj:assertj-core:3.18.1"
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
- dependencySet(group: 'org.mockito', version: '3.1.0') {
+ dependencySet(group: 'org.mockito', version: '3.3.3') {
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
entry 'mockito-junit-jupiter'
}
- dependency "io.mockk:mockk:1.9.3"
+ dependency "io.mockk:mockk:1.10.2"
- dependency("net.sourceforge.htmlunit:htmlunit:2.36.0") {
+ dependency("net.sourceforge.htmlunit:htmlunit:2.43.0") {
exclude group: "commons-logging", name: "commons-logging"
}
- dependency("org.seleniumhq.selenium:htmlunit-driver:2.36.0") {
+ dependency("org.seleniumhq.selenium:htmlunit-driver:2.43.1") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -240,9 +223,9 @@ configure(allprojects) { project ->
}
dependency "com.ibm.websphere:uow:6.0.2.17"
- dependency "com.jamonapi:jamon:2.81"
- dependency "joda-time:joda-time:2.10.4"
- dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.5"
+ dependency "com.jamonapi:jamon:2.82"
+ dependency "joda-time:joda-time:2.10.10"
+ dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
dependency "org.javamoney:moneta:1.3"
dependency "com.sun.activation:javax.activation:1.2.0"
@@ -310,8 +293,10 @@ configure([rootProject] + javaProjects) { project ->
group = "org.springframework"
apply plugin: "java"
+ apply plugin: "java-test-fixtures"
apply plugin: "checkstyle"
apply plugin: 'org.springframework.build.compile'
+ apply from: "${rootDir}/gradle/custom-java-home.gradle"
apply from: "${rootDir}/gradle/ide.gradle"
pluginManager.withPlugin("kotlin") {
@@ -340,7 +325,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
- toolVersion = "8.26"
+ toolVersion = "8.38"
configDir = rootProject.file("src/checkstyle")
}
@@ -374,14 +359,14 @@ configure([rootProject] + javaProjects) { project ->
"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.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-ga/httpclient/apidocs/",
"https://junit.org/junit4/javadoc/4.12/",
- "https://junit.org/junit5/docs/5.5.2/api/"
+ "https://junit.org/junit5/docs/5.6.3/api/"
] as String[]
}
@@ -401,7 +386,7 @@ configure(rootProject) {
nohttp {
source.exclude "**/test-output/**"
- whitelistFile = project.file("src/nohttp/whitelist.lines")
+ allowlistFile = project.file("src/nohttp/allowlist.lines")
def rootPath = file(rootDir).toPath()
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
projectDirs.forEach { dir ->
@@ -414,10 +399,6 @@ configure(rootProject) {
}
}
- dependencies {
- asciidoctor("io.spring.asciidoctor:spring-asciidoctor-extensions:0.1.3.RELEASE")
- }
-
publishing {
publications {
mavenJava(MavenPublication) {
diff --git a/buildSrc/README.md b/buildSrc/README.md
index 7c722a362730..f48339e6d61f 100644
--- a/buildSrc/README.md
+++ b/buildSrc/README.md
@@ -1,4 +1,4 @@
-# Spring Framework build
+# Spring Framework Build
This folder contains the custom plugins and conventions for the Spring Framework build.
They are declared in the `build.gradle` file in this folder.
@@ -7,8 +7,8 @@ They are declared in the `build.gradle` file in this folder.
### Compiler conventions
-The `org.springframework.build.compile` applies the Java compiler conventions to the build.
-By default, the build is compiling sources with the `1.8` source and target compatibility.
+The `org.springframework.build.compile` plugin applies the Java compiler conventions to the build.
+By default, the build compiles sources with Java `1.8` source and target compatibility.
You can test a different source compatibility version on the CLI with a project property like:
```
@@ -25,17 +25,11 @@ but doesn't affect the classpath of dependent projects.
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
configurations are preferred.
-## Test sources
-
-The `org.springframework.build.test-sources` updates `testCompile` dependencies to include
-the test source sets of `project()` dependencies. This plugin is used in the Spring Framework build
-to share test utilities and fixtures amongst modules.
-
## API Diff
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
-project and create tasks in each framework module. Unlike previous versions of this part of the build,
+project and creates tasks in each framework module. Unlike previous versions of this part of the build,
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
current working version with. You can generate the reports for all modules or a single module:
@@ -44,4 +38,4 @@ current working version with. You can generate the reports for all modules or a
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
```
-The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
\ No newline at end of file
+The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 1aed5f6e78a2..03f629496a28 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -9,7 +9,7 @@ repositories {
dependencies {
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.2.8"
- implementation "com.google.guava:guava:18.0" // required by japicmp-gradle-plugin
+ implementation "com.google.guava:guava:28.2-jre" // required by japicmp-gradle-plugin
}
gradlePlugin {
@@ -26,9 +26,5 @@ gradlePlugin {
id = "org.springframework.build.optional-dependencies"
implementationClass = "org.springframework.build.optional.OptionalDependenciesPlugin"
}
- testSourcesPlugin {
- id = "org.springframework.build.test-sources"
- implementationClass = "org.springframework.build.testsources.TestSourcesPlugin"
- }
}
}
diff --git a/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
index 5cb9a70a37e5..db51666f74b5 100644
--- a/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
+++ b/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 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.
@@ -33,6 +33,7 @@
* with a dedicated property on the CLI: {@code "./gradlew test -PjavaSourceVersion=11"}.
*
* @author Brian Clozel
+ * @author Sam Brannen
*/
public class CompilerConventionsPlugin implements Plugin {
@@ -40,13 +41,13 @@ public class CompilerConventionsPlugin implements Plugin {
* The project property that can be used to switch the Java source
* compatibility version for building source and test classes.
*/
- public static String JAVA_SOURCE_VERSION_PROPERTY = "javaSourceVersion";
+ public static final String JAVA_SOURCE_VERSION_PROPERTY = "javaSourceVersion";
- public static JavaVersion DEFAULT_COMPILER_VERSION = JavaVersion.VERSION_1_8;
+ public static final JavaVersion DEFAULT_COMPILER_VERSION = JavaVersion.VERSION_1_8;
- private static List COMPILER_ARGS;
+ private static final List COMPILER_ARGS;
- private static List TEST_COMPILER_ARGS;
+ private static final List TEST_COMPILER_ARGS;
static {
List commonCompilerArgs = Arrays.asList(
@@ -72,7 +73,8 @@ public void apply(Project project) {
}
/**
- * Applies the common Java compiler options for sources and test sources.
+ * Applies the common Java compiler options for main sources, test fixture sources, and
+ * test sources.
* @param project the current project
*/
private void applyJavaCompileConventions(Project project) {
@@ -87,16 +89,18 @@ private void applyJavaCompileConventions(Project project) {
java.setTargetCompatibility(DEFAULT_COMPILER_VERSION);
project.getTasks().withType(JavaCompile.class)
- .matching(javaCompile -> javaCompile.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
+ .matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
.forEach(compileTask -> {
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
compileTask.getOptions().setEncoding("UTF-8");
});
project.getTasks().withType(JavaCompile.class)
- .matching(javaCompile -> javaCompile.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME))
+ .matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
+ || compileTask.getName().equals("compileTestFixturesJava"))
.forEach(compileTask -> {
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
compileTask.getOptions().setEncoding("UTF-8");
});
}
-}
\ No newline at end of file
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/build/testsources/TestSourcesPlugin.java b/buildSrc/src/main/java/org/springframework/build/testsources/TestSourcesPlugin.java
deleted file mode 100644
index 68b97661902c..000000000000
--- a/buildSrc/src/main/java/org/springframework/build/testsources/TestSourcesPlugin.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- * 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.testsources;
-
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.plugins.JavaPlugin;
-import org.gradle.api.plugins.JavaPluginConvention;
-import org.gradle.api.tasks.SourceSet;
-import org.gradle.api.tasks.SourceSetOutput;
-
-import org.springframework.build.optional.OptionalDependenciesPlugin;
-
-/**
- * {@link Plugin} that automatically updates testCompile dependencies to include
- * the test source sets of {@code project()} dependencies.
- *
- *
This plugin is used in the Spring Framework build to share test utilities and fixtures
- * between projects.
- *
- * @author Phillip Webb
- * @author Brian Clozel
- */
-public class TestSourcesPlugin implements Plugin {
-
- /**
- * List of configurations this plugin should look for project dependencies in.
- */
- @SuppressWarnings("deprecation")
- private static final List CONFIGURATIONS = Arrays.asList(
- JavaPlugin.COMPILE_CONFIGURATION_NAME,
- JavaPlugin.API_CONFIGURATION_NAME,
- JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME,
- OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME,
- JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME);
-
- @Override
- public void apply(Project project) {
- project.getPlugins().withType(JavaPlugin.class, (plugin) -> addTestSourcesToProject(project));
- }
-
- private void addTestSourcesToProject(Project project) {
- project.afterEvaluate(currentProject -> {
- Set projectDependencies = new LinkedHashSet<>();
- collectProjectDependencies(projectDependencies, project);
- projectDependencies.forEach(dep -> addTestSourcesFromDependency(currentProject, dep));
- });
- }
-
- private void collectProjectDependencies(Set projectDependencies, Project project) {
- for (String configurationName : CONFIGURATIONS) {
- Configuration configuration = project.getConfigurations().findByName(configurationName);
- if (configuration != null) {
- configuration.getDependencies().forEach(dependency -> {
- if (dependency instanceof ProjectDependency) {
- ProjectDependency projectDependency = (ProjectDependency) dependency;
- projectDependencies.add(projectDependency);
- collectProjectDependencies(projectDependencies, projectDependency.getDependencyProject());
- }
- });
- }
- }
- }
-
- private void addTestSourcesFromDependency(final Project currentProject, ProjectDependency dependency) {
- Project dependencyProject = dependency.getDependencyProject();
- dependencyProject.getPlugins().withType(JavaPlugin.class, plugin -> {
- final JavaPluginConvention javaPlugin = dependencyProject.getConvention()
- .getPlugin(JavaPluginConvention.class);
- SourceSetOutput test = javaPlugin.getSourceSets().findByName(SourceSet.TEST_SOURCE_SET_NAME).getOutput();
- currentProject.getDependencies().add(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME, test);
- });
- }
-}
diff --git a/ci/README.adoc b/ci/README.adoc
new file mode 100644
index 000000000000..f66fd74a82c8
--- /dev/null
+++ b/ci/README.adoc
@@ -0,0 +1,57 @@
+== Spring Framework Concourse pipeline
+
+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-5.2.x[Spring Framework 5.2.x].
+
+=== Setting up your development environment
+
+If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
+First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
+
+You can then login with the instance using:
+
+[source]
+----
+$ fly -t spring login -n spring-framework -c https://ci.spring.io
+----
+
+Once logged in, you should get something like:
+
+[source]
+----
+$ fly ts
+name url team expiry
+spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
+----
+
+=== Pipeline configuration and structure
+
+The build pipelines are described in `pipeline.yml` file.
+
+This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
+
+It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
+
+The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
+
+You'll find in this folder the following resources:
+
+* `pipeline.yml` the build pipeline
+* `parameters.yml` the build parameters used for the pipeline
+* `images/` holds the container images definitions used in this pipeline
+* `scripts/` holds the build scripts that ship within the CI container images
+* `tasks` contains the task definitions used in the main `pipeline.yml`
+
+=== Updating the build pipeline
+
+Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
+
+The pipeline can be deployed using the following command:
+
+[source]
+----
+$ fly -t spring set-pipeline -p spring-framework-5.2.x -c ci/pipeline.yml -l ci/parameters.yml
+----
+
+NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
diff --git a/ci/config/changelog-generator.yml b/ci/config/changelog-generator.yml
new file mode 100644
index 000000000000..a029e25582e4
--- /dev/null
+++ b/ci/config/changelog-generator.yml
@@ -0,0 +1,17 @@
+changelog:
+ repository: spring-projects/spring-framework
+ sections:
+ - title: ":star: New Features"
+ labels:
+ - "type: enhancement"
+ - title: ":lady_beetle: Bug Fixes"
+ labels:
+ - "type: bug"
+ - "type: regression"
+ - title: ":notebook_with_decorative_cover: Documentation"
+ labels:
+ - "type: documentation"
+ - title: ":hammer: Dependency Upgrades"
+ sort: "title"
+ labels:
+ - "type: dependency-upgrade"
diff --git a/ci/config/release-scripts.yml b/ci/config/release-scripts.yml
new file mode 100644
index 000000000000..d31f8cba00dc
--- /dev/null
+++ b/ci/config/release-scripts.yml
@@ -0,0 +1,10 @@
+logging:
+ level:
+ io.spring.concourse: DEBUG
+spring:
+ main:
+ banner-mode: off
+sonatype:
+ exclude:
+ - 'build-info\.json'
+ - '.*\.zip'
diff --git a/ci/images/README.adoc b/ci/images/README.adoc
new file mode 100644
index 000000000000..6da9addd9ca5
--- /dev/null
+++ b/ci/images/README.adoc
@@ -0,0 +1,21 @@
+== CI Images
+
+These images are used by CI to run the actual builds.
+
+To build the image locally run the following from this directory:
+
+----
+$ docker build --no-cache -f /Dockerfile .
+----
+
+For example
+
+----
+$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
+----
+
+To test run:
+
+----
+$ docker run -it --entrypoint /bin/bash
+----
diff --git a/ci/images/ci-image-jdk11/Dockerfile b/ci/images/ci-image-jdk11/Dockerfile
new file mode 100644
index 000000000000..253720dc2e21
--- /dev/null
+++ b/ci/images/ci-image-jdk11/Dockerfile
@@ -0,0 +1,8 @@
+FROM ubuntu:focal-20220302
+
+ADD setup.sh /setup.sh
+ADD get-jdk-url.sh /get-jdk-url.sh
+RUN ./setup.sh java11
+
+ENV JAVA_HOME /opt/openjdk
+ENV PATH $JAVA_HOME/bin:$PATH
diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile
new file mode 100644
index 000000000000..157d7f28b848
--- /dev/null
+++ b/ci/images/ci-image/Dockerfile
@@ -0,0 +1,8 @@
+FROM ubuntu:focal-20220302
+
+ADD setup.sh /setup.sh
+ADD get-jdk-url.sh /get-jdk-url.sh
+RUN ./setup.sh java8
+
+ENV JAVA_HOME /opt/openjdk
+ENV PATH $JAVA_HOME/bin:$PATH
diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh
new file mode 100755
index 000000000000..fca99d595e5c
--- /dev/null
+++ b/ci/images/get-jdk-url.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e
+
+case "$1" in
+ java8)
+ echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz"
+ ;;
+ java11)
+ echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz"
+ ;;
+ *)
+ echo $"Unknown java version"
+ exit 1
+esac
diff --git a/ci/images/setup.sh b/ci/images/setup.sh
new file mode 100755
index 000000000000..82f777d89fd4
--- /dev/null
+++ b/ci/images/setup.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+set -ex
+
+###########################################################
+# UTILS
+###########################################################
+
+export DEBIAN_FRONTEND=noninteractive
+apt-get update
+apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
+ln -fs /usr/share/zoneinfo/UTC /etc/localtime
+dpkg-reconfigure --frontend noninteractive tzdata
+rm -rf /var/lib/apt/lists/*
+
+curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
+
+curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.2/concourse-release-scripts-0.3.2.jar
+
+###########################################################
+# JAVA
+###########################################################
+JDK_URL=$( ./get-jdk-url.sh $1 )
+
+mkdir -p /opt/openjdk
+cd /opt/openjdk
+curl -L ${JDK_URL} | tar zx --strip-components=1
+test -f /opt/openjdk/bin/java
+test -f /opt/openjdk/bin/javac
+
+###########################################################
+# GRADLE ENTERPRISE
+###########################################################
+cd /
+mkdir ~/.gradle
+echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
diff --git a/ci/parameters.yml b/ci/parameters.yml
new file mode 100644
index 000000000000..26579fc60280
--- /dev/null
+++ b/ci/parameters.yml
@@ -0,0 +1,13 @@
+email-server: "smtp.svc.pivotal.io"
+email-from: "ci@spring.io"
+email-to: ["spring-framework-dev@pivotal.io"]
+github-repo: "https://github.com/spring-projects/spring-framework.git"
+github-repo-name: "spring-projects/spring-framework"
+docker-hub-organization: "springci"
+artifactory-server: "https://repo.spring.io"
+branch: "5.2.x"
+milestone: "5.2.x"
+build-name: "spring-framework"
+pipeline-name: "spring-framework"
+concourse-url: "https://ci.spring.io"
+task-timeout: 1h00m
diff --git a/ci/pipeline.yml b/ci/pipeline.yml
new file mode 100644
index 000000000000..1f57b3d999b9
--- /dev/null
+++ b/ci/pipeline.yml
@@ -0,0 +1,391 @@
+anchors:
+ git-repo-resource-source: &git-repo-resource-source
+ uri: ((github-repo))
+ username: ((github-username))
+ password: ((github-ci-release-token))
+ branch: ((branch))
+ gradle-enterprise-task-params: &gradle-enterprise-task-params
+ GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
+ sonatype-task-params: &sonatype-task-params
+ SONATYPE_USERNAME: ((sonatype-username))
+ SONATYPE_PASSWORD: ((sonatype-password))
+ SONATYPE_URL: ((sonatype-url))
+ SONATYPE_STAGING_PROFILE_ID: ((sonatype-staging-profile-id))
+ artifactory-task-params: &artifactory-task-params
+ ARTIFACTORY_SERVER: ((artifactory-server))
+ ARTIFACTORY_USERNAME: ((artifactory-username))
+ ARTIFACTORY_PASSWORD: ((artifactory-password))
+ build-project-task-params: &build-project-task-params
+ privileged: true
+ timeout: ((task-timeout))
+ params:
+ BRANCH: ((branch))
+ <<: *gradle-enterprise-task-params
+ docker-resource-source: &docker-resource-source
+ username: ((docker-hub-username))
+ password: ((docker-hub-password))
+ tag: ((milestone))
+ slack-fail-params: &slack-fail-params
+ text: >
+ :concourse-failed:
+ [$TEXT_FILE_CONTENT]
+ text_file: git-repo/build/build-scan-uri.txt
+ silent: true
+ icon_emoji: ":concourse:"
+ username: concourse-ci
+ changelog-task-params: &changelog-task-params
+ name: generated-changelog/tag
+ tag: generated-changelog/tag
+ body: generated-changelog/changelog.md
+ github-task-params: &github-task-params
+ GITHUB_USERNAME: ((github-username))
+ GITHUB_TOKEN: ((github-ci-release-token))
+
+resource_types:
+- name: artifactory-resource
+ type: registry-image
+ source:
+ repository: springio/artifactory-resource
+ tag: 0.0.17
+- name: github-release
+ type: registry-image
+ source:
+ repository: concourse/github-release-resource
+ tag: 1.5.5
+- name: github-status-resource
+ type: registry-image
+ source:
+ repository: dpb587/github-status-resource
+ tag: master
+- name: slack-notification
+ type: registry-image
+ source:
+ repository: cfcommunity/slack-notification-resource
+ tag: latest
+resources:
+- name: git-repo
+ type: git
+ 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
+ source:
+ uri: ((github-repo))
+ branch: ((branch))
+ paths: ["ci/images/*"]
+- name: ci-image
+ type: docker-image
+ icon: docker
+ source:
+ <<: *docker-resource-source
+ repository: ((docker-hub-organization))/spring-framework-ci
+- name: ci-image-jdk11
+ type: docker-image
+ icon: docker
+ source:
+ <<: *docker-resource-source
+ repository: ((docker-hub-organization))/spring-framework-ci-jdk11
+- name: artifactory-repo
+ type: artifactory-resource
+ icon: package-variant
+ source:
+ uri: ((artifactory-server))
+ username: ((artifactory-username))
+ password: ((artifactory-password))
+ build_name: ((build-name))
+- name: repo-status-build
+ type: github-status-resource
+ icon: eye-check-outline
+ source:
+ repository: ((github-repo-name))
+ access_token: ((github-ci-status-token))
+ branch: ((branch))
+ context: build
+- name: repo-status-jdk11-build
+ type: github-status-resource
+ icon: eye-check-outline
+ source:
+ repository: ((github-repo-name))
+ access_token: ((github-ci-status-token))
+ branch: ((branch))
+ context: jdk11-build
+- name: slack-alert
+ type: slack-notification
+ icon: slack
+ source:
+ url: ((slack-webhook-url))
+- name: github-pre-release
+ type: github-release
+ icon: briefcase-download-outline
+ source:
+ owner: spring-projects
+ repository: spring-framework
+ access_token: ((github-ci-release-token))
+ pre_release: true
+ release: false
+- name: github-release
+ type: github-release
+ icon: briefcase-download
+ source:
+ owner: spring-projects
+ repository: spring-framework
+ access_token: ((github-ci-release-token))
+ pre_release: false
+jobs:
+- name: build-ci-images
+ plan:
+ - get: ci-images-git-repo
+ trigger: true
+ - in_parallel:
+ - put: ci-image
+ params:
+ build: ci-images-git-repo/ci/images
+ dockerfile: ci-images-git-repo/ci/images/ci-image/Dockerfile
+ - put: ci-image-jdk11
+ params:
+ build: ci-images-git-repo/ci/images
+ dockerfile: ci-images-git-repo/ci/images/ci-image-jdk11/Dockerfile
+- name: build
+ serial: true
+ public: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: true
+ - put: repo-status-build
+ params: { state: "pending", commit: "git-repo" }
+ - do:
+ - task: build-project
+ image: ci-image
+ file: git-repo/ci/tasks/build-project.yml
+ <<: *build-project-task-params
+ on_failure:
+ do:
+ - put: repo-status-build
+ params: { state: "failure", commit: "git-repo" }
+ - put: slack-alert
+ params:
+ <<: *slack-fail-params
+ - put: repo-status-build
+ params: { state: "success", commit: "git-repo" }
+ - put: artifactory-repo
+ params: &artifactory-params
+ signing_key: ((signing-key))
+ signing_passphrase: ((signing-passphrase))
+ repo: libs-snapshot-local
+ folder: distribution-repository
+ build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
+ build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
+ disable_checksum_uploads: true
+ threads: 8
+ artifact_set:
+ - include:
+ - "/**/spring-*.zip"
+ properties:
+ "zip.name": "spring-framework"
+ "zip.displayname": "Spring Framework"
+ "zip.deployed": "false"
+ - include:
+ - "/**/spring-*-docs.zip"
+ properties:
+ "zip.type": "docs"
+ - include:
+ - "/**/spring-*-dist.zip"
+ properties:
+ "zip.type": "dist"
+ - include:
+ - "/**/spring-*-schema.zip"
+ properties:
+ "zip.type": "schema"
+ get_params:
+ threads: 8
+- name: jdk11-build
+ serial: true
+ public: true
+ plan:
+ - get: ci-image-jdk11
+ - get: git-repo
+ - get: every-morning
+ trigger: true
+ - put: repo-status-jdk11-build
+ params: { state: "pending", commit: "git-repo" }
+ - do:
+ - task: check-project
+ image: ci-image-jdk11
+ file: git-repo/ci/tasks/check-project.yml
+ <<: *build-project-task-params
+ on_failure:
+ do:
+ - put: repo-status-jdk11-build
+ params: { state: "failure", commit: "git-repo" }
+ - put: slack-alert
+ params:
+ <<: *slack-fail-params
+ - put: repo-status-jdk11-build
+ params: { state: "success", commit: "git-repo" }
+- name: stage-milestone
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: false
+ - task: stage
+ image: ci-image
+ file: git-repo/ci/tasks/stage-version.yml
+ params:
+ RELEASE_TYPE: M
+ <<: *gradle-enterprise-task-params
+ - put: artifactory-repo
+ params:
+ <<: *artifactory-params
+ repo: libs-staging-local
+ - put: git-repo
+ params:
+ repository: stage-git-repo
+- name: promote-milestone
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: false
+ - get: artifactory-repo
+ trigger: false
+ passed: [stage-milestone]
+ params:
+ download_artifacts: false
+ save_build_info: true
+ - task: promote
+ image: ci-image
+ file: git-repo/ci/tasks/promote-version.yml
+ params:
+ RELEASE_TYPE: M
+ <<: *artifactory-task-params
+ - task: generate-changelog
+ file: git-repo/ci/tasks/generate-changelog.yml
+ params:
+ RELEASE_TYPE: M
+ <<: *github-task-params
+ - put: github-pre-release
+ params:
+ <<: *changelog-task-params
+- name: stage-rc
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: false
+ - task: stage
+ image: ci-image
+ file: git-repo/ci/tasks/stage-version.yml
+ params:
+ RELEASE_TYPE: RC
+ <<: *gradle-enterprise-task-params
+ - put: artifactory-repo
+ params:
+ <<: *artifactory-params
+ repo: libs-staging-local
+ - put: git-repo
+ params:
+ repository: stage-git-repo
+- name: promote-rc
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: false
+ - get: artifactory-repo
+ trigger: false
+ passed: [stage-rc]
+ params:
+ download_artifacts: false
+ save_build_info: true
+ - task: promote
+ image: ci-image
+ file: git-repo/ci/tasks/promote-version.yml
+ params:
+ RELEASE_TYPE: RC
+ <<: *artifactory-task-params
+ - task: generate-changelog
+ file: git-repo/ci/tasks/generate-changelog.yml
+ params:
+ RELEASE_TYPE: RC
+ <<: *github-task-params
+ - put: github-pre-release
+ params:
+ <<: *changelog-task-params
+- name: stage-release
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: false
+ - task: stage
+ image: ci-image
+ file: git-repo/ci/tasks/stage-version.yml
+ params:
+ RELEASE_TYPE: RELEASE
+ <<: *gradle-enterprise-task-params
+ - put: artifactory-repo
+ params:
+ <<: *artifactory-params
+ repo: libs-staging-local
+ - put: git-repo
+ params:
+ repository: stage-git-repo
+- name: promote-release
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ trigger: false
+ - get: artifactory-repo
+ trigger: false
+ passed: [stage-release]
+ params:
+ download_artifacts: true
+ save_build_info: true
+ - task: promote
+ image: ci-image
+ file: git-repo/ci/tasks/promote-version.yml
+ params:
+ RELEASE_TYPE: RELEASE
+ <<: *artifactory-task-params
+ <<: *sonatype-task-params
+- name: create-github-release
+ serial: true
+ plan:
+ - get: ci-image
+ - get: git-repo
+ - get: artifactory-repo
+ trigger: true
+ passed: [promote-release]
+ params:
+ download_artifacts: false
+ save_build_info: true
+ - task: generate-changelog
+ file: git-repo/ci/tasks/generate-changelog.yml
+ params:
+ RELEASE_TYPE: RELEASE
+ <<: *github-task-params
+ - put: github-release
+ params:
+ <<: *changelog-task-params
+
+groups:
+- name: "builds"
+ jobs: ["build", "jdk11-build"]
+- name: "releases"
+ jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
+- name: "ci-images"
+ jobs: ["build-ci-images"]
diff --git a/ci/scripts/build-project.sh b/ci/scripts/build-project.sh
new file mode 100755
index 000000000000..3844d1a3ddb4
--- /dev/null
+++ b/ci/scripts/build-project.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+
+source $(dirname $0)/common.sh
+repository=$(pwd)/distribution-repository
+
+pushd git-repo > /dev/null
+./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
+popd > /dev/null
diff --git a/ci/scripts/check-project.sh b/ci/scripts/check-project.sh
new file mode 100755
index 000000000000..94c4e8df65b4
--- /dev/null
+++ b/ci/scripts/check-project.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+
+source $(dirname $0)/common.sh
+
+pushd git-repo > /dev/null
+./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
+popd > /dev/null
diff --git a/ci/scripts/common.sh b/ci/scripts/common.sh
new file mode 100644
index 000000000000..1accaa616732
--- /dev/null
+++ b/ci/scripts/common.sh
@@ -0,0 +1,2 @@
+source /opt/concourse-java.sh
+setup_symlinks
\ No newline at end of file
diff --git a/ci/scripts/generate-changelog.sh b/ci/scripts/generate-changelog.sh
new file mode 100755
index 000000000000..49e96c1ff326
--- /dev/null
+++ b/ci/scripts/generate-changelog.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e
+
+CONFIG_DIR=git-repo/ci/config
+version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
+
+milestone=${version}
+if [[ $RELEASE_TYPE = "RELEASE" ]]; then
+ milestone=${version%.RELEASE}
+fi
+
+java -jar /github-changelog-generator.jar \
+ --spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
+ ${milestone} generated-changelog/changelog.md
+
+echo ${version} > generated-changelog/version
+echo v${version} > generated-changelog/tag
diff --git a/ci/scripts/promote-version.sh b/ci/scripts/promote-version.sh
new file mode 100755
index 000000000000..44c5ff626f91
--- /dev/null
+++ b/ci/scripts/promote-version.sh
@@ -0,0 +1,18 @@
+#!/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/' )
+export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
+
+java -jar /opt/concourse-release-scripts.jar \
+ --spring.config.location=${CONFIG_DIR}/release-scripts.yml \
+ publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
+
+java -jar /opt/concourse-release-scripts.jar \
+ --spring.config.location=${CONFIG_DIR}/release-scripts.yml \
+ promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
+
+echo "Promotion complete"
+echo $version > version/version
diff --git a/ci/scripts/stage-version.sh b/ci/scripts/stage-version.sh
new file mode 100755
index 000000000000..5bb7300e799a
--- /dev/null
+++ b/ci/scripts/stage-version.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+set -e
+
+source $(dirname $0)/common.sh
+repository=$(pwd)/distribution-repository
+
+pushd git-repo > /dev/null
+git fetch --tags --all > /dev/null
+popd > /dev/null
+
+git clone git-repo stage-git-repo > /dev/null
+
+pushd stage-git-repo > /dev/null
+
+snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
+if [[ $RELEASE_TYPE = "M" ]]; then
+ stageVersion=$( get_next_milestone_release $snapshotVersion)
+ nextVersion=$snapshotVersion
+elif [[ $RELEASE_TYPE = "RC" ]]; then
+ stageVersion=$( get_next_rc_release $snapshotVersion)
+ nextVersion=$snapshotVersion
+elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
+ stageVersion=$( get_next_release $snapshotVersion "RELEASE")
+ nextVersion=$( bump_version_number $snapshotVersion)
+else
+ echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
+fi
+
+echo "Staging $stageVersion (next version will be $nextVersion)"
+sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
+
+git config user.name "Spring Builds" > /dev/null
+git config user.email "spring-builds@users.noreply.github.com" > /dev/null
+git add gradle.properties > /dev/null
+git commit -m"Release v$stageVersion" > /dev/null
+git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
+
+./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
+
+git reset --hard HEAD^ > /dev/null
+if [[ $nextVersion != $snapshotVersion ]]; then
+ echo "Setting next development version (v$nextVersion)"
+ sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
+ git add gradle.properties > /dev/null
+ git commit -m"Next development version (v$nextVersion)" > /dev/null
+fi;
+
+echo "Staging Complete"
+
+popd > /dev/null
diff --git a/ci/scripts/sync-to-maven-central.sh b/ci/scripts/sync-to-maven-central.sh
new file mode 100755
index 000000000000..b42631164ed5
--- /dev/null
+++ b/ci/scripts/sync-to-maven-central.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
+version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
+java -jar /opt/concourse-release-scripts.jar syncToCentral "RELEASE" $BUILD_INFO_LOCATION || { exit 1; }
+
+echo "Sync complete"
+echo $version > version/version
diff --git a/ci/tasks/build-project.yml b/ci/tasks/build-project.yml
new file mode 100644
index 000000000000..759749ef433f
--- /dev/null
+++ b/ci/tasks/build-project.yml
@@ -0,0 +1,22 @@
+---
+platform: linux
+inputs:
+- name: git-repo
+outputs:
+- name: distribution-repository
+- name: git-repo
+caches:
+- path: gradle
+params:
+ BRANCH:
+ CI: true
+ GRADLE_ENTERPRISE_ACCESS_KEY:
+ GRADLE_ENTERPRISE_CACHE_USERNAME:
+ GRADLE_ENTERPRISE_CACHE_PASSWORD:
+ GRADLE_ENTERPRISE_URL: https://ge.spring.io
+run:
+ path: bash
+ args:
+ - -ec
+ - |
+ ${PWD}/git-repo/ci/scripts/build-project.sh
diff --git a/ci/tasks/check-project.yml b/ci/tasks/check-project.yml
new file mode 100644
index 000000000000..ea6d6ddb94c8
--- /dev/null
+++ b/ci/tasks/check-project.yml
@@ -0,0 +1,22 @@
+---
+platform: linux
+inputs:
+- name: git-repo
+outputs:
+- name: distribution-repository
+- name: git-repo
+caches:
+- path: gradle
+params:
+ BRANCH:
+ CI: true
+ GRADLE_ENTERPRISE_ACCESS_KEY:
+ GRADLE_ENTERPRISE_CACHE_USERNAME:
+ GRADLE_ENTERPRISE_CACHE_PASSWORD:
+ GRADLE_ENTERPRISE_URL: https://ge.spring.io
+run:
+ path: bash
+ args:
+ - -ec
+ - |
+ ${PWD}/git-repo/ci/scripts/check-project.sh
diff --git a/ci/tasks/generate-changelog.yml b/ci/tasks/generate-changelog.yml
new file mode 100755
index 000000000000..b3f40278c8e6
--- /dev/null
+++ b/ci/tasks/generate-changelog.yml
@@ -0,0 +1,20 @@
+---
+platform: linux
+image_resource:
+ type: registry-image
+ source:
+ repository: springio/github-changelog-generator
+ tag: '0.0.7'
+inputs:
+- name: git-repo
+- name: artifactory-repo
+outputs:
+- name: generated-changelog
+params:
+ GITHUB_ORGANIZATION:
+ GITHUB_REPO:
+ GITHUB_USERNAME:
+ GITHUB_TOKEN:
+ RELEASE_TYPE:
+run:
+ path: git-repo/ci/scripts/generate-changelog.sh
diff --git a/ci/tasks/promote-version.yml b/ci/tasks/promote-version.yml
new file mode 100644
index 000000000000..abdd8fed5c5c
--- /dev/null
+++ b/ci/tasks/promote-version.yml
@@ -0,0 +1,18 @@
+---
+platform: linux
+inputs:
+- name: git-repo
+- name: artifactory-repo
+outputs:
+- name: version
+params:
+ RELEASE_TYPE:
+ ARTIFACTORY_SERVER:
+ ARTIFACTORY_USERNAME:
+ ARTIFACTORY_PASSWORD:
+ SONATYPE_USER:
+ SONATYPE_PASSWORD:
+ SONATYPE_URL:
+ SONATYPE_STAGING_PROFILE_ID:
+run:
+ path: git-repo/ci/scripts/promote-version.sh
diff --git a/ci/tasks/stage-version.yml b/ci/tasks/stage-version.yml
new file mode 100644
index 000000000000..ded11483a76b
--- /dev/null
+++ b/ci/tasks/stage-version.yml
@@ -0,0 +1,17 @@
+---
+platform: linux
+inputs:
+- name: git-repo
+outputs:
+- name: stage-git-repo
+- name: distribution-repository
+params:
+ RELEASE_TYPE:
+ CI: true
+ GRADLE_ENTERPRISE_CACHE_USERNAME:
+ GRADLE_ENTERPRISE_CACHE_PASSWORD:
+ GRADLE_ENTERPRISE_URL: https://ge.spring.io
+caches:
+- path: gradle
+run:
+ path: git-repo/ci/scripts/stage-version.sh
diff --git a/gradle.properties b/gradle.properties
index 5fbb91d49b83..8673a6ed3fd2 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-version=5.2.1.BUILD-SNAPSHOT
+version=5.2.20.RELEASE
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
diff --git a/gradle/build-cache-settings.gradle b/gradle/build-cache-settings.gradle
index d62ea57ebd26..5684b4942b8f 100644
--- a/gradle/build-cache-settings.gradle
+++ b/gradle/build-cache-settings.gradle
@@ -2,18 +2,16 @@ buildCache {
local {
enabled = true
}
- if (System.getenv('GRADLE_ENTERPRISE_URL')) {
- remote(HttpBuildCache) {
- enabled = true
- url = "${System.getenv('GRADLE_ENTERPRISE_URL')}/cache/"
- def cacheUsername = System.getenv('GRADLE_ENTERPRISE_CACHE_USERNAME')
- def cachePassword = System.getenv('GRADLE_ENTERPRISE_CACHE_PASSWORD')
- if (cacheUsername && cachePassword) {
- push = true
- credentials {
- username = cacheUsername
- password = cachePassword
- }
+ remote(HttpBuildCache) {
+ enabled = true
+ url = 'https://ge.spring.io/cache/'
+ def cacheUsername = System.getenv('GRADLE_ENTERPRISE_CACHE_USERNAME')
+ def cachePassword = System.getenv('GRADLE_ENTERPRISE_CACHE_PASSWORD')
+ if (cacheUsername && cachePassword) {
+ push = true
+ credentials {
+ username = cacheUsername
+ password = cachePassword
}
}
}
diff --git a/gradle/build-scan-user-data.gradle b/gradle/build-scan-user-data.gradle
index d4f483d66f26..156191d11526 100644
--- a/gradle/build-scan-user-data.gradle
+++ b/gradle/build-scan-user-data.gradle
@@ -1,90 +1,16 @@
-tagOs()
-tagIde()
-tagCiOrLocal()
-addCiMetadata()
-addGitMetadata()
-addTestTaskMetadata()
+addCustomJavaHomeMetadata()
+addCustomJavaSourceVersionMetadata()
-void tagOs() {
- buildScan.tag System.getProperty('os.name')
-}
-
-void tagIde() {
- if (System.getProperty('idea.version')) {
- buildScan.tag 'IntelliJ IDEA'
- } else if (System.getProperty('eclipse.buildId')) {
- buildScan.tag 'Eclipse'
- }
-}
-
-void tagCiOrLocal() {
- buildScan.tag(isCi() ? 'CI' : 'LOCAL')
-}
-
-void addGitMetadata() {
- buildScan.background {
- def gitCommitId = execAndGetStdout('git', 'rev-parse', '--short=8', '--verify', 'HEAD')
- def gitBranchName = execAndGetStdout('git', 'rev-parse', '--abbrev-ref', 'HEAD')
- def gitStatus = execAndGetStdout('git', 'status', '--porcelain')
-
- if(gitCommitId) {
- def commitIdLabel = 'Git Commit ID'
- value commitIdLabel, gitCommitId
- link 'Git commit build scans', customValueSearchUrl([(commitIdLabel): gitCommitId])
- }
- if (gitBranchName) {
- tag gitBranchName
- value 'Git branch', gitBranchName
- }
- if (gitStatus) {
- tag 'dirty'
- value 'Git status', gitStatus
- }
- }
-}
-
-void addCiMetadata() {
- def ciBuild = 'CI BUILD'
- if (isBamboo()) {
- buildScan.link ciBuild, System.getenv('bamboo_resultsUrl')
+void addCustomJavaHomeMetadata() {
+ def customJavaHome = System.getProperty("customJavaHome")
+ if (customJavaHome) {
+ buildScan.value "Custom JAVA_HOME", customJavaHome
}
}
-void addTestTaskMetadata() {
- allprojects {
- tasks.withType(Test) { test ->
- doFirst {
- buildScan.value "Test#maxParallelForks[${test.path}]", test.maxParallelForks.toString()
- }
- }
+void addCustomJavaSourceVersionMetadata() {
+ def customJavaSourceVersion = System.getProperty("customJavaSourceVersion")
+ if (customJavaSourceVersion) {
+ buildScan.value "Custom Java Source Version", customJavaSourceVersion
}
}
-
-boolean isCi() {
- isBamboo()
-}
-
-boolean isBamboo() {
- System.getenv('bamboo_resultsUrl')
-}
-
-String execAndGetStdout(String... args) {
- def stdout = new ByteArrayOutputStream()
- exec {
- commandLine(args)
- standardOutput = stdout
- }
- return stdout.toString().trim()
-}
-
-String customValueSearchUrl(Map search) {
- def query = search.collect { name, value ->
- "search.names=${encodeURL(name)}&search.values=${encodeURL(value)}"
- }.join('&')
-
- "$buildScan.server/scans?$query"
-}
-
-String encodeURL(String url) {
- URLEncoder.encode(url, 'UTF-8')
-}
diff --git a/gradle/custom-java-home.gradle b/gradle/custom-java-home.gradle
new file mode 100644
index 000000000000..54d1de1eb8f9
--- /dev/null
+++ b/gradle/custom-java-home.gradle
@@ -0,0 +1,80 @@
+// -----------------------------------------------------------------------------
+//
+// This script adds support for the following two JVM system properties
+// that control the build for alternative JDKs (i.e., a JDK other than
+// the one used to launch the Gradle process).
+//
+// - customJavaHome: absolute path to the alternate JDK installation to
+// use to compile Java code and execute tests. This system property
+// is also used in spring-oxm.gradle to determine whether JiBX is
+// supported.
+//
+// - customJavaSourceVersion: Java version supplied to the `--release`
+// command line flag to control the Java source and target
+// compatibility version. Supported versions include 9 or higher.
+// Do not set this system property if Java 8 should be used.
+//
+// Examples:
+//
+// ./gradlew -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home test
+//
+// ./gradlew --no-build-cache -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home test
+//
+// ./gradlew -DcustomJavaHome=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home -DcustomJavaSourceVersion=14 test
+//
+//
+// Credits: inspired by work from Marc Philipp and Stephane Nicoll
+//
+// -----------------------------------------------------------------------------
+
+import org.gradle.internal.os.OperatingSystem
+// import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
+
+def customJavaHome = System.getProperty("customJavaHome")
+
+if (customJavaHome) {
+ def customJavaHomeDir = new File(customJavaHome)
+ def customJavaSourceVersion = System.getProperty("customJavaSourceVersion")
+
+ tasks.withType(JavaCompile) {
+ logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHomeDir)
+ options.forkOptions.javaHome = customJavaHomeDir
+ inputs.property("customJavaHome", customJavaHome)
+ if (customJavaSourceVersion) {
+ options.compilerArgs += [ "--release", customJavaSourceVersion]
+ inputs.property("customJavaSourceVersion", customJavaSourceVersion)
+ }
+ }
+
+ tasks.withType(GroovyCompile) {
+ logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHomeDir)
+ options.forkOptions.javaHome = customJavaHomeDir
+ inputs.property("customJavaHome", customJavaHome)
+ if (customJavaSourceVersion) {
+ options.compilerArgs += [ "--release", customJavaSourceVersion]
+ inputs.property("customJavaSourceVersion", customJavaSourceVersion)
+ }
+ }
+
+ /*
+ tasks.withType(KotlinJvmCompile) {
+ logger.info("Java home for " + it.name + " task in " + project.name + ": " + customJavaHome)
+ kotlinOptions.jdkHome = customJavaHomeDir
+ inputs.property("customJavaHome", customJavaHome)
+ }
+ */
+
+ tasks.withType(Test) {
+ def javaExecutable = customJavaHome + "/bin/java"
+ if (OperatingSystem.current().isWindows()) {
+ javaExecutable += ".exe"
+ }
+ logger.info("Java executable for " + it.name + " task in " + project.name + ": " + javaExecutable)
+ executable = javaExecutable
+ inputs.property("customJavaHome", customJavaHome)
+ if (customJavaSourceVersion) {
+ inputs.property("customJavaSourceVersion", customJavaSourceVersion)
+ }
+ }
+
+}
diff --git a/gradle/docs.gradle b/gradle/docs.gradle
index 51b51252e860..27005827312d 100644
--- a/gradle/docs.gradle
+++ b/gradle/docs.gradle
@@ -1,3 +1,20 @@
+configurations {
+ asciidoctorExt
+}
+
+dependencies {
+ asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.0.RELEASE")
+}
+
+repositories {
+ maven {
+ url "https://repo.spring.io/release"
+ mavenContent {
+ includeGroup "io.spring.asciidoctor"
+ }
+ }
+}
+
/**
* Produce Javadoc for all Spring Framework modules in "build/docs/javadoc"
*/
@@ -48,39 +65,49 @@ dokka {
dependsOn {
tasks.getByName("api")
}
+
doFirst {
- classpath = moduleProjects.collect { project -> project.jar.outputs.files.getFiles() }.flatten()
- classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
- sourceDirs = files(moduleProjects
- .findAll {
- it.pluginManager.hasPlugin("kotlin")
+ configuration {
+ classpath = moduleProjects.collect { project -> project.jar.outputs.files.getFiles() }.flatten()
+ classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
+
+ moduleProjects.findAll {
+ it.pluginManager.hasPlugin("kotlin")
+ }.each { project ->
+ def kotlinDirs = project.sourceSets.main.kotlin.srcDirs.collect()
+ kotlinDirs -= project.sourceSets.main.java.srcDirs
+ kotlinDirs.each { dir ->
+ if (dir.exists()) {
+ sourceRoot {
+ path = dir.path
+ }
+ }
}
- .collect { project ->
- def kotlinDirs = project.sourceSets.main.kotlin.srcDirs.collect()
- kotlinDirs -= project.sourceSets.main.java.srcDirs
- })
+ }
+ }
}
- moduleName = "spring-framework"
+
outputFormat = "html"
outputDirectory = "$buildDir/docs/kdoc"
- externalDocumentationLink {
- url = new URL("https://docs.spring.io/spring-framework/docs/$version/javadoc-api/")
- packageListUrl = new File(buildDir, "docs/javadoc/package-list").toURI().toURL()
- }
- externalDocumentationLink {
- url = new URL("https://projectreactor.io/docs/core/release/api/")
- }
- externalDocumentationLink {
- url = new URL("https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/")
- }
- externalDocumentationLink {
- url = new URL("https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/")
+ configuration {
+ moduleName = "spring-framework"
+
+ externalDocumentationLink {
+ url = new URL("https://docs.spring.io/spring-framework/docs/$version/javadoc-api/")
+ packageListUrl = new File(buildDir, "docs/javadoc/package-list").toURI().toURL()
+ }
+ externalDocumentationLink {
+ url = new URL("https://projectreactor.io/docs/core/release/api/")
+ }
+ externalDocumentationLink {
+ url = new URL("https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/")
+ }
}
}
task downloadResources(type: Download) {
- def version = "0.1.3.RELEASE"
+ def version = "0.2.1.RELEASE"
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")
@@ -93,27 +120,13 @@ task extractDocResources(type: Copy, dependsOn: downloadResources) {
into "$buildDir/docs/spring-docs-resources/"
}
-/**
- * Produce the Spring Framework Reference documentation
- * from "src/docs/asciidoc" into "build/asciidoc/html5"
- */
-asciidoctor {
- sources {
- include '*.adoc'
- }
- outputDir "$buildDir/docs/ref-docs/"
- resources {
- from(sourceDir) {
- include 'images/*', 'css/**', 'js/**'
+asciidoctorj {
+ modules {
+ pdf {
+ version '1.5.0-beta.8'
}
- from "$buildDir/docs/spring-docs-resources/"
- }
- logDocuments = true
- backends = ["html5"]
- // only ouput PDF documentation for non-SNAPSHOT builds
- if (!project.getVersion().toString().contains("BUILD-SNAPSHOT")) {
- backends += "pdf"
}
+ fatalWarnings ".*"
options doctype: 'book', eruby: 'erubis'
attributes([
icons: 'font',
@@ -125,19 +138,51 @@ asciidoctor {
sectnums: '',
'source-highlighter': 'highlight.js',
highlightjsdir: 'js/highlight',
- 'highlightjs-theme': 'github',
+ 'highlightjs-theme': 'github', // 'googlecode',
stylesdir: 'css/',
stylesheet: 'stylesheet.css',
'spring-version': project.version
])
}
-asciidoctor.dependsOn extractDocResources
+/**
+ * Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
+ * in "build/docs/ref-docs/html5".
+ */
+asciidoctor {
+ baseDirFollowsSourceDir()
+ configurations 'asciidoctorExt'
+ sources {
+ include '*.adoc'
+ }
+ outputDir "$buildDir/docs/ref-docs/html5"
+ logDocuments = true
+ resources {
+ from(sourceDir) {
+ include 'images/*', 'css/**', 'js/**'
+ }
+ from extractDocResources
+ }
+}
+
+/**
+ * Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
+ * in "build/docs/ref-docs/pdf".
+ */
+asciidoctorPdf {
+ baseDirFollowsSourceDir()
+ configurations 'asciidoctorExt'
+ sources {
+ include '*.adoc'
+ }
+ outputDir "$buildDir/docs/ref-docs/pdf"
+ logDocuments = true
+}
/**
* Zip all docs (API and reference) into a single archive
*/
-task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'dokka']) {
+task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokka']) {
group = "Distribution"
description = "Builds -${archiveClassifier} archive containing api and reference " +
"for deployment at https://docs.spring.io/spring-framework/docs."
@@ -150,10 +195,10 @@ task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'dokka']) {
from (api) {
into "javadoc-api"
}
- from ("$asciidoctor.outputDir/html5") {
+ from ("$asciidoctor.outputDir") {
into "spring-framework-reference"
}
- from ("$asciidoctor.outputDir/pdf") {
+ from ("$asciidoctorPdf.outputDir") {
into "spring-framework-reference/pdf"
}
from (dokka) {
@@ -175,14 +220,14 @@ task schemaZip(type: Zip) {
def Properties schemas = new Properties();
module.sourceSets.main.resources.find {
- it.path.endsWith("META-INF/spring.schemas")
+ (it.path.endsWith("META-INF/spring.schemas") || it.path.endsWith("META-INF\\spring.schemas"))
}?.withInputStream { schemas.load(it) }
for (def key : schemas.keySet()) {
def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
assert shortName != key
File xsdFile = module.sourceSets.main.resources.find {
- it.path.endsWith(schemas.get(key))
+ (it.path.endsWith(schemas.get(key)) || it.path.endsWith(schemas.get(key).replaceAll('\\/','\\\\')))
}
assert xsdFile != null
into (shortName) {
diff --git a/gradle/ide.gradle b/gradle/ide.gradle
index 554d4b3c5432..acb37abadbbf 100644
--- a/gradle/ide.gradle
+++ b/gradle/ide.gradle
@@ -29,12 +29,11 @@ eclipse.classpath.file.whenMerged { classpath ->
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
}
-
// Use separate main/test outputs (prevents WTP from packaging test classes)
eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse")
eclipse.classpath.file.beforeMerged { classpath ->
classpath.entries.findAll{ it instanceof SourceFolder }.each {
- if(it.output.startsWith("bin/")) {
+ if (it.output.startsWith("bin/")) {
it.output = null
}
}
diff --git a/gradle/publications.gradle b/gradle/publications.gradle
index 97d6e51f05f6..86e0d2221c0b 100644
--- a/gradle/publications.gradle
+++ b/gradle/publications.gradle
@@ -1,5 +1,4 @@
apply plugin: "maven-publish"
-apply plugin: 'com.jfrog.artifactory'
publishing {
publications {
@@ -50,6 +49,16 @@ publishing {
}
}
-artifactoryPublish {
- publications(publishing.publications.mavenJava)
+configureDeploymentRepository(project)
+
+void configureDeploymentRepository(Project project) {
+ project.plugins.withType(MavenPublishPlugin.class).all {
+ PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
+ if (project.hasProperty("deploymentRepository")) {
+ publishing.repositories.maven {
+ it.url = project.property("deploymentRepository")
+ it.name = "deployment"
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/gradle/spring-module.gradle b/gradle/spring-module.gradle
index c70263e18c0a..048735af1cbb 100644
--- a/gradle/spring-module.gradle
+++ b/gradle/spring-module.gradle
@@ -1,6 +1,5 @@
apply plugin: 'org.springframework.build.compile'
apply plugin: 'org.springframework.build.optional-dependencies'
-apply plugin: 'org.springframework.build.test-sources'
apply from: "$rootDir/gradle/publications.gradle"
jar {
@@ -62,3 +61,11 @@ publishing {
}
}
}
+
+// Disable publication of test fixture artifacts.
+//
+// Once we upgrade to Gradle 6.x, we will need to delete the following line ...
+components.java.variants.removeAll { it.outgoingConfiguration.name.startsWith("testFixtures") }
+// ... and uncomment the following two lines.
+// components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() }
+// components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f04d6a20aec6..5028f28f8e47 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/import-into-eclipse.md b/import-into-eclipse.md
index d303e0c7412b..bb665d788a13 100644
--- a/import-into-eclipse.md
+++ b/import-into-eclipse.md
@@ -3,12 +3,12 @@
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
-8 support, the Kotlin plugin, and the Groovy plugin.
+8 support, Eclipse Buildship, the Kotlin plugin, and the Groovy plugin.
The following instructions have been tested against [STS](https://spring.io/tools) 4.3.2
([download](https://github.com/spring-projects/sts4/wiki/Previous-Versions#spring-tools-432-changelog))
-with [Eclipse Buildship](https://projects.eclipse.org/projects/tools.buildship). The
-instructions should work with the latest Eclipse distribution as long as you install
+(based on Eclipse 4.12) with [Eclipse Buildship](https://projects.eclipse.org/projects/tools.buildship).
+The instructions should work with the latest Eclipse distribution as long as you install
[Buildship](https://marketplace.eclipse.org/content/buildship-gradle-integration). Note
that STS 4 comes with Buildship preinstalled.
@@ -23,7 +23,7 @@ _When instructed to execute `./gradlew` from the command line, be sure to execut
1. Switch to Groovy 2.5 (Preferences -> Groovy -> Compiler -> Switch to 2.5...) in Eclipse.
1. Change the _Forbidden reference (access rule)_ in Eclipse from Error to Warning
(Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference (access rule)).
-1. Optionally install the [AspectJ Development Tools](https://www.eclipse.org/ajdt/downloads/) (_AJDT_) if you need to work with the `spring-aspects` project.
+1. Optionally install the [AspectJ Development Tools](https://marketplace.eclipse.org/content/aspectj-development-tools) (_AJDT_) if you need to work with the `spring-aspects` project. The AspectJ Development Tools available in the Eclipse Marketplace have been tested with these instructions using STS 4.5 (Eclipse 4.14).
1. Optionally install the [TestNG plugin](https://testng.org/doc/eclipse.html) in Eclipse if you need to execute TestNG tests in the `spring-test` module.
1. Build `spring-oxm` from the command line with `./gradlew :spring-oxm:check`.
1. To apply project specific settings, run `./gradlew eclipseBuildship` from the command line.
diff --git a/integration-tests/integration-tests.gradle b/integration-tests/integration-tests.gradle
index ab3bba7354b6..71e23b906049 100644
--- a/integration-tests/integration-tests.gradle
+++ b/integration-tests/integration-tests.gradle
@@ -1,12 +1,14 @@
description = "Spring Integration Tests"
-apply plugin: "org.springframework.build.test-sources"
-
dependencies {
testCompile(project(":spring-aop"))
testCompile(project(":spring-beans"))
testCompile(project(":spring-context"))
testCompile(project(":spring-core"))
+ testCompile(testFixtures(project(":spring-aop")))
+ testCompile(testFixtures(project(":spring-beans")))
+ testCompile(testFixtures(project(":spring-core")))
+ testCompile(testFixtures(project(":spring-tx")))
testCompile(project(":spring-expression"))
testCompile(project(":spring-jdbc"))
testCompile(project(":spring-orm"))
diff --git a/integration-tests/src/test/java/.gitignore b/integration-tests/src/test/java/.gitignore
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerAdviceOrderIntegrationTests.java b/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerAdviceOrderIntegrationTests.java
new file mode 100644
index 000000000000..da32ff120181
--- /dev/null
+++ b/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerAdviceOrderIntegrationTests.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ * 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.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Integration tests for advice invocation order for advice configured via the
+ * AOP namespace.
+ *
+ * @author Sam Brannen
+ * @since 5.2.7
+ * @see org.springframework.aop.framework.autoproxy.AspectJAutoProxyAdviceOrderIntegrationTests
+ */
+class AopNamespaceHandlerAdviceOrderIntegrationTests {
+
+ @Nested
+ @SpringJUnitConfig(locations = "AopNamespaceHandlerAdviceOrderIntegrationTests-afterFirst.xml")
+ @DirtiesContext
+ class AfterAdviceFirstTests {
+
+ @Test
+ void afterAdviceIsInvokedFirst(@Autowired Echo echo, @Autowired InvocationTrackingAspect aspect) throws Exception {
+ assertThat(aspect.invocations).isEmpty();
+ assertThat(echo.echo(42)).isEqualTo(42);
+ assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after returning");
+
+ aspect.invocations.clear();
+ assertThatExceptionOfType(Exception.class).isThrownBy(() -> echo.echo(new Exception()));
+ assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after throwing");
+ }
+ }
+
+ @Nested
+ @SpringJUnitConfig(locations = "AopNamespaceHandlerAdviceOrderIntegrationTests-afterLast.xml")
+ @DirtiesContext
+ class AfterAdviceLastTests {
+
+ @Test
+ void afterAdviceIsInvokedLast(@Autowired Echo echo, @Autowired InvocationTrackingAspect aspect) throws Exception {
+ assertThat(aspect.invocations).isEmpty();
+ assertThat(echo.echo(42)).isEqualTo(42);
+ assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after returning", "after");
+
+ aspect.invocations.clear();
+ assertThatExceptionOfType(Exception.class).isThrownBy(() -> echo.echo(new Exception()));
+ assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after throwing", "after");
+ }
+ }
+
+
+ static class Echo {
+
+ Object echo(Object obj) throws Exception {
+ if (obj instanceof Exception) {
+ throw (Exception) obj;
+ }
+ return obj;
+ }
+ }
+
+ static class InvocationTrackingAspect {
+
+ List invocations = new ArrayList<>();
+
+ Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ invocations.add("around - start");
+ try {
+ return joinPoint.proceed();
+ }
+ finally {
+ invocations.add("around - end");
+ }
+ }
+
+ void before() {
+ invocations.add("before");
+ }
+
+ void afterReturning() {
+ invocations.add("after returning");
+ }
+
+ void afterThrowing() {
+ invocations.add("after throwing");
+ }
+
+ void after() {
+ invocations.add("after");
+ }
+ }
+
+}
diff --git a/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java b/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java
index 29f6e050812e..32ac556fe482 100644
--- a/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java
+++ b/integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerScopeIntegrationTests.java
@@ -21,12 +21,12 @@
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.testfixture.beans.ITestBean;
+import org.springframework.beans.testfixture.beans.TestBean;
+import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
-import org.springframework.tests.sample.beans.ITestBean;
-import org.springframework.tests.sample.beans.TestBean;
-import org.springframework.util.SerializationTestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
diff --git a/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorIntegrationTests.java b/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorIntegrationTests.java
index e8d30cd33b96..cf067e01415c 100644
--- a/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorIntegrationTests.java
+++ b/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorIntegrationTests.java
@@ -26,17 +26,17 @@
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
+import org.springframework.aop.testfixture.advice.CountingBeforeAdvice;
+import org.springframework.aop.testfixture.advice.MethodCounter;
+import org.springframework.aop.testfixture.interceptor.NopInterceptor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.lang.Nullable;
-import org.springframework.tests.aop.advice.CountingBeforeAdvice;
-import org.springframework.tests.aop.advice.MethodCounter;
-import org.springframework.tests.aop.interceptor.NopInterceptor;
-import org.springframework.tests.sample.beans.ITestBean;
-import org.springframework.tests.transaction.CallCountingTransactionManager;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.interceptor.TransactionInterceptor;
+import org.springframework.transaction.testfixture.CallCountingTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AspectJAutoProxyAdviceOrderIntegrationTests.java b/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AspectJAutoProxyAdviceOrderIntegrationTests.java
new file mode 100644
index 000000000000..78d45922be0c
--- /dev/null
+++ b/integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AspectJAutoProxyAdviceOrderIntegrationTests.java
@@ -0,0 +1,233 @@
+/*
+ * 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.
+ * 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.framework.autoproxy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Integration tests for advice invocation order for advice configured via
+ * AspectJ auto-proxy support.
+ *
+ * @author Sam Brannen
+ * @since 5.2.7
+ * @see org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests
+ */
+class AspectJAutoProxyAdviceOrderIntegrationTests {
+
+ /**
+ * {@link After @After} advice declared as first after method in source code.
+ */
+ @Nested
+ @SpringJUnitConfig(AfterAdviceFirstConfig.class)
+ @DirtiesContext
+ class AfterAdviceFirstTests {
+
+ @Test
+ void afterAdviceIsInvokedLast(@Autowired Echo echo, @Autowired AfterAdviceFirstAspect aspect) throws Exception {
+ assertThat(aspect.invocations).isEmpty();
+ assertThat(echo.echo(42)).isEqualTo(42);
+ assertThat(aspect.invocations).containsExactly("around - start", "before", "after returning", "after", "around - end");
+
+ aspect.invocations.clear();
+ assertThatExceptionOfType(Exception.class).isThrownBy(
+ () -> echo.echo(new Exception()));
+ assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
+ }
+ }
+
+
+ /**
+ * This test class uses {@link AfterAdviceLastAspect} which declares its
+ * {@link After @After} advice as the last after advice type method
+ * in its source code.
+ *
+ *
A runtime joinpoint is an event that occurs on a static
- * joinpoint (i.e. a location in a the program). For instance, an
+ * joinpoint (i.e. a location in a program). For instance, an
* invocation is the runtime joinpoint on a method (static joinpoint).
* The static part of a given joinpoint can be generically retrieved
* using the {@link #getStaticPart()} method.
diff --git a/spring-aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java b/spring-aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java
index b3b2c0a9d9bd..8c2c5d6ef8f0 100644
--- a/spring-aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java
@@ -33,9 +33,9 @@ public interface AfterReturningAdvice extends AfterAdvice {
/**
* Callback after a given method successfully returned.
* @param returnValue the value returned by the method, if any
- * @param method method being invoked
- * @param args arguments to the method
- * @param target target of the method invocation. May be {@code null}.
+ * @param method the method being invoked
+ * @param args the arguments to the method
+ * @param target the target of the method invocation. May be {@code null}.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
diff --git a/spring-aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java b/spring-aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java
index 08c704857f75..2f46775b9459 100644
--- a/spring-aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 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.
@@ -26,7 +26,7 @@
*
Introductions are often mixins, enabling the building of composite
* objects that can achieve many of the goals of multiple inheritance in Java.
*
- *
Compared to {qlink IntroductionInfo}, this interface allows an advice to
+ *
Compared to {@link IntroductionInfo}, this interface allows an advice to
* implement a range of interfaces that is not necessarily known in advance.
* Thus an {@link IntroductionAdvisor} can be used to specify which interfaces
* will be exposed in an advised object.
diff --git a/spring-aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java b/spring-aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java
index ae791a21b04c..806744d09c31 100644
--- a/spring-aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java
+++ b/spring-aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java
@@ -32,9 +32,9 @@ public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* Callback before a given method is invoked.
- * @param method method being invoked
- * @param args arguments to the method
- * @param target target of the method invocation. May be {@code null}.
+ * @param method the method being invoked
+ * @param args the arguments to the method
+ * @param target the target of the method invocation. May be {@code null}.
* @throws Throwable if this object wishes to abort the call.
* Any exception thrown will be returned to the caller if it's
* allowed by the method signature. Otherwise the exception
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java
index 1dacc08948b7..ec58afb29652 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -648,7 +648,7 @@ private PointcutBody getPointcutBody(String[] tokens, int startIndex) {
}
if (tokens[currentIndex].endsWith(")")) {
- sb.append(tokens[currentIndex].substring(0, tokens[currentIndex].length() - 1));
+ sb.append(tokens[currentIndex], 0, tokens[currentIndex].length() - 1);
return new PointcutBody(numTokensConsumed, sb.toString().trim());
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java
index 471647f89cf3..d1c4db25c284 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -219,10 +219,12 @@ public Class>[] getParameterTypes() {
@Override
@Nullable
public String[] getParameterNames() {
- if (this.parameterNames == null) {
- this.parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
+ String[] parameterNames = this.parameterNames;
+ if (parameterNames == null) {
+ parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
+ this.parameterNames = parameterNames;
}
- return this.parameterNames;
+ return parameterNames;
}
@Override
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java
index e76156bf8265..58f3c23b459f 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -103,10 +103,11 @@ private boolean compiledByAjc(Class> clazz) {
@Override
public void validate(Class> aspectClass) throws AopConfigException {
// If the parent has the annotation and isn't abstract it's an error
- if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null &&
- !Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) {
+ Class> superclass = aspectClass.getSuperclass();
+ if (superclass.getAnnotation(Aspect.class) != null &&
+ !Modifier.isAbstract(superclass.getModifiers())) {
throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" +
- aspectClass.getSuperclass().getName() + "]");
+ superclass.getName() + "]");
}
AjType> ajType = AjTypeSystem.getAjType(aspectClass);
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java
index a3e73ed063e2..048cc603ccc1 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java
@@ -127,9 +127,9 @@ public AspectMetadata(Class> aspectClass, String aspectName) {
*/
private String findPerClause(Class> aspectClass) {
String str = aspectClass.getAnnotation(Aspect.class).value();
- str = str.substring(str.indexOf('(') + 1);
- str = str.substring(0, str.length() - 1);
- return str;
+ int beginIndex = str.indexOf('(') + 1;
+ int endIndex = str.length() - 1;
+ return str.substring(beginIndex, endIndex);
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java
index 9160c6480026..d5fefcec81d0 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java
@@ -56,7 +56,7 @@ public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInst
* introspect to create AJType metadata using the type returned for the
* given bean name from the BeanFactory.
* @param beanFactory the BeanFactory to obtain instance(s) from
- * @param name name of the bean
+ * @param name the name of the bean
*/
public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name) {
this(beanFactory, name, null);
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java
index 85adb1daa7a4..8896f990ecbb 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -97,7 +97,7 @@ public List buildAspectJAdvisors() {
}
// We must be careful not to instantiate beans eagerly as in this case they
// would be cached by the Spring container but would not have been weaved.
- Class> beanType = this.beanFactory.getType(beanName);
+ Class> beanType = this.beanFactory.getType(beanName, false);
if (beanType == null) {
continue;
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
index 7bf8cec70540..5355b2bbb379 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 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.
@@ -56,14 +56,15 @@
/**
* Factory that can create Spring AOP Advisors given AspectJ classes from
- * classes honoring the AspectJ 5 annotation syntax, using reflection to
- * invoke the corresponding advice methods.
+ * classes honoring AspectJ's annotation syntax, using reflection to invoke the
+ * corresponding advice methods.
*
* @author Rod Johnson
* @author Adrian Colyer
* @author Juergen Hoeller
* @author Ramnivas Laddad
* @author Phillip Webb
+ * @author Sam Brannen
* @since 2.0
*/
@SuppressWarnings("serial")
@@ -72,13 +73,17 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
private static final Comparator METHOD_COMPARATOR;
static {
+ // Note: although @After is ordered before @AfterReturning and @AfterThrowing,
+ // an @After advice method will actually be invoked after @AfterReturning and
+ // @AfterThrowing methods due to the fact that AspectJAfterAdvice.invoke(MethodInvocation)
+ // invokes proceed() in a `try` block and only invokes the @After advice method
+ // in a corresponding `finally` block.
Comparator adviceKindComparator = new ConvertingComparator<>(
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
(Converter) method -> {
- AspectJAnnotation> annotation =
- AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
- return (annotation != null ? annotation.getAnnotation() : null);
+ AspectJAnnotation> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
+ return (ann != null ? ann.getAnnotation() : null);
});
Comparator methodNameComparator = new ConvertingComparator<>(Method::getName);
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
@@ -123,7 +128,15 @@ public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstan
List advisors = new ArrayList<>();
for (Method method : getAdvisorMethods(aspectClass)) {
- Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
+ // Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
+ // to getAdvisor(...) to represent the "current position" in the declared methods list.
+ // However, since Java 7 the "current position" is not valid since the JDK no longer
+ // returns declared methods in the order in which they are declared in the source code.
+ // Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
+ // discovered via reflection in order to support reliable advice ordering across JVM launches.
+ // Specifically, a value of 0 aligns with the default value used in
+ // AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
+ Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
}
@@ -154,7 +167,9 @@ private List getAdvisorMethods(Class> aspectClass) {
methods.add(method);
}
}, ReflectionUtils.USER_DECLARED_METHODS);
- methods.sort(METHOD_COMPARATOR);
+ if (methods.size() > 1) {
+ methods.sort(METHOD_COMPARATOR);
+ }
return methods;
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java
index ea21644dc15c..2d2aabd07fae 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -50,27 +50,27 @@ public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProx
/**
- * Sort the rest by AspectJ precedence. If two pieces of advice have
- * come from the same aspect they will have the same order.
- * Advice from the same aspect is then further ordered according to the
+ * Sort the supplied {@link Advisor} instances according to AspectJ precedence.
+ *
If two pieces of advice come from the same aspect, they will have the same
+ * order. Advice from the same aspect is then further ordered according to the
* following rules:
*
- *
if either of the pair is after advice, then the advice declared
- * last gets highest precedence (runs last)
- *
otherwise the advice declared first gets highest precedence (runs first)
+ *
If either of the pair is after advice, then the advice declared
+ * last gets highest precedence (i.e., runs last).
+ *
Otherwise the advice declared first gets highest precedence (i.e., runs
+ * first).
*
*
Important: Advisors are sorted in precedence order, from highest
* precedence to lowest. "On the way in" to a join point, the highest precedence
- * advisor should run first. "On the way out" of a join point, the highest precedence
- * advisor should run last.
+ * advisor should run first. "On the way out" of a join point, the highest
+ * precedence advisor should run last.
*/
@Override
- @SuppressWarnings("unchecked")
protected List sortAdvisors(List advisors) {
List partiallyComparableAdvisors = new ArrayList<>(advisors.size());
- for (Advisor element : advisors) {
+ for (Advisor advisor : advisors) {
partiallyComparableAdvisors.add(
- new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
+ new PartiallyComparableAdvisorHolder(advisor, DEFAULT_PRECEDENCE_COMPARATOR));
}
List sorted = PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
@@ -86,8 +86,8 @@ protected List sortAdvisors(List advisors) {
}
/**
- * Adds an {@link ExposeInvocationInterceptor} to the beginning of the advice chain.
- * These additional advices are needed when using AspectJ expression pointcuts
+ * Add an {@link ExposeInvocationInterceptor} to the beginning of the advice chain.
+ *
This additional advice is needed when using AspectJ pointcut expressions
* and when using AspectJ-style advice.
*/
@Override
@@ -110,7 +110,7 @@ protected boolean shouldSkip(Class> beanClass, String beanName) {
/**
- * Implements AspectJ PartialComparable interface for defining partial orderings.
+ * Implements AspectJ's {@link PartialComparable} interface for defining partial orderings.
*/
private static class PartiallyComparableAdvisorHolder implements PartialComparable {
@@ -140,17 +140,19 @@ public Advisor getAdvisor() {
@Override
public String toString() {
- StringBuilder sb = new StringBuilder();
Advice advice = this.advisor.getAdvice();
- sb.append(ClassUtils.getShortName(advice.getClass()));
- sb.append(": ");
+ StringBuilder sb = new StringBuilder(ClassUtils.getShortName(advice.getClass()));
+ boolean appended = false;
if (this.advisor instanceof Ordered) {
- sb.append("order ").append(((Ordered) this.advisor).getOrder()).append(", ");
+ sb.append(": order = ").append(((Ordered) this.advisor).getOrder());
+ appended = true;
}
if (advice instanceof AbstractAspectJAdvice) {
+ sb.append(!appended ? ": " : ", ");
AbstractAspectJAdvice ajAdvice = (AbstractAspectJAdvice) advice;
+ sb.append("aspect name = ");
sb.append(ajAdvice.getAspectName());
- sb.append(", declaration order ");
+ sb.append(", declaration order = ");
sb.append(ajAdvice.getDeclarationOrder());
}
return sb.toString();
diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java
index 64066f7c04e5..2d243fadc726 100644
--- a/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java
+++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 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.
@@ -27,20 +27,22 @@
/**
* Orders AspectJ advice/advisors by precedence (not invocation order).
*
- *
Given two pieces of advice, {@code a} and {@code b}:
+ *
Given two pieces of advice, {@code A} and {@code B}:
*
- *
if {@code a} and {@code b} are defined in different aspects, then the advice
- * in the aspect with the lowest order value has the highest precedence
- *
if {@code a} and {@code b} are defined in the same aspect, then if one of
- * {@code a} or {@code b} is a form of after advice, then the advice declared last
- * in the aspect has the highest precedence. If neither {@code a} nor {@code b} is
- * a form of after advice, then the advice declared first in the aspect has the
- * highest precedence.
+ *
If {@code A} and {@code B} are defined in different aspects, then the advice
+ * in the aspect with the lowest order value has the highest precedence.
+ *
If {@code A} and {@code B} are defined in the same aspect, if one of
+ * {@code A} or {@code B} is a form of after advice, then the advice declared
+ * last in the aspect has the highest precedence. If neither {@code A} nor {@code B}
+ * is a form of after advice, then the advice declared first in the aspect
+ * has the highest precedence.
*
*
- *
Important: Note that unlike a normal comparator a return of 0 means
- * we don't care about the ordering, not that the two elements must be sorted
- * identically. Used with AspectJ PartialOrder class.
+ *
Important: This comparator is used with AspectJ's
+ * {@link org.aspectj.util.PartialOrder PartialOrder} sorting utility. Thus, unlike
+ * a normal {@link Comparator}, a return value of {@code 0} from this comparator
+ * means we don't care about the ordering, not that the two elements must be sorted
+ * identically.
*
* @author Adrian Colyer
* @author Juergen Hoeller
@@ -59,16 +61,16 @@ class AspectJPrecedenceComparator implements Comparator {
/**
- * Create a default AspectJPrecedenceComparator.
+ * Create a default {@code AspectJPrecedenceComparator}.
*/
public AspectJPrecedenceComparator() {
this.advisorComparator = AnnotationAwareOrderComparator.INSTANCE;
}
/**
- * Create a AspectJPrecedenceComparator, using the given Comparator
+ * Create an {@code AspectJPrecedenceComparator}, using the given {@link Comparator}
* for comparing {@link org.springframework.aop.Advisor} instances.
- * @param advisorComparator the Comparator to use for Advisors
+ * @param advisorComparator the {@code Comparator} to use for advisors
*/
public AspectJPrecedenceComparator(Comparator super Advisor> advisorComparator) {
Assert.notNull(advisorComparator, "Advisor comparator must not be null");
@@ -125,27 +127,21 @@ private boolean declaredInSameAspect(Advisor advisor1, Advisor advisor2) {
getAspectName(advisor1).equals(getAspectName(advisor2)));
}
- private boolean hasAspectName(Advisor anAdvisor) {
- return (anAdvisor instanceof AspectJPrecedenceInformation ||
- anAdvisor.getAdvice() instanceof AspectJPrecedenceInformation);
+ private boolean hasAspectName(Advisor advisor) {
+ return (advisor instanceof AspectJPrecedenceInformation ||
+ advisor.getAdvice() instanceof AspectJPrecedenceInformation);
}
// pre-condition is that hasAspectName returned true
- private String getAspectName(Advisor anAdvisor) {
- AspectJPrecedenceInformation pi = AspectJAopUtils.getAspectJPrecedenceInformationFor(anAdvisor);
- Assert.state(pi != null, "Unresolvable precedence information");
- return pi.getAspectName();
+ private String getAspectName(Advisor advisor) {
+ AspectJPrecedenceInformation precedenceInfo = AspectJAopUtils.getAspectJPrecedenceInformationFor(advisor);
+ Assert.state(precedenceInfo != null, () -> "Unresolvable AspectJPrecedenceInformation for " + advisor);
+ return precedenceInfo.getAspectName();
}
- private int getAspectDeclarationOrder(Advisor anAdvisor) {
- AspectJPrecedenceInformation precedenceInfo =
- AspectJAopUtils.getAspectJPrecedenceInformationFor(anAdvisor);
- if (precedenceInfo != null) {
- return precedenceInfo.getDeclarationOrder();
- }
- else {
- return 0;
- }
+ private int getAspectDeclarationOrder(Advisor advisor) {
+ AspectJPrecedenceInformation precedenceInfo = AspectJAopUtils.getAspectJPrecedenceInformationFor(advisor);
+ return (precedenceInfo != null ? precedenceInfo.getDeclarationOrder() : 0);
}
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AdviceEntry.java b/spring-aop/src/main/java/org/springframework/aop/config/AdviceEntry.java
index f9869a5f9b10..7d9b2ad2dc82 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/AdviceEntry.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/AdviceEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 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.
@@ -30,13 +30,14 @@ public class AdviceEntry implements ParseState.Entry {
/**
- * Creates a new instance of the {@link AdviceEntry} class.
- * @param kind the kind of advice represented by this entry (before, after, around, etc.)
+ * Create a new {@code AdviceEntry} instance.
+ * @param kind the kind of advice represented by this entry (before, after, around)
*/
public AdviceEntry(String kind) {
this.kind = kind;
}
+
@Override
public String toString() {
return "Advice (" + this.kind + ")";
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java b/spring-aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java
index 1f7ba059620a..1a8b45c4823f 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 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.
@@ -30,13 +30,14 @@ public class AdvisorEntry implements ParseState.Entry {
/**
- * Creates a new instance of the {@link AdvisorEntry} class.
+ * Create a new {@code AdvisorEntry} instance.
* @param name the bean name of the advisor
*/
public AdvisorEntry(String name) {
this.name = name;
}
+
@Override
public String toString() {
return "Advisor '" + this.name + "'";
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java b/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java
index 99eb2fa6f594..fa6cc80a1f3c 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 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.
@@ -61,12 +61,12 @@ public class AopNamespaceHandler extends NamespaceHandlerSupport {
*/
@Override
public void init() {
- // In 2.0 XSD as well as in 2.1 XSD.
+ // In 2.0 XSD as well as in 2.5+ XSDs
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
- // Only in 2.0 XSD: moved to context namespace as of 2.1
+ // Only in 2.0 XSD: moved to context namespace in 2.5+
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AspectEntry.java b/spring-aop/src/main/java/org/springframework/aop/config/AspectEntry.java
index 13633bc2a27c..2d4360048cf7 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/AspectEntry.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/AspectEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2007 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.
@@ -34,7 +34,7 @@ public class AspectEntry implements ParseState.Entry {
/**
- * Create a new AspectEntry.
+ * Create a new {@code AspectEntry} instance.
* @param id the id of the aspect element
* @param ref the bean name referenced by this aspect element
*/
@@ -43,6 +43,7 @@ public AspectEntry(String id, String ref) {
this.ref = ref;
}
+
@Override
public String toString() {
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'" : "ref='" + this.ref + "'");
diff --git a/spring-aop/src/main/java/org/springframework/aop/config/PointcutEntry.java b/spring-aop/src/main/java/org/springframework/aop/config/PointcutEntry.java
index 950f8da387e7..e6066c513ee9 100644
--- a/spring-aop/src/main/java/org/springframework/aop/config/PointcutEntry.java
+++ b/spring-aop/src/main/java/org/springframework/aop/config/PointcutEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 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.
@@ -28,14 +28,16 @@ public class PointcutEntry implements ParseState.Entry {
private final String name;
+
/**
- * Creates a new instance of the {@link PointcutEntry} class.
+ * Create a new {@code PointcutEntry} instance.
* @param name the bean name of the pointcut
*/
public PointcutEntry(String name) {
this.name = name;
}
+
@Override
public String toString() {
return "Pointcut '" + this.name + "'";
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java
index 6882e97cc9fd..bb680a3477b5 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java
@@ -189,7 +189,7 @@ else if (!isProxyTargetClass()) {
/**
* Determine a TargetSource for the given target (or TargetSource).
- * @param target target. If this is an implementation of TargetSource it is
+ * @param target the target. If this is an implementation of TargetSource it is
* used as our TargetSource; otherwise it is wrapped in a SingletonTargetSource.
* @return a TargetSource for this object
*/
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java b/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java
index 3deb11bb2a1d..035ae8b30f9a 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/Advised.java
@@ -142,7 +142,7 @@ public interface Advised extends TargetClassAware {
/**
* Remove the advisor at the given index.
- * @param index index of advisor to remove
+ * @param index the index of advisor to remove
* @throws AopConfigException if the index is invalid
*/
void removeAdvisor(int index) throws AopConfigException;
@@ -177,7 +177,7 @@ public interface Advised extends TargetClassAware {
*
Note that the given advice will apply to all invocations on the proxy,
* even to the {@code toString()} method! Use appropriate advice implementations
* or specify appropriate pointcuts to apply to a narrower set of methods.
- * @param advice advice to add to the tail of the chain
+ * @param advice the advice to add to the tail of the chain
* @throws AopConfigException in case of invalid advice
* @see #addAdvice(int, Advice)
* @see org.springframework.aop.support.DefaultPointcutAdvisor
@@ -193,7 +193,7 @@ public interface Advised extends TargetClassAware {
* even to the {@code toString()} method! Use appropriate advice implementations
* or specify appropriate pointcuts to apply to a narrower set of methods.
* @param pos index from 0 (head)
- * @param advice advice to add at the specified position in the advice chain
+ * @param advice the advice to add at the specified position in the advice chain
* @throws AopConfigException in case of invalid advice
*/
void addAdvice(int pos, Advice advice) throws AopConfigException;
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
index a82e77a0f1b9..b4de26beedfe 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -283,16 +283,15 @@ public void removeAdvisor(int index) throws AopConfigException {
"This configuration only has " + this.advisors.size() + " advisors.");
}
- Advisor advisor = this.advisors.get(index);
+ Advisor advisor = this.advisors.remove(index);
if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
// We need to remove introduction interfaces.
- for (int j = 0; j < ia.getInterfaces().length; j++) {
- removeInterface(ia.getInterfaces()[j]);
+ for (Class> ifc : ia.getInterfaces()) {
+ removeInterface(ifc);
}
}
- this.advisors.remove(index);
updateAdvisorArray();
adviceChanged();
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopContext.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopContext.java
index 36b5635d4abf..9653ced6bc8b 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AopContext.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 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.
@@ -67,7 +67,8 @@ public static Object currentProxy() throws IllegalStateException {
Object proxy = currentProxy.get();
if (proxy == null) {
throw new IllegalStateException(
- "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
+ "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and " +
+ "ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.");
}
return proxy;
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
index a501d4614f27..e417f6ebed6b 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
@@ -224,8 +224,8 @@ static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] argu
return new Object[0];
}
if (method.isVarArgs()) {
- Class>[] paramTypes = method.getParameterTypes();
- if (paramTypes.length == arguments.length) {
+ if (method.getParameterCount() == arguments.length) {
+ Class>[] paramTypes = method.getParameterTypes();
int varargIndex = paramTypes.length - 1;
Class> varargType = paramTypes[varargIndex];
if (varargType.isArray()) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
index 1e899671aaaf..f37a4076aec1 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 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.
@@ -49,6 +49,7 @@
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
+import org.springframework.core.KotlinDetector;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -236,7 +237,7 @@ protected Enhancer createEnhancer() {
* validates it if not.
*/
private void validateClassIfNecessary(Class> proxySuperClass, @Nullable ClassLoader proxyClassLoader) {
- if (logger.isWarnEnabled()) {
+ if (logger.isInfoEnabled()) {
synchronized (validatedClasses) {
if (!validatedClasses.containsKey(proxySuperClass)) {
doValidateClass(proxySuperClass, proxyClassLoader,
@@ -258,15 +259,17 @@ private void doValidateClass(Class> proxySuperClass, @Nullable ClassLoader pro
int mod = method.getModifiers();
if (!Modifier.isStatic(mod) && !Modifier.isPrivate(mod)) {
if (Modifier.isFinal(mod)) {
- if (implementsInterface(method, ifcs)) {
+ if (logger.isInfoEnabled() && implementsInterface(method, ifcs)) {
logger.info("Unable to proxy interface-implementing method [" + method + "] because " +
"it is marked as final: Consider using interface-based JDK proxies instead!");
}
- logger.debug("Final method [" + method + "] cannot get proxied via CGLIB: " +
- "Calls to this method will NOT be routed to the target instance and " +
- "might lead to NPEs against uninitialized fields in the proxy instance.");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Final method [" + method + "] cannot get proxied via CGLIB: " +
+ "Calls to this method will NOT be routed to the target instance and " +
+ "might lead to NPEs against uninitialized fields in the proxy instance.");
+ }
}
- else if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod) &&
+ else if (logger.isDebugEnabled() && !Modifier.isPublic(mod) && !Modifier.isProtected(mod) &&
proxyClassLoader != null && proxySuperClass.getClassLoader() != proxyClassLoader) {
logger.debug("Method [" + method + "] is package-visible across different ClassLoaders " +
"and cannot get proxied via CGLIB: Declare this method as public or protected " +
@@ -365,7 +368,7 @@ public int hashCode() {
*/
private static boolean implementsInterface(Method method, Set> ifcs) {
for (Class> ifc : ifcs) {
- if (ClassUtils.hasMethod(ifc, method.getName(), method.getParameterTypes())) {
+ if (ClassUtils.hasMethod(ifc, method)) {
return true;
}
}
@@ -526,7 +529,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
private static class StaticDispatcher implements Dispatcher, Serializable {
@Nullable
- private Object target;
+ private final Object target;
public StaticDispatcher(@Nullable Object target) {
this.target = target;
@@ -552,7 +555,7 @@ public AdvisedDispatcher(AdvisedSupport advised) {
}
@Override
- public Object loadObject() throws Exception {
+ public Object loadObject() {
return this.advised;
}
}
@@ -676,13 +679,19 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
- if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
+ 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 = methodProxy.invoke(target, argsToUse);
+ try {
+ retVal = methodProxy.invoke(target, argsToUse);
+ }
+ catch (CodeGenerationException ex) {
+ CglibMethodInvocation.logFastClassGenerationFailure(method);
+ retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
+ }
}
else {
// We need to create a method invocation...
@@ -734,10 +743,7 @@ public CglibMethodInvocation(Object proxy, @Nullable Object target, Method metho
super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
// Only use method proxy for public methods not derived from java.lang.Object
- this.methodProxy = (Modifier.isPublic(method.getModifiers()) &&
- method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
- !AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method) ?
- methodProxy : null);
+ this.methodProxy = (isMethodProxyCompatible(method) ? methodProxy : null);
}
@Override
@@ -750,10 +756,17 @@ public Object proceed() throws Throwable {
throw ex;
}
catch (Exception ex) {
- if (ReflectionUtils.declaresException(getMethod(), ex.getClass())) {
+ if (ReflectionUtils.declaresException(getMethod(), ex.getClass()) ||
+ KotlinDetector.isKotlinType(getMethod().getDeclaringClass())) {
+ // Propagate original exception if declared on the target method
+ // (with callers expecting it). Always propagate it for Kotlin code
+ // since checked exceptions do not have to be explicitly declared there.
throw ex;
}
else {
+ // Checked exception thrown in the interceptor but not declared on the
+ // target method signature -> apply an UndeclaredThrowableException,
+ // aligned with standard JDK dynamic proxy behavior.
throw new UndeclaredThrowableException(ex);
}
}
@@ -766,10 +779,25 @@ public Object proceed() throws Throwable {
@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.methodProxy != null) {
- return this.methodProxy.invoke(this.target, this.arguments);
+ try {
+ return this.methodProxy.invoke(this.target, this.arguments);
+ }
+ catch (CodeGenerationException ex) {
+ logFastClassGenerationFailure(this.method);
+ }
}
- else {
- return super.invokeJoinpoint();
+ 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);
}
}
}
@@ -872,15 +900,14 @@ public int accept(Method method) {
}
return AOP_PROXY;
}
- Method key = method;
// Check to see if we have fixed interceptor to serve this method.
// Else use the AOP_PROXY.
- if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {
+ if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(method)) {
if (logger.isTraceEnabled()) {
logger.trace("Method has advice and optimizations are enabled: " + method);
}
// We know that we are optimizing so we can use the FixedStaticChainInterceptors.
- int index = this.fixedInterceptorMap.get(key);
+ int index = this.fixedInterceptorMap.get(method);
return (index + this.fixedInterceptorOffset);
}
else {
@@ -959,11 +986,11 @@ public boolean equals(@Nullable Object other) {
return true;
}
- private boolean equalsAdviceClasses(Advisor a, Advisor b) {
+ private static boolean equalsAdviceClasses(Advisor a, Advisor b) {
return (a.getAdvice().getClass() == b.getAdvice().getClass());
}
- private boolean equalsPointcuts(Advisor a, Advisor b) {
+ private static boolean equalsPointcuts(Advisor a, Advisor b) {
// If only one of the advisor (but not both) is PointcutAdvisor, then it is a mismatch.
// Takes care of the situations where an IntroductionAdvisor is used (see SPR-3959).
return (!(a instanceof PointcutAdvisor) ||
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java
index d304397b568d..0a072316f4cc 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 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.
@@ -43,9 +43,11 @@
* @see AdvisedSupport#setProxyTargetClass
* @see AdvisedSupport#setInterfaces
*/
-@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
+ private static final long serialVersionUID = 7930414337282325166L;
+
+
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java
index c9c081c47497..5e1ed1603fa6 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 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.
@@ -21,9 +21,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.Interceptor;
@@ -342,11 +340,8 @@ private synchronized Object newPrototypeInstance() {
// an independent instance of the configuration.
// In this case, no proxy will have an instance of this object's configuration,
// but will have an independent copy.
- if (logger.isTraceEnabled()) {
- logger.trace("Creating copy of prototype ProxyFactoryBean config: " + this);
- }
-
ProxyCreatorSupport copy = new ProxyCreatorSupport(getAopProxyFactory());
+
// The copy needs a fresh advisor chain, and a fresh TargetSource.
TargetSource targetSource = freshTargetSource();
copy.copyConfigurationFrom(this, targetSource, freshAdvisorChain());
@@ -359,9 +354,6 @@ private synchronized Object newPrototypeInstance() {
}
copy.setFrozen(this.freezeProxy);
- if (logger.isTraceEnabled()) {
- logger.trace("Using ProxyCreatorSupport copy: " + copy);
- }
return getProxy(copy.createAopProxy());
}
@@ -429,11 +421,7 @@ private boolean isNamedBeanAnAdvisorOrAdvice(String beanName) {
* are unaffected by such changes.
*/
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
- if (this.advisorChainInitialized) {
- return;
- }
-
- if (!ObjectUtils.isEmpty(this.interceptorNames)) {
+ if (!this.advisorChainInitialized && !ObjectUtils.isEmpty(this.interceptorNames)) {
if (this.beanFactory == null) {
throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
@@ -447,16 +435,12 @@ private synchronized void initializeAdvisorChain() throws AopConfigException, Be
// Materialize interceptor chain from bean names.
for (String name : this.interceptorNames) {
- if (logger.isTraceEnabled()) {
- logger.trace("Configuring advisor or advice '" + name + "'");
- }
-
if (name.endsWith(GLOBAL_SUFFIX)) {
if (!(this.beanFactory instanceof ListableBeanFactory)) {
throw new AopConfigException(
"Can only use global advisors or interceptors with a ListableBeanFactory");
}
- addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
+ addGlobalAdvisors((ListableBeanFactory) this.beanFactory,
name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
}
@@ -473,12 +457,12 @@ private synchronized void initializeAdvisorChain() throws AopConfigException, Be
// Avoid unnecessary creation of prototype bean just for advisor chain initialization.
advice = new PrototypePlaceholderAdvisor(name);
}
- addAdvisorOnChainCreation(advice, name);
+ addAdvisorOnChainCreation(advice);
}
}
- }
- this.advisorChainInitialized = true;
+ this.advisorChainInitialized = true;
+ }
}
@@ -496,11 +480,10 @@ private List freshAdvisorChain() {
if (logger.isDebugEnabled()) {
logger.debug("Refreshing bean named '" + pa.getBeanName() + "'");
}
- // Replace the placeholder with a fresh prototype instance resulting
- // from a getBean() lookup
+ // 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 '" + pa.getBeanName() + "'");
+ throw new IllegalStateException("No BeanFactory available anymore (probably due to " +
+ "serialization) - cannot resolve prototype advisor '" + pa.getBeanName() + "'");
}
Object bean = this.beanFactory.getBean(pa.getBeanName());
Advisor refreshedAdvisor = namedBeanToAdvisor(bean);
@@ -517,28 +500,26 @@ private List freshAdvisorChain() {
/**
* Add all global interceptors and pointcuts.
*/
- private void addGlobalAdvisor(ListableBeanFactory beanFactory, String prefix) {
+ private void addGlobalAdvisors(ListableBeanFactory beanFactory, String prefix) {
String[] globalAdvisorNames =
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class);
String[] globalInterceptorNames =
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class);
- List