diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 5cdaa4ba711..36717b4858e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,5 @@ **Affects:** \ @@ -14,4 +14,4 @@ Thanks for taking the time to create an issue. Please read the following: Issue or Pull Request? Create only one, not both. GitHub treats them as the same. If unsure, start with an issue, and if you submit a pull request later, the issue will be closed as superseded. ---> \ No newline at end of file +--> diff --git a/.gitignore b/.gitignore index 3f904904f76..b08c4536d77 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ out test-output atlassian-ide-plugin.xml .gradletasknamecache + +# VS Code +.vscode/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2295a092882..2400b729b4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ to start a discussion first or have already created an issue, once a pull reques 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 +1. Always check out the `main` 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. @@ -125,10 +125,7 @@ The reference documentation is in the [src/docs/asciidoc](src/docs/asciidoc) dir edit source files, and submit directly from GitHub. When making changes locally, execute `./gradlew asciidoctor` and then browse the result under -`build/asciidoc/html5/index.html`. +`build/docs/ref-docs/html5/index.html`. -Asciidoctor also supports live editing. For more details read -[Editing AsciiDoc with Live Preview](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/). -Note that if you choose the -[System Monitor](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/#using-a-system-monitor) -option, you can find a Guardfile under `src/docs/asciidoc`. +Asciidoctor also supports live editing. For more details see +[AsciiDoc Tooling](https://docs.asciidoctor.org/asciidoctor/latest/tooling/). diff --git a/README.md b/README.md index 43094aa5919..d3d16d18d11 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") +# Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring) This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring". @@ -25,6 +25,10 @@ See the [Micro-Benchmarks](https://github.com/spring-projects/spring-framework/w See the [Build from Source](https://github.com/spring-projects/spring-framework/wiki/Build-from-Source) Wiki page and the [CONTRIBUTING.md](CONTRIBUTING.md) file. +## Continuous Integration Builds + +Information regarding CI builds can be found in the [Spring Framework Concourse pipeline](ci/README.adoc) documentation. + ## Stay in Touch Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [news feed](https://spring.io/blog/category/news). diff --git a/SECURITY.md b/SECURITY.md index 8ed0ff41237..038a36b5653 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 49e9569a618..58352ec8740 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,17 @@ plugins { - id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false - id 'io.spring.nohttp' version '0.0.5.RELEASE' - id 'org.jetbrains.kotlin.jvm' version '1.4.10' apply false - id 'org.jetbrains.dokka' version '0.10.1' apply false - id 'org.asciidoctor.jvm.convert' version '3.1.0' - id 'org.asciidoctor.jvm.pdf' version '3.1.0' - id 'de.undercouch.download' version '4.1.1' - id "io.freefair.aspectj" version '5.1.1' apply false - id "com.github.ben-manes.versions" version '0.28.0' - id "me.champeau.gradle.jmh" version "0.5.0" apply false - id "org.jetbrains.kotlin.plugin.serialization" version "1.4.10" apply false + id 'io.spring.dependency-management' version '1.0.11.RELEASE' apply false + id 'io.spring.nohttp' version '0.0.10' + id "io.freefair.aspectj" version '6.2.0' apply false + id 'org.jetbrains.dokka' version '1.6.10' apply false + id 'org.jetbrains.kotlin.jvm' version '1.5.32' apply false + id "org.jetbrains.kotlin.plugin.serialization" version "1.5.32" apply false + id 'org.asciidoctor.jvm.convert' version '3.3.2' + id 'org.asciidoctor.jvm.pdf' version '3.3.2' + id "org.unbroken-dome.xjc" version '2.0.0' apply false + id "com.github.ben-manes.versions" version '0.39.0' + id "com.github.johnrengelman.shadow" version '7.0.0' apply false + id 'de.undercouch.download' version '4.1.2' + id "me.champeau.jmh" version "0.6.6" apply false } ext { @@ -25,35 +27,36 @@ configure(allprojects) { project -> dependencyManagement { imports { - mavenBom "com.fasterxml.jackson:jackson-bom:2.11.3" - mavenBom "io.netty:netty-bom:4.1.53.Final" - mavenBom "io.projectreactor:reactor-bom:2020.0.0" - mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR8" - mavenBom "io.rsocket:rsocket-bom:1.1.0" - mavenBom "org.eclipse.jetty:jetty-bom:9.4.34.v20201102" - mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.10" - mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.1" - mavenBom "org.junit:junit-bom:5.7.0" + mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6" + mavenBom "io.netty:netty-bom:4.1.74.Final" + mavenBom "io.projectreactor:reactor-bom:2020.0.16" + mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR12" + mavenBom "io.rsocket:rsocket-bom:1.1.1" + mavenBom "org.eclipse.jetty:jetty-bom:9.4.45.v20220203" + mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32" + mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2" + mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2" + mavenBom "org.junit:junit-bom:5.8.2" } dependencies { - dependencySet(group: 'org.apache.logging.log4j', version: '2.13.3') { + dependencySet(group: 'org.apache.logging.log4j', version: '2.17.1') { entry 'log4j-api' entry 'log4j-core' entry 'log4j-jul' entry 'log4j-slf4j-impl' } - dependency "org.slf4j:slf4j-api:1.7.30" + dependency "org.slf4j:slf4j-api:1.7.35" dependency("com.google.code.findbugs:findbugs:3.0.1") { exclude group: "dom4j", name: "dom4j" } dependency "com.google.code.findbugs:jsr305:3.0.2" - dependencySet(group: 'org.aspectj', version: '1.9.6') { + dependencySet(group: 'org.aspectj', version: '1.9.7') { entry 'aspectjrt' entry 'aspectjtools' entry 'aspectjweaver' } - dependencySet(group: 'org.codehaus.groovy', version: '3.0.6') { + dependencySet(group: 'org.codehaus.groovy', version: '3.0.9') { entry 'groovy' entry 'groovy-jsr223' entry 'groovy-templates' // requires findbugs for warning-free compilation @@ -63,23 +66,24 @@ 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.19" - dependency "io.reactivex.rxjava3:rxjava:3.0.7" - dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE" + dependency "io.reactivex.rxjava2:rxjava:2.2.21" + dependency "io.reactivex.rxjava3:rxjava:3.1.3" + dependency "io.smallrye.reactive:mutiny:1.3.1" + dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE" dependency "com.caucho:hessian:4.0.63" - dependency "com.fasterxml:aalto-xml:1.2.2" - dependency("com.fasterxml.woodstox:woodstox-core:6.2.3") { + dependency "com.fasterxml:aalto-xml:1.3.1" + dependency("com.fasterxml.woodstox:woodstox-core:6.2.8") { exclude group: "stax", name: "stax-api" } - dependency "com.google.code.gson:gson:2.8.6" - dependency "com.google.protobuf:protobuf-java-util:3.13.0" + dependency "com.google.code.gson:gson:2.8.9" + dependency "com.google.protobuf:protobuf-java-util:3.19.3" dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4" - dependency("com.thoughtworks.xstream:xstream:1.4.13") { + dependency("com.thoughtworks.xstream:xstream:1.4.18") { exclude group: "xpp3", name: "xpp3_min" exclude group: "xmlpull", name: "xmlpull" } - dependency "org.apache.johnzon:johnzon-jsonb:1.2.8" + dependency "org.apache.johnzon:johnzon-jsonb:1.2.16" dependency("org.codehaus.jettison:jettison:1.3.8") { exclude group: "stax", name: "stax-api" } @@ -88,20 +92,16 @@ configure(allprojects) { project -> entry 'jibx-run' } dependency "org.ogce:xpp3:1.1.6" - dependency "org.yaml:snakeyaml:1.27" - dependencySet(group: 'org.jetbrains.kotlinx', version: '1.0.0') { - entry 'kotlinx-serialization-core' - entry 'kotlinx-serialization-json' - } + dependency "org.yaml:snakeyaml:1.30" - dependency "com.h2database:h2:1.4.200" - dependency "com.github.ben-manes.caffeine:caffeine:2.8.6" - dependency "com.github.librepdf:openpdf:1.3.22" - dependency "com.rometools:rome:1.15.0" + dependency "com.h2database:h2:2.1.210" + dependency "com.github.ben-manes.caffeine:caffeine:2.9.3" + dependency "com.github.librepdf:openpdf:1.3.26" + dependency "com.rometools:rome:1.18.0" dependency "commons-io:commons-io:2.5" - dependency "io.vavr:vavr:0.10.3" + dependency "io.vavr:vavr:0.10.4" dependency "net.sf.jopt-simple:jopt-simple:5.0.4" - dependencySet(group: 'org.apache.activemq', version: '5.16.0') { + dependencySet(group: 'org.apache.activemq', version: '5.16.2') { entry 'activemq-broker' entry('activemq-kahadb-store') { exclude group: "org.springframework", name: "spring-context" @@ -116,55 +116,55 @@ configure(allprojects) { project -> } 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.freemarker:freemarker:2.3.31" + dependency "org.hsqldb:hsqldb:2.5.2" dependency "org.quartz-scheduler:quartz:2.3.2" dependency "org.codehaus.fabric3.api:commonj:1.1.0" dependency "net.sf.ehcache:ehcache:2.10.6" dependency "org.ehcache:jcache:1.0.1" dependency "org.ehcache:ehcache:3.4.0" - dependency "org.hibernate:hibernate-core:5.4.23.Final" - dependency "org.hibernate:hibernate-validator:6.1.6.Final" - dependency "org.webjars:webjars-locator-core:0.46" + dependency "org.hibernate:hibernate-core:5.4.33.Final" + dependency "org.hibernate:hibernate-validator:6.2.2.Final" + dependency "org.webjars:webjars-locator-core:0.48" dependency "org.webjars:underscorejs:1.8.3" - dependencySet(group: 'org.apache.tomcat', version: '9.0.39') { + dependencySet(group: 'org.apache.tomcat', version: '9.0.58') { entry 'tomcat-util' entry('tomcat-websocket') { - exclude group: "org.apache.tomcat", name: "tomcat-websocket-api" exclude group: "org.apache.tomcat", name: "tomcat-servlet-api" + exclude group: "org.apache.tomcat", name: "tomcat-websocket-api" } } - dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.39') { + dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.58') { entry 'tomcat-embed-core' entry 'tomcat-embed-websocket' } - dependencySet(group: 'io.undertow', version: '2.2.2.Final') { + dependencySet(group: 'io.undertow', version: '2.2.16.Final') { entry 'undertow-core' + entry('undertow-servlet') { + exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec" + exclude group: "org.jboss.spec.javax.annotation", name: "jboss-annotations-api_1.3_spec" + } entry('undertow-websockets-jsr') { exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec" } - entry('undertow-servlet') { - exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_3.1_spec" - exclude group: "org.jboss.spec.javax.annotation", name: "jboss-annotations-api_1.2_spec" - } } - dependencySet(group: 'com.squareup.okhttp3', version: '3.14.9') { - entry 'okhttp' - entry 'mockwebserver' - } - dependency("org.apache.httpcomponents:httpclient:4.5.12") { + dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.10" + dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.3' + dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3' + dependency("org.apache.httpcomponents:httpclient:4.5.13") { exclude group: "commons-logging", name: "commons-logging" } - dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") { + dependency("org.apache.httpcomponents:httpasyncclient:4.1.5") { exclude group: "commons-logging", name: "commons-logging" } - dependency 'org.apache.httpcomponents.client5:httpclient5:5.0.3' - dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.0.2' - dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.4" + dependencySet(group: 'com.squareup.okhttp3', version: '3.14.9') { + entry 'okhttp' + entry 'mockwebserver' + } - dependency "org.jruby:jruby:9.2.13.0" + dependency "org.jruby:jruby:9.2.20.1" dependency "org.python:jython-standalone:2.7.1" dependency "org.mozilla:rhino:1.7.11" @@ -181,34 +181,35 @@ configure(allprojects) { project -> exclude group: "dom4j", name: "dom4j" } - dependency("junit:junit:4.13.1") { + dependency("junit:junit:4.13.2") { exclude group: "org.hamcrest", name: "hamcrest-core" } dependency("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1") { exclude group: "junit", name: "junit" } - dependency "org.testng:testng:7.3.0" + dependency "org.testng:testng:7.4.0" + dependency "org.junit.support:testng-engine:1.0.1" dependency "org.hamcrest:hamcrest:2.1" dependency "org.awaitility:awaitility:3.1.6" - dependency "org.assertj:assertj-core:3.18.0" - dependencySet(group: 'org.xmlunit', version: '2.6.2') { + dependency "org.assertj:assertj-core:3.22.0" + dependencySet(group: 'org.xmlunit', version: '2.9.0') { entry 'xmlunit-assertj' entry('xmlunit-matchers') { exclude group: "org.hamcrest", name: "hamcrest-core" } } - dependencySet(group: 'org.mockito', version: '3.6.0') { + dependencySet(group: 'org.mockito', version: '4.3.1') { entry('mockito-core') { exclude group: "org.hamcrest", name: "hamcrest-core" } entry 'mockito-junit-jupiter' } - dependency "io.mockk:mockk:1.10.0" + dependency "io.mockk:mockk:1.12.1" - dependency("net.sourceforge.htmlunit:htmlunit:2.44.0") { + dependency("net.sourceforge.htmlunit:htmlunit:2.58.0") { exclude group: "commons-logging", name: "commons-logging" } - dependency("org.seleniumhq.selenium:htmlunit-driver:2.44.0") { + dependency("org.seleniumhq.selenium:htmlunit-driver:2.58.0") { exclude group: "commons-logging", name: "commons-logging" } dependency("org.seleniumhq.selenium:selenium-java:3.141.59") { @@ -216,7 +217,7 @@ configure(allprojects) { project -> exclude group: "io.netty", name: "netty" } dependency "org.skyscreamer:jsonassert:1.5.0" - dependency "com.jayway.jsonpath:json-path:2.4.0" + dependency "com.jayway.jsonpath:json-path:2.6.0" dependency "org.bouncycastle:bcpkix-jdk15on:1.66" dependencySet(group: 'org.apache.tiles', version: '3.0.8') { @@ -236,8 +237,8 @@ configure(allprojects) { project -> dependency "com.ibm.websphere:uow:6.0.2.17" dependency "com.jamonapi:jamon:2.82" - dependency "joda-time:joda-time:2.10.6" - dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7" + dependency "joda-time:joda-time:2.10.13" + dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10" dependency "org.javamoney:moneta:1.3" dependency "com.sun.activation:javax.activation:1.2.0" @@ -308,23 +309,23 @@ configure([rootProject] + javaProjects) { project -> 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/toolchains.gradle" apply from: "${rootDir}/gradle/ide.gradle" pluginManager.withPlugin("kotlin") { apply plugin: "org.jetbrains.dokka" + apply from: "${rootDir}/gradle/docs-dokka.gradle" + compileKotlin { kotlinOptions { - jvmTarget = "1.8" languageVersion = "1.3" apiVersion = "1.3" - freeCompilerArgs = ["-Xjsr305=strict"] + freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings", "-Xopt-in=kotlin.RequiresOptIn"] allWarningsAsErrors = true } } compileTestKotlin { kotlinOptions { - jvmTarget = "1.8" freeCompilerArgs = ["-Xjsr305=strict"] } } @@ -339,35 +340,36 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "8.36.2" + toolVersion = "9.3" configDirectory.set(rootProject.file("src/checkstyle")) } dependencies { - testCompile("org.junit.jupiter:junit-jupiter-api") - testCompile("org.junit.jupiter:junit-jupiter-params") - testCompile("org.mockito:mockito-core") - testCompile("org.mockito:mockito-junit-jupiter") - testCompile("io.mockk:mockk") - testCompile("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.junit.jupiter:junit-jupiter-params") + testImplementation("org.junit.platform:junit-platform-suite-api") + testImplementation("org.mockito:mockito-core") + testImplementation("org.mockito:mockito-junit-jupiter") + testImplementation("io.mockk:mockk") + testImplementation("org.assertj:assertj-core") // Pull in the latest JUnit 5 Launcher API to ensure proper support in IDEs. - testRuntime("org.junit.platform:junit-platform-launcher") - testRuntime("org.junit.jupiter:junit-jupiter-engine") - testRuntime("org.apache.logging.log4j:log4j-core") - testRuntime("org.apache.logging.log4j:log4j-slf4j-impl") - testRuntime("org.apache.logging.log4j:log4j-jul") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testRuntimeOnly("org.junit.platform:junit-platform-suite-engine") + testRuntimeOnly("org.apache.logging.log4j:log4j-core") + testRuntimeOnly("org.apache.logging.log4j:log4j-jul") + testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j-impl") // JSR-305 only used for non-required meta-annotations compileOnly("com.google.code.findbugs:jsr305") testCompileOnly("com.google.code.findbugs:jsr305") - checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.15") + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.31") } ext.javadocLinks = [ "https://docs.oracle.com/javase/8/docs/api/", "https://docs.oracle.com/javaee/7/api/", "https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ - "https://www.ibm.com/support/knowledgecenter/SS7JFU_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", - "https://glassfish.java.net/nonav/docs/v3/api/", + "https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", "https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", "https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/", "https://tiles.apache.org/tiles-request/apidocs/", @@ -378,10 +380,21 @@ configure([rootProject] + javaProjects) { project -> "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://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/", "https://projectreactor.io/docs/test/release/api/", - "https://junit.org/junit4/javadoc/4.13.1/", - "https://junit.org/junit5/docs/5.7.0/api/" + "https://junit.org/junit4/javadoc/4.13.2/", + // Disabling linking to JUnit 5.8.2, since the `package-list` file no longer exists due to + // https://github.com/junit-team/junit5/commit/67ad4e545518b0ce2b0e7c96df31a669866d5003. + // "https://junit.org/junit5/docs/5.8.2/api/", + "https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/", + "https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/", + "https://r2dbc.io/spec/0.8.5.RELEASE/api/", + // The external Javadoc link for JSR 305 must come last to ensure that types from + // JSR 250 (such as @PostConstruct) are still supported. This is due to the fact + // that JSR 250 and JSR 305 both define types in javax.annotation, which results + // in a split package, and the javadoc tool does not support split packages + // across multiple external Javadoc sites. + "https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/" ] as String[] } @@ -392,7 +405,6 @@ configure(moduleProjects) { project -> configure(rootProject) { description = "Spring Framework" - apply plugin: "groovy" apply plugin: "kotlin" apply plugin: "io.spring.nohttp" apply plugin: 'org.springframework.build.api-diff' diff --git a/buildSrc/README.md b/buildSrc/README.md index f48339e6d61..ada387cf714 100644 --- a/buildSrc/README.md +++ b/buildSrc/README.md @@ -8,16 +8,10 @@ They are declared in the `build.gradle` file in this folder. ### Compiler conventions 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: - -``` -./gradlew test -PjavaSourceVersion=11 -``` ## Build Plugins -## Optional dependencies +### Optional dependencies The `org.springframework.build.optional-dependencies` plugin creates a new `optional` Gradle configuration - it adds the dependencies to the project's compile and runtime classpath @@ -25,7 +19,7 @@ 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. -## API Diff +### 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 diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 03f629496a2..6f5712565d8 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -8,8 +8,7 @@ repositories { } dependencies { - implementation "me.champeau.gradle:japicmp-gradle-plugin:0.2.8" - implementation "com.google.guava:guava:28.2-jre" // required by japicmp-gradle-plugin + implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0" } gradlePlugin { 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 db51666f74b..f2424c549e5 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-2020 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. @@ -20,31 +20,22 @@ import java.util.Arrays; import java.util.List; -import org.gradle.api.JavaVersion; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.compile.JavaCompile; /** * {@link Plugin} that applies conventions for compiling Java sources in Spring Framework. - *

One can override the default Java source compatibility version - * with a dedicated property on the CLI: {@code "./gradlew test -PjavaSourceVersion=11"}. * * @author Brian Clozel * @author Sam Brannen + * @author Sebastien Deleuze */ 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 final String JAVA_SOURCE_VERSION_PROPERTY = "javaSourceVersion"; - - public static final JavaVersion DEFAULT_COMPILER_VERSION = JavaVersion.VERSION_1_8; - private static final List COMPILER_ARGS; private static final List TEST_COMPILER_ARGS; @@ -53,7 +44,8 @@ public class CompilerConventionsPlugin implements Plugin { List commonCompilerArgs = Arrays.asList( "-Xlint:serial", "-Xlint:cast", "-Xlint:classfile", "-Xlint:dep-ann", "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", - "-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options" + "-Xlint:path", "-Xlint:processing", "-Xlint:static", "-Xlint:try", "-Xlint:-options", + "-parameters" ); COMPILER_ARGS = new ArrayList<>(); COMPILER_ARGS.addAll(commonCompilerArgs); @@ -64,12 +56,12 @@ public class CompilerConventionsPlugin implements Plugin { TEST_COMPILER_ARGS = new ArrayList<>(); TEST_COMPILER_ARGS.addAll(commonCompilerArgs); TEST_COMPILER_ARGS.addAll(Arrays.asList("-Xlint:-varargs", "-Xlint:-fallthrough", "-Xlint:-rawtypes", - "-Xlint:-deprecation", "-Xlint:-unchecked", "-parameters")); + "-Xlint:-deprecation", "-Xlint:-unchecked")); } @Override public void apply(Project project) { - project.getPlugins().withType(JavaPlugin.class, javaPlugin -> applyJavaCompileConventions(project)); + project.getPlugins().withType(JavaLibraryPlugin.class, javaPlugin -> applyJavaCompileConventions(project)); } /** @@ -79,15 +71,6 @@ public void apply(Project project) { */ private void applyJavaCompileConventions(Project project) { JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class); - if (project.hasProperty(JAVA_SOURCE_VERSION_PROPERTY)) { - JavaVersion javaSourceVersion = JavaVersion.toVersion(project.property(JAVA_SOURCE_VERSION_PROPERTY)); - java.setSourceCompatibility(javaSourceVersion); - } - else { - java.setSourceCompatibility(DEFAULT_COMPILER_VERSION); - } - java.setTargetCompatibility(DEFAULT_COMPILER_VERSION); - project.getTasks().withType(JavaCompile.class) .matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME)) .forEach(compileTask -> { diff --git a/buildSrc/src/main/java/org/springframework/build/optional/OptionalDependenciesPlugin.java b/buildSrc/src/main/java/org/springframework/build/optional/OptionalDependenciesPlugin.java index 55537c2d825..a7e7101daee 100644 --- a/buildSrc/src/main/java/org/springframework/build/optional/OptionalDependenciesPlugin.java +++ b/buildSrc/src/main/java/org/springframework/build/optional/OptionalDependenciesPlugin.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. @@ -19,6 +19,7 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSetContainer; @@ -28,7 +29,7 @@ /** * A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new * {@code optional} configuration. The {@code optional} configuration is part of the - * project's compile and runtime classpath's but does not affect the classpath of + * project's compile and runtime classpaths but does not affect the classpath of * dependent projects. * * @author Andy Wilkinson @@ -43,22 +44,16 @@ public class OptionalDependenciesPlugin implements Plugin { @Override public void apply(Project project) { Configuration optional = project.getConfigurations().create("optional"); + optional.setCanBeConsumed(false); + optional.setCanBeResolved(false); project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { - SourceSetContainer sourceSets = project.getConvention() - .getPlugin(JavaPluginConvention.class).getSourceSets(); + SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class) + .getSourceSets(); sourceSets.all((sourceSet) -> { - sourceSet.setCompileClasspath( - sourceSet.getCompileClasspath().plus(optional)); - sourceSet.setRuntimeClasspath( - sourceSet.getRuntimeClasspath().plus(optional)); + project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(optional); + project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(optional); }); }); - project.getPlugins().withType(EclipsePlugin.class, (eclipePlugin) -> { - project.getExtensions().getByType(EclipseModel.class) - .classpath((classpath) -> { - classpath.getPlusConfigurations().add(optional); - }); - }); } } \ No newline at end of file diff --git a/ci/README.adoc b/ci/README.adoc index c2c154acca9..cb617637d9b 100644 --- a/ci/README.adoc +++ b/ci/README.adoc @@ -1,7 +1,8 @@ == Spring Framework Concourse pipeline -The Spring Framework is using 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. +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.3.x[Spring Framework 5.3.x]. === Setting up your development environment @@ -25,13 +26,17 @@ spring https://ci.spring.io spring-framework Wed, 25 Mar 20 ---- === 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 @@ -41,6 +46,7 @@ You'll find in this folder the following resources: === 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] @@ -48,4 +54,4 @@ The pipeline can be deployed using the following command: $ fly -t spring set-pipeline -p spring-framework-5.3.x -c ci/pipeline.yml -l ci/parameters.yml ---- -NOTE: This assumes that you have credhub integration configured with the appropriate secrets. \ No newline at end of file +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 index 248e58db739..a029e25582e 100644 --- a/ci/config/changelog-generator.yml +++ b/ci/config/changelog-generator.yml @@ -4,7 +4,7 @@ changelog: - title: ":star: New Features" labels: - "type: enhancement" - - title: ":beetle: Bug Fixes" + - title: ":lady_beetle: Bug Fixes" labels: - "type: bug" - "type: regression" diff --git a/ci/config/release-scripts.yml b/ci/config/release-scripts.yml index 032a42d6716..d31f8cba00d 100644 --- a/ci/config/release-scripts.yml +++ b/ci/config/release-scripts.yml @@ -1,9 +1,10 @@ logging: level: io.spring.concourse: DEBUG -distribute: - optional-deployments: - - '.*\\.zip' spring: main: - banner-mode: off \ No newline at end of file + banner-mode: off +sonatype: + exclude: + - 'build-info\.json' + - '.*\.zip' diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile new file mode 100644 index 00000000000..edc9eac649b --- /dev/null +++ b/ci/images/ci-image/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:focal-20220113 + +ADD setup.sh /setup.sh +ADD get-jdk-url.sh /get-jdk-url.sh +RUN ./setup.sh java8 + +ENV JAVA_HOME /opt/openjdk/java8 +ENV JDK11 /opt/openjdk/java11 +ENV JDK17 /opt/openjdk/java17 + +ENV PATH $JAVA_HOME/bin:$PATH diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index 25a9daea4cd..d090494f662 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,16 +3,16 @@ set -e case "$1" in java8) - echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u265-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u265b01.tar.gz" + echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz" ;; java11) - echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.8%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.8_10.tar.gz" + 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" ;; - java14) - echo "https://github.com/AdoptOpenJDK/openjdk14-binaries/releases/download/jdk-14.0.2%2B12/OpenJDK14U-jdk_x64_linux_hotspot_14.0.2_12.tar.gz" - ;; - java15) - echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15%2B36/OpenJDK15U-jdk_x64_linux_hotspot_15_36.tar.gz" + java17) + echo "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/OpenJDK17U-jdk_x64_linux_hotspot_17.0.2_8.tar.gz" + ;; + java18) + echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk18-2022-02-12-08-06-beta/OpenJDK18-jdk_x64_linux_hotspot_2022-02-12-08-06.tar.gz" ;; *) echo $"Unknown java version" diff --git a/ci/images/setup.sh b/ci/images/setup.sh index ffc3d86d89d..3e1e0bc05c2 100755 --- a/ci/images/setup.sh +++ b/ci/images/setup.sh @@ -5,24 +5,34 @@ set -ex # UTILS ########################################################### +export DEBIAN_FRONTEND=noninteractive apt-get update -apt-get install --no-install-recommends -y ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig +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.3/concourse-java.sh > /opt/concourse-java.sh +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.2.1/concourse-release-scripts-0.2.1.jar +curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.3/concourse-release-scripts-0.3.3.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 +pushd /opt/openjdk > /dev/null +for jdk in java8 java11 java17 +do + JDK_URL=$( /get-jdk-url.sh $jdk ) + mkdir $jdk + pushd $jdk > /dev/null + curl -L ${JDK_URL} | tar zx --strip-components=1 + test -f bin/java + test -f bin/javac + popd > /dev/null +done +popd ########################################################### # GRADLE ENTERPRISE diff --git a/ci/images/spring-framework-ci-image/Dockerfile b/ci/images/spring-framework-ci-image/Dockerfile deleted file mode 100644 index 2e237054650..00000000000 --- a/ci/images/spring-framework-ci-image/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM ubuntu:bionic-20200713 - -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/spring-framework-jdk11-ci-image/Dockerfile b/ci/images/spring-framework-jdk11-ci-image/Dockerfile deleted file mode 100644 index 29ac3967755..00000000000 --- a/ci/images/spring-framework-jdk11-ci-image/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM ubuntu:bionic-20200713 - -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/spring-framework-jdk14-ci-image/Dockerfile b/ci/images/spring-framework-jdk14-ci-image/Dockerfile deleted file mode 100644 index bd7a467de22..00000000000 --- a/ci/images/spring-framework-jdk14-ci-image/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM ubuntu:bionic-20200713 - -ADD setup.sh /setup.sh -ADD get-jdk-url.sh /get-jdk-url.sh -RUN ./setup.sh java14 - -ENV JAVA_HOME /opt/openjdk -ENV PATH $JAVA_HOME/bin:$PATH diff --git a/ci/images/spring-framework-jdk15-ci-image/Dockerfile b/ci/images/spring-framework-jdk15-ci-image/Dockerfile deleted file mode 100644 index c3b544e01cc..00000000000 --- a/ci/images/spring-framework-jdk15-ci-image/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM ubuntu:bionic-20200713 - -ADD setup.sh /setup.sh -ADD get-jdk-url.sh /get-jdk-url.sh -RUN ./setup.sh java15 - -ENV JAVA_HOME /opt/openjdk -ENV PATH $JAVA_HOME/bin:$PATH diff --git a/ci/parameters.yml b/ci/parameters.yml index 3e09f785ebf..7f26578d27a 100644 --- a/ci/parameters.yml +++ b/ci/parameters.yml @@ -1,14 +1,10 @@ -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: "master" +branch: "5.3.x" +milestone: "5.3.x" build-name: "spring-framework" pipeline-name: "spring-framework" concourse-url: "https://ci.spring.io/" -bintray-subject: "spring" -bintray-repo: "jars" -task-timeout: 1h00m \ No newline at end of file +task-timeout: 1h00m diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 38e7a4f4d8b..eb8de81ea1e 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -1,21 +1,29 @@ 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)) - bintray-task-params: &bintray-task-params - BINTRAY_SUBJECT: ((bintray-subject)) - BINTRAY_REPO: ((bintray-repo)) - BINTRAY_USERNAME: ((bintray-username)) - BINTRAY_API_KEY: ((bintray-api-key)) + build-project-task-params: &build-project-task-params + BRANCH: ((branch)) + <<: *gradle-enterprise-task-params docker-resource-source: &docker-resource-source username: ((docker-hub-username)) password: ((docker-hub-password)) - tag: 5.3.x - 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)) + tag: ((milestone)) slack-fail-params: &slack-fail-params text: > :concourse-failed: @@ -24,9 +32,6 @@ anchors: silent: true icon_emoji: ":concourse:" username: concourse-ci - sonatype-task-params: &sonatype-task-params - SONATYPE_USER_TOKEN: ((sonatype-user-token)) - SONATYPE_PASSWORD_TOKEN: ((sonatype-user-token-password)) changelog-task-params: &changelog-task-params name: generated-changelog/tag tag: generated-changelog/tag @@ -37,30 +42,36 @@ anchors: resource_types: - name: artifactory-resource - type: docker-image + type: registry-image source: repository: springio/artifactory-resource - tag: 0.0.12 + 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: docker-image + type: registry-image source: repository: dpb587/github-status-resource tag: master +- name: pull-request + type: registry-image + source: + repository: teliaoss/github-pr-resource + tag: v0.23.0 - name: slack-notification - type: docker-image + type: registry-image source: repository: cfcommunity/slack-notification-resource tag: latest - resources: - name: git-repo type: git icon: github source: - uri: ((github-repo)) - username: ((github-username)) - password: ((github-password)) - branch: ((branch)) + <<: *git-repo-resource-source - name: every-morning type: time icon: alarm @@ -75,30 +86,12 @@ resources: uri: ((github-repo)) branch: ((branch)) paths: ["ci/images/*"] -- name: spring-framework-ci-image - type: docker-image - icon: docker - source: - <<: *docker-resource-source - repository: ((docker-hub-organization))/spring-framework-ci-image -- name: spring-framework-jdk11-ci-image +- name: ci-image type: docker-image icon: docker source: <<: *docker-resource-source - repository: ((docker-hub-organization))/spring-framework-jdk11-ci-image -- name: spring-framework-jdk14-ci-image - type: docker-image - icon: docker - source: - <<: *docker-resource-source - repository: ((docker-hub-organization))/spring-framework-jdk14-ci-image -- name: spring-framework-jdk15-ci-image - type: docker-image - icon: docker - source: - <<: *docker-resource-source - repository: ((docker-hub-organization))/spring-framework-jdk15-ci-image + repository: ((docker-hub-organization))/spring-framework-ci - name: artifactory-repo type: artifactory-resource icon: package-variant @@ -107,6 +100,14 @@ resources: username: ((artifactory-username)) password: ((artifactory-password)) build_name: ((build-name)) +- name: git-pull-request + type: pull-request + icon: source-pull + source: + access_token: ((github-ci-pull-request-token)) + repository: ((github-repo-name)) + base_branch: ((branch)) + ignore_paths: ["ci/*"] - name: repo-status-build type: github-status-resource icon: eye-check-outline @@ -123,22 +124,14 @@ resources: access_token: ((github-ci-status-token)) branch: ((branch)) context: jdk11-build -- name: repo-status-jdk14-build - type: github-status-resource - icon: eye-check-outline - source: - repository: ((github-repo-name)) - access_token: ((github-ci-status-token)) - branch: ((branch)) - context: jdk14-build -- name: repo-status-jdk15-build +- name: repo-status-jdk17-build type: github-status-resource icon: eye-check-outline source: repository: ((github-repo-name)) access_token: ((github-ci-status-token)) branch: ((branch)) - context: jdk15-build + context: jdk17-build - name: slack-alert type: slack-notification icon: slack @@ -161,47 +154,33 @@ resources: repository: spring-framework access_token: ((github-ci-release-token)) pre_release: false - jobs: -- name: build-spring-framework-ci-images +- name: build-ci-images plan: - get: ci-images-git-repo trigger: true - in_parallel: - - put: spring-framework-ci-image - params: - build: ci-images-git-repo/ci/images - dockerfile: ci-images-git-repo/ci/images/spring-framework-ci-image/Dockerfile - - put: spring-framework-jdk11-ci-image - params: - build: ci-images-git-repo/ci/images - dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk11-ci-image/Dockerfile - - put: spring-framework-jdk14-ci-image - params: - build: ci-images-git-repo/ci/images - dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk14-ci-image/Dockerfile - - put: spring-framework-jdk15-ci-image + - put: ci-image params: build: ci-images-git-repo/ci/images - dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk15-ci-image/Dockerfile + dockerfile: ci-images-git-repo/ci/images/ci-image/Dockerfile - name: build serial: true public: true plan: - - get: spring-framework-ci-image + - 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 privileged: true timeout: ((task-timeout)) - image: spring-framework-ci-image - file: git-repo/ci/tasks/build-project.yml params: - BRANCH: ((branch)) - <<: *gradle-enterprise-task-params + <<: *build-project-task-params on_failure: do: - put: repo-status-build @@ -213,6 +192,8 @@ jobs: 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/$%7BBUILD_TEAM_NAME%7D/pipelines/$%7BBUILD_PIPELINE_NAME%7D/jobs/$%7BBUILD_JOB_NAME%7D/builds/$%7BBUILD_NAME%7D" @@ -244,7 +225,7 @@ jobs: serial: true public: true plan: - - get: spring-framework-jdk11-ci-image + - get: ci-image - get: git-repo - get: every-morning trigger: true @@ -252,13 +233,13 @@ jobs: params: { state: "pending", commit: "git-repo" } - do: - task: check-project + image: ci-image + file: git-repo/ci/tasks/check-project.yml privileged: true timeout: ((task-timeout)) - image: spring-framework-jdk11-ci-image - file: git-repo/ci/tasks/check-project.yml params: - BRANCH: ((branch)) - <<: *gradle-enterprise-task-params + TEST_TOOLCHAIN: 11 + <<: *build-project-task-params on_failure: do: - put: repo-status-jdk11-build @@ -268,70 +249,73 @@ jobs: <<: *slack-fail-params - put: repo-status-jdk11-build params: { state: "success", commit: "git-repo" } -- name: jdk14-build +- name: jdk17-build serial: true public: true plan: - - get: spring-framework-jdk14-ci-image + - get: ci-image - get: git-repo - get: every-morning trigger: true - - put: repo-status-jdk14-build + - put: repo-status-jdk17-build params: { state: "pending", commit: "git-repo" } - do: - task: check-project + image: ci-image + file: git-repo/ci/tasks/check-project.yml privileged: true timeout: ((task-timeout)) - image: spring-framework-jdk14-ci-image - file: git-repo/ci/tasks/check-project.yml params: - BRANCH: ((branch)) - <<: *gradle-enterprise-task-params + TEST_TOOLCHAIN: 15 + <<: *build-project-task-params on_failure: do: - - put: repo-status-jdk14-build + - put: repo-status-jdk17-build params: { state: "failure", commit: "git-repo" } - put: slack-alert params: <<: *slack-fail-params - - put: repo-status-jdk14-build + - put: repo-status-jdk17-build params: { state: "success", commit: "git-repo" } -- name: jdk15-build +- name: build-pull-requests serial: true public: true plan: - - get: spring-framework-jdk15-ci-image + - get: ci-image - get: git-repo - - get: every-morning + resource: git-pull-request trigger: true - - put: repo-status-jdk15-build - params: { state: "pending", commit: "git-repo" } + version: every - do: - - task: check-project - privileged: true - timeout: ((task-timeout)) - image: spring-framework-jdk15-ci-image - file: git-repo/ci/tasks/check-project.yml - params: - BRANCH: ((branch)) - <<: *gradle-enterprise-task-params + - put: git-pull-request + params: + path: git-repo + status: pending + - task: build-pr + image: ci-image + file: git-repo/ci/tasks/build-pr.yml + privileged: true + timeout: ((task-timeout)) + params: + BRANCH: ((branch)) + on_success: + put: git-pull-request + params: + path: git-repo + status: success on_failure: - do: - - put: repo-status-jdk15-build - params: { state: "failure", commit: "git-repo" } - - put: slack-alert - params: - <<: *slack-fail-params - - put: repo-status-jdk15-build - params: { state: "success", commit: "git-repo" } + put: git-pull-request + params: + path: git-repo + status: failure - name: stage-milestone serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo trigger: false - task: stage - image: spring-framework-ci-image + image: ci-image file: git-repo/ci/tasks/stage-version.yml params: RELEASE_TYPE: M @@ -346,7 +330,7 @@ jobs: - name: promote-milestone serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo trigger: false - get: artifactory-repo @@ -356,7 +340,7 @@ jobs: download_artifacts: false save_build_info: true - task: promote - image: spring-framework-ci-image + image: ci-image file: git-repo/ci/tasks/promote-version.yml params: RELEASE_TYPE: M @@ -372,11 +356,11 @@ jobs: - name: stage-rc serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo trigger: false - task: stage - image: spring-framework-ci-image + image: ci-image file: git-repo/ci/tasks/stage-version.yml params: RELEASE_TYPE: RC @@ -391,7 +375,7 @@ jobs: - name: promote-rc serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo trigger: false - get: artifactory-repo @@ -401,7 +385,7 @@ jobs: download_artifacts: false save_build_info: true - task: promote - image: spring-framework-ci-image + image: ci-image file: git-repo/ci/tasks/promote-version.yml params: RELEASE_TYPE: RC @@ -417,11 +401,11 @@ jobs: - name: stage-release serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo trigger: false - task: stage - image: spring-framework-ci-image + image: ci-image file: git-repo/ci/tasks/stage-version.yml params: RELEASE_TYPE: RELEASE @@ -436,26 +420,26 @@ jobs: - name: promote-release serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo trigger: false - get: artifactory-repo trigger: false passed: [stage-release] params: - download_artifacts: false + download_artifacts: true save_build_info: true - task: promote - image: spring-framework-ci-image + image: ci-image file: git-repo/ci/tasks/promote-version.yml params: RELEASE_TYPE: RELEASE <<: *artifactory-task-params - <<: *bintray-task-params -- name: sync-to-maven-central + <<: *sonatype-task-params +- name: create-github-release serial: true plan: - - get: spring-framework-ci-image + - get: ci-image - get: git-repo - get: artifactory-repo trigger: true @@ -463,12 +447,6 @@ jobs: params: download_artifacts: false save_build_info: true - - task: sync-to-maven-central - image: spring-framework-ci-image - file: git-repo/ci/tasks/sync-to-maven-central.yml - params: - <<: *bintray-task-params - <<: *sonatype-task-params - task: generate-changelog file: git-repo/ci/tasks/generate-changelog.yml params: @@ -480,8 +458,10 @@ jobs: groups: - name: "builds" - jobs: ["build", "jdk11-build", "jdk14-build", "jdk15-build"] + jobs: ["build", "jdk11-build", "jdk17-build"] - name: "releases" - jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone","promote-rc", "promote-release", "sync-to-maven-central"] + jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"] - name: "ci-images" - jobs: ["build-spring-framework-ci-images"] + jobs: ["build-ci-images"] +- name: "pull-requests" + jobs: [ "build-pull-requests" ] diff --git a/ci/scripts/build-pr.sh b/ci/scripts/build-pr.sh new file mode 100755 index 00000000000..94c4e8df65b --- /dev/null +++ b/ci/scripts/build-pr.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/check-project.sh b/ci/scripts/check-project.sh index 94c4e8df65b..7f6ca04cea9 100755 --- a/ci/scripts/check-project.sh +++ b/ci/scripts/check-project.sh @@ -4,5 +4,6 @@ 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 +./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK11,JDK15 \ + -PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check popd > /dev/null diff --git a/ci/scripts/generate-changelog.sh b/ci/scripts/generate-changelog.sh index b0bc952a33a..d3d2b97e5db 100755 --- a/ci/scripts/generate-changelog.sh +++ b/ci/scripts/generate-changelog.sh @@ -2,7 +2,7 @@ set -e CONFIG_DIR=git-repo/ci/config -version=$( cat version/version ) +version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' ) java -jar /github-changelog-generator.jar \ --spring.config.location=${CONFIG_DIR}/changelog-generator.yml \ diff --git a/ci/scripts/promote-version.sh b/ci/scripts/promote-version.sh index 020db4f300b..44c5ff626f9 100755 --- a/ci/scripts/promote-version.sh +++ b/ci/scripts/promote-version.sh @@ -6,11 +6,13 @@ 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 promote $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; } +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 \ - distribute $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; } + 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 index 0b285de014b..73c57755451 100755 --- a/ci/scripts/stage-version.sh +++ b/ci/scripts/stage-version.sh @@ -29,8 +29,8 @@ fi echo "Staging $stageVersion (next version will be $nextVersion)" sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties -git config user.name "Spring Buildmaster" > /dev/null -git config user.email "buildmaster@springframework.org" > /dev/null +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 diff --git a/ci/tasks/build-pr.yml b/ci/tasks/build-pr.yml new file mode 100644 index 00000000000..dbf6e9c0cf6 --- /dev/null +++ b/ci/tasks/build-pr.yml @@ -0,0 +1,19 @@ +--- +platform: linux +inputs: +- 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-pr.sh diff --git a/ci/tasks/check-project.yml b/ci/tasks/check-project.yml index ea6d6ddb94c..bea1185231b 100644 --- a/ci/tasks/check-project.yml +++ b/ci/tasks/check-project.yml @@ -10,6 +10,8 @@ caches: params: BRANCH: CI: true + MAIN_TOOLCHAIN: + TEST_TOOLCHAIN: GRADLE_ENTERPRISE_ACCESS_KEY: GRADLE_ENTERPRISE_CACHE_USERNAME: GRADLE_ENTERPRISE_CACHE_PASSWORD: diff --git a/ci/tasks/generate-changelog.yml b/ci/tasks/generate-changelog.yml index 2df097bc298..b3f40278c8e 100755 --- a/ci/tasks/generate-changelog.yml +++ b/ci/tasks/generate-changelog.yml @@ -1,13 +1,13 @@ --- platform: linux image_resource: - type: docker-image + type: registry-image source: repository: springio/github-changelog-generator - tag: '0.0.4' + tag: '0.0.7' inputs: - name: git-repo -- name: version +- name: artifactory-repo outputs: - name: generated-changelog params: diff --git a/ci/tasks/promote-version.yml b/ci/tasks/promote-version.yml index 2da899a0ebe..abdd8fed5c5 100644 --- a/ci/tasks/promote-version.yml +++ b/ci/tasks/promote-version.yml @@ -10,9 +10,9 @@ params: ARTIFACTORY_SERVER: ARTIFACTORY_USERNAME: ARTIFACTORY_PASSWORD: - BINTRAY_SUBJECT: - BINTRAY_REPO: - BINTRAY_USERNAME: - BINTRAY_API_KEY: + SONATYPE_USER: + SONATYPE_PASSWORD: + SONATYPE_URL: + SONATYPE_STAGING_PROFILE_ID: run: path: git-repo/ci/scripts/promote-version.sh diff --git a/ci/tasks/sync-to-maven-central.yml b/ci/tasks/sync-to-maven-central.yml deleted file mode 100644 index a44af5af169..00000000000 --- a/ci/tasks/sync-to-maven-central.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -platform: linux -inputs: -- name: git-repo -- name: artifactory-repo -outputs: -- name: version -params: - BINTRAY_REPO: - BINTRAY_SUBJECT: - BINTRAY_USERNAME: - BINTRAY_API_KEY: - SONATYPE_USER_TOKEN: - SONATYPE_PASSWORD_TOKEN: -run: - path: git-repo/ci/scripts/sync-to-maven-central.sh diff --git a/gradle.properties b/gradle.properties index 61d8962566a..04842d94e7b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=5.3.1-SNAPSHOT +version=5.3.16 org.gradle.jvmargs=-Xmx1536M org.gradle.caching=true org.gradle.parallel=true diff --git a/gradle/custom-java-home.gradle b/gradle/custom-java-home.gradle deleted file mode 100644 index 54d1de1eb8f..00000000000 --- a/gradle/custom-java-home.gradle +++ /dev/null @@ -1,80 +0,0 @@ -// ----------------------------------------------------------------------------- -// -// 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-dokka.gradle b/gradle/docs-dokka.gradle new file mode 100644 index 00000000000..dfe137d9cfe --- /dev/null +++ b/gradle/docs-dokka.gradle @@ -0,0 +1,30 @@ +tasks.findByName("dokkaHtmlPartial")?.configure { + outputDirectory.set(new File(buildDir, "docs/kdoc")) + dokkaSourceSets { + configureEach { + sourceRoots.setFrom(file("src/main/kotlin")) + classpath.from(sourceSets["main"].runtimeClasspath) + externalDocumentationLink { + url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/")) + } + externalDocumentationLink { + url.set(new URL("https://projectreactor.io/docs/core/release/api/")) + } + externalDocumentationLink { + url.set(new URL("https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/")) + } + externalDocumentationLink { + url.set(new URL("https://kotlin.github.io/kotlinx.coroutines/")) + } + externalDocumentationLink { + url.set(new URL("https://javadoc.io/doc/org.hamcrest/hamcrest/2.1/")) + } + externalDocumentationLink { + url.set(new URL("https://javadoc.io/doc/javax.servlet/javax.servlet-api/latest/")) + } + externalDocumentationLink { + url.set(new URL("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/")) + } + } + } +} diff --git a/gradle/docs.gradle b/gradle/docs.gradle index 76ca834b452..b0a356be920 100644 --- a/gradle/docs.gradle +++ b/gradle/docs.gradle @@ -3,7 +3,7 @@ configurations { } dependencies { - asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.3.RELEASE") + asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.5.0") } repositories { @@ -46,8 +46,9 @@ task api(type: Javadoc) { stylesheetFile = file("src/docs/api/stylesheet.css") splitIndex = true links(project.ext.javadocLinks) - addStringOption('Xdoclint:none', '-quiet') - if(JavaVersion.current().isJava9Compatible()) { + addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint + addBooleanOption('Xwerror', true) // fail build on Javadoc warnings + if (JavaVersion.current().isJava9Compatible()) { addBooleanOption('html5', true) } } @@ -61,59 +62,18 @@ task api(type: Javadoc) { /** * Produce KDoc for all Spring Framework modules in "build/docs/kdoc" */ -dokka { - dependsOn { - tasks.getByName("api") - } - - doFirst { - 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 - } - } - } - } - } - } - - outputFormat = "html" - outputDirectory = "$buildDir/docs/kdoc" - - 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/") - } - externalDocumentationLink { - url = new URL("https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/") - } - externalDocumentationLink { - url = new URL("https://r2dbc.io/spec/0.8.3.RELEASE/api/") +pluginManager.withPlugin("kotlin") { + tasks.dokkaHtmlMultiModule.configure { + dependsOn { + tasks.getByName("api") } + moduleName.set("spring-framework") + outputDirectory.set(project.file("$buildDir/docs/kdoc")) } } task downloadResources(type: Download) { - def version = "0.2.2.RELEASE" + def version = "0.2.5" src "https://repo.spring.io/release/io/spring/docresources/" + "spring-doc-resources/$version/spring-doc-resources-${version}.zip" dest project.file("$buildDir/docs/spring-doc-resources.zip") @@ -127,6 +87,8 @@ task extractDocResources(type: Copy, dependsOn: downloadResources) { } asciidoctorj { + def docRoot = 'https://docs.spring.io/' + def docsSpringFramework = "${docRoot}/spring-framework/docs/${project.version}" version = '2.4.1' fatalWarnings ".*" options doctype: 'book', eruby: 'erubis' @@ -134,7 +96,7 @@ asciidoctorj { icons: 'font', idprefix: '', idseparator: '-', - docinfo: 'shared', + docinfo: 'shared,private-header', // https://docs.asciidoctor.org/asciidoctor/latest/docinfo/ revnumber: project.version, sectanchors: '', sectnums: '', @@ -143,7 +105,11 @@ asciidoctorj { 'highlightjs-theme': 'googlecode', stylesdir: 'css/', stylesheet: 'stylesheet.css', - 'spring-version': project.version + 'spring-version': project.version, + 'spring-framework-main-code': 'https://github.com/spring-projects/spring-framework/tree/main', + 'doc-root': docRoot, + 'docs-spring-framework': docsSpringFramework, + 'api-spring-framework': "${docsSpringFramework}/javadoc-api/org/springframework" ]) } @@ -184,7 +150,7 @@ asciidoctorPdf { /** * Zip all docs (API and reference) into a single archive */ -task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokka']) { +task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokkaHtmlMultiModule']) { group = "Distribution" description = "Builds -${archiveClassifier} archive containing api and reference " + "for deployment at https://docs.spring.io/spring-framework/docs." @@ -203,7 +169,7 @@ task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dok from ("$asciidoctorPdf.outputDir") { into "reference/pdf" } - from (dokka) { + from (dokkaHtmlMultiModule.outputDirectory) { into "kdoc-api" } } @@ -260,11 +226,11 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { expand(copyright: new Date().format("yyyy"), version: project.version) } - from(zipTree(docsZip.archivePath)) { + from(zipTree(docsZip.archiveFile)) { into "${baseDir}/docs" } - from(zipTree(schemaZip.archivePath)) { + from(zipTree(schemaZip.archiveFile)) { into "${baseDir}/schema" } diff --git a/gradle/ide.gradle b/gradle/ide.gradle index 554d4b3c543..d5215f2875f 100644 --- a/gradle/ide.gradle +++ b/gradle/ide.gradle @@ -1,7 +1,7 @@ import org.gradle.plugins.ide.eclipse.model.ProjectDependency import org.gradle.plugins.ide.eclipse.model.SourceFolder -apply plugin: "eclipse" +apply plugin: 'eclipse' eclipse.jdt { sourceCompatibility = 1.8 @@ -18,103 +18,71 @@ eclipse.classpath.file.whenMerged { classpath -> if (matcher) { def projectName = matcher[0][1] def path = "/${projectName}" - if(!classpath.entries.find { e -> e instanceof ProjectDependency && e.path == path }) { - def dependency = new ProjectDependency(path) - dependency.exported = true - classpath.entries.add(dependency) + if (!classpath.entries.find { e -> e instanceof ProjectDependency && e.path == path }) { + def recursiveDependency = entry.path.matches('.+/' + projectName + '/build/([^/]+/)+(?:main|test)') + // Avoid recursive dependency on current project. + if (!recursiveDependency) { + classpath.entries.add(new ProjectDependency(path)) + } } classpath.entries.remove(entry) } } + + // Remove any remaining direct depencencies on JARs in the build/libs folder + // except Spring's spring-cglib-repack and spring-objenesis-repack JARs. 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.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 } } } -eclipse.classpath.file.whenMerged { classpath -> - classpath.entries.findAll{ it instanceof SourceFolder }.each { - it.output = "bin/" + it.path.split("/")[1] +eclipse.classpath.file.whenMerged { + entries.findAll{ it instanceof SourceFolder }.each { + it.output = 'bin/' + it.path.split('/')[1] } } // Ensure project dependencies come after 3rd-party libs (SPR-11836) // https://jira.spring.io/browse/SPR-11836 -eclipse.classpath.file.whenMerged { classpath -> - classpath.entries.findAll { it instanceof ProjectDependency }.each { +eclipse.classpath.file.whenMerged { + entries.findAll { it instanceof ProjectDependency }.each { // delete from original position - classpath.entries.remove(it) + entries.remove(it) // append to end of classpath - classpath.entries.add(it) + entries.add(it) } } -// Allow projects to be used as WTP modules -eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature" +// Ensure that JMH sources and resources are treated as test classpath entries +// so that they can see test fixtures. +// https://github.com/melix/jmh-gradle-plugin/issues/157 +eclipse.classpath.file.whenMerged { + entries.findAll { it.path =~ /src\/jmh\/(java|resources)/ }.each { + it.entryAttributes['test'] = 'true' + } +} // Include project specific settings task eclipseSettings(type: Copy) { from rootProject.files( - "src/eclipse/org.eclipse.jdt.ui.prefs", - "src/eclipse/org.eclipse.wst.common.project.facet.core.xml") + 'src/eclipse/org.eclipse.jdt.core.prefs', + 'src/eclipse/org.eclipse.jdt.ui.prefs') into project.file('.settings/') outputs.upToDateWhen { false } } -task eclipseWstComponent(type: Copy) { - from rootProject.files( - "src/eclipse/org.eclipse.wst.common.component") - into project.file('.settings/') - expand(deployname: project.name) - outputs.upToDateWhen { false } -} - -task eclipseJdtPrepare(type: Copy) { - from rootProject.file("src/eclipse/org.eclipse.jdt.core.prefs") - into project.file(".settings/") - outputs.upToDateWhen { false } +task cleanEclipseSettings(type: Delete) { + delete project.file('.settings/org.eclipse.jdt.core.prefs') + delete project.file('.settings/org.eclipse.jdt.ui.prefs') } -task cleanEclipseJdtUi(type: Delete) { - delete project.file(".settings/org.eclipse.jdt.core.prefs") - delete project.file(".settings/org.eclipse.jdt.ui.prefs") - delete project.file(".settings/org.eclipse.wst.common.component") - delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml") -} - -task eclipseBuildship(type: Copy) { - from rootProject.files( - "src/eclipse/org.eclipse.jdt.ui.prefs", - "src/eclipse/org.eclipse.jdt.core.prefs") - into project.file('.settings/') - outputs.upToDateWhen { false } -} - -tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare) -tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi) -tasks["eclipse"].dependsOn(eclipseSettings, eclipseWstComponent) - - -// Filter 'build' folder -eclipse.project.file.withXml { - def node = it.asNode() - - def filteredResources = node.get("filteredResources") - if(filteredResources) { - node.remove(filteredResources) - } - def filterNode = node.appendNode("filteredResources").appendNode("filter") - filterNode.appendNode("id", "1359048889071") - filterNode.appendNode("name", "") - filterNode.appendNode("type", "30") - def matcherNode = filterNode.appendNode("matcher") - matcherNode.appendNode("id", "org.eclipse.ui.ide.multiFilter") - matcherNode.appendNode("arguments", "1.0-projectRelativePath-matches-false-false-build") -} +tasks['eclipse'].dependsOn(eclipseSettings) +tasks['eclipseJdt'].dependsOn(eclipseSettings) +tasks['cleanEclipse'].dependsOn(cleanEclipseSettings) diff --git a/gradle/spring-module.gradle b/gradle/spring-module.gradle index ebf196ff1fa..7628127cb5d 100644 --- a/gradle/spring-module.gradle +++ b/gradle/spring-module.gradle @@ -1,17 +1,42 @@ +apply plugin: 'java-library' apply plugin: 'org.springframework.build.compile' apply plugin: 'org.springframework.build.optional-dependencies' -apply plugin: 'me.champeau.gradle.jmh' +// Uncomment the following for Shadow support in the jmhJar block. +// Currently commented out due to ZipException: archive is not a ZIP archive +// apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'me.champeau.jmh' apply from: "$rootDir/gradle/publications.gradle" dependencies { - jmh 'org.openjdk.jmh:jmh-core:1.23' - jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.23' - jmh 'net.sf.jopt-simple:jopt-simple:4.6' + jmh 'org.openjdk.jmh:jmh-core:1.32' + jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.32' + jmh 'net.sf.jopt-simple:jopt-simple' } + jmh { duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE } +tasks.findByName("processJmhResources").configure { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +jmhJar { + // Uncomment the following for Shadow's Transformer support. + // mergeServiceFiles() + // append('META-INF/spring.handlers') + // append('META-INF/spring.schemas') + // append('META-INF/spring.tooling') + exclude 'LICENSE' + exclude 'THIRD-PARTY' + exclude 'META-INF/license.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE*' + exclude 'META-INF/NOTICE' + exclude 'META-INF/THIRD-PARTY' +} + jar { manifest.attributes["Implementation-Title"] = project.name manifest.attributes["Implementation-Version"] = project.version diff --git a/gradle/toolchains.gradle b/gradle/toolchains.gradle new file mode 100644 index 00000000000..5573efa1a52 --- /dev/null +++ b/gradle/toolchains.gradle @@ -0,0 +1,182 @@ +/** + * Apply the JVM Toolchain conventions + * See https://docs.gradle.org/current/userguide/toolchains.html + * + * One can choose the toolchain to use for compiling the MAIN sources and/or compiling + * and running the TEST sources. These options apply to Java, Kotlin and Groovy sources + * when available. + * {@code "./gradlew check -PmainToolchain=8 -PtestToolchain=11"} will use: + *

+ * + * By default, the build will fall back to using the current JDK and 1.8 language level for all sourceSets. + * + * Gradle will automatically detect JDK distributions in well-known locations. + * The following command will list the detected JDKs on the host. + * {@code + * $ ./gradlew -q javaToolchains + * } + * + * We can also configure ENV variables and let Gradle know about them: + * {@code + * $ echo JDK11 + * /opt/openjdk/java11 + * $ echo JDK15 + * /opt/openjdk/java15 + * $ ./gradlew -Porg.gradle.java.installations.fromEnv=JDK11,JDK15 check + * } + * + * @author Brian Clozel + * @author Sam Brannen + */ + +def mainToolchainConfigured() { + return project.hasProperty('mainToolchain') && project.mainToolchain +} + +def testToolchainConfigured() { + return project.hasProperty('testToolchain') && project.testToolchain +} + +def mainToolchainLanguageVersion() { + if (mainToolchainConfigured()) { + return JavaLanguageVersion.of(project.mainToolchain.toString()) + } + return JavaLanguageVersion.of(8) +} + +def testToolchainLanguageVersion() { + if (testToolchainConfigured()) { + return JavaLanguageVersion.of(project.testToolchain.toString()) + } + return mainToolchainLanguageVersion() +} + +plugins.withType(JavaPlugin) { + // Configure the Java Toolchain if the 'mainToolchain' is configured + if (mainToolchainConfigured()) { + java { + toolchain { + languageVersion = mainToolchainLanguageVersion() + } + } + } + else { + // Fallback to JDK8 + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + } + } + // Configure a specific Java Toolchain for compiling and running tests if the 'testToolchain' property is defined + if (testToolchainConfigured()) { + def testLanguageVersion = testToolchainLanguageVersion() + tasks.withType(JavaCompile).matching { it.name.contains("Test") }.configureEach { + javaCompiler = javaToolchains.compilerFor { + languageVersion = testLanguageVersion + } + } + tasks.withType(Test).configureEach{ + javaLauncher = javaToolchains.launcherFor { + languageVersion = testLanguageVersion + } + } + } +} + +plugins.withType(GroovyPlugin) { + // Fallback to JDK8 + if (!mainToolchainConfigured()) { + compileGroovy { + sourceCompatibility = JavaVersion.VERSION_1_8 + } + } +} + +pluginManager.withPlugin("kotlin") { + // Configure the Kotlin compiler if the 'mainToolchain' property is defined + if (mainToolchainConfigured()) { + def mainLanguageVersion = mainToolchainLanguageVersion() + def compiler = javaToolchains.compilerFor { + languageVersion = mainLanguageVersion + } + // See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm + def javaVersion = mainLanguageVersion.toString() == '8' ? '1.8' : mainLanguageVersion.toString() + compileKotlin { + kotlinOptions { + jvmTarget = javaVersion + jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath + } + } + // Compile the test classes with the same version, 'testToolchain' will override if defined + compileTestKotlin { + kotlinOptions { + jvmTarget = javaVersion + jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath + } + } + } + else { + // Fallback to JDK8 + compileKotlin { + kotlinOptions { + jvmTarget = '1.8' + } + } + compileTestKotlin { + kotlinOptions { + jvmTarget = '1.8' + } + } + } + + if (testToolchainConfigured()) { + def testLanguageVersion = testToolchainLanguageVersion() + def compiler = javaToolchains.compilerFor { + languageVersion = testLanguageVersion + } + // See https://kotlinlang.org/docs/gradle.html#attributes-specific-for-jvm + def javaVersion = testLanguageVersion.toString() == '8' ? '1.8' : testLanguageVersion.toString() + compileTestKotlin { + kotlinOptions { + jvmTarget = javaVersion + jdkHome = compiler.get().metadata.installationPath.asFile.absolutePath + } + } + } +} + +// Configure the JMH plugin to use the toolchain for generating and running JMH bytecode +pluginManager.withPlugin("me.champeau.jmh") { + if (mainToolchainConfigured() || testToolchainConfigured()) { + tasks.matching { it.name.contains('jmh') && it.hasProperty('javaLauncher') }.configureEach { + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(testToolchainLanguageVersion()) + }) + } + tasks.withType(JavaCompile).matching { it.name.contains("Jmh") }.configureEach { + javaCompiler = javaToolchains.compilerFor { + languageVersion = testToolchainLanguageVersion() + } + } + } +} + +// Store resolved Toolchain JVM information as custom values in the build scan. +rootProject.ext { + resolvedMainToolchain = false + resolvedTestToolchain = false +} +gradle.taskGraph.afterTask { Task task, TaskState state -> + if (mainToolchainConfigured() && !resolvedMainToolchain && task instanceof JavaCompile && task.javaCompiler.isPresent()) { + def metadata = task.javaCompiler.get().metadata + task.project.buildScan.value('Main toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)") + resolvedMainToolchain = true + } + if (testToolchainConfigured() && !resolvedTestToolchain && task instanceof Test && task.javaLauncher.isPresent()) { + def metadata = task.javaLauncher.get().metadata + task.project.buildScan.value('Test toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)") + resolvedTestToolchain = true + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023e..7454180f2ae 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be52383ef49..ffed3a254e9 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-6.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c811..1b6c787337f 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/import-into-eclipse.md b/import-into-eclipse.md index bb665d788a1..fc3d36a8854 100644 --- a/import-into-eclipse.md +++ b/import-into-eclipse.md @@ -3,11 +3,11 @@ 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, Eclipse Buildship, the Kotlin plugin, and the Groovy plugin. +8 support, Eclipse Buildship, 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)) -(based on Eclipse 4.12) with [Eclipse Buildship](https://projects.eclipse.org/projects/tools.buildship). +The following instructions have been tested against [STS](https://spring.io/tools) 4.12.0 +([download](https://github.com/spring-projects/sts4/wiki/Previous-Versions#spring-tools-4120-changelog)) +(based on Eclipse 4.21) 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. @@ -16,28 +16,32 @@ that STS 4 comes with Buildship preinstalled. _When instructed to execute `./gradlew` from the command line, be sure to execute it within your locally cloned `spring-framework` working directory._ -1. Ensure that Eclipse launches with JDK 8. - - For example, on Mac OS this can be configured in the `Info.plist` file located in the `Contents` folder of the installed Eclipse or STS application (e.g., the `Eclipse.app` file). -1. Install the [Kotlin Plugin for Eclipse](https://marketplace.eclipse.org/content/kotlin-plugin-eclipse) in Eclipse. -1. Install the [Eclipse Groovy Development Tools](https://github.com/groovy/groovy-eclipse/wiki) in Eclipse. -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://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. Install the [Groovy Development Tools](https://marketplace.eclipse.org/content/groovy-development-tools). +1. Switch to Groovy 3.0 in Eclipse (Preferences → Groovy → Compiler → Switch to 3.0...). + - If you encounter build errors stating something similar to _"Groovy: compiler mismatch: project level is 2.5, workspace level is 3.0"_, change the Groovy compiler version to 3.0 for each affected project. +1. Ensure that the _Forbidden reference (access rule)_ in Eclipse is set to `Info` +(Preferences → Java → Compiler → Errors/Warnings → Deprecated and restricted API → Forbidden reference (access rule)). +1. Optionally install the [Kotlin Plugin for Eclipse](https://marketplace.eclipse.org/content/kotlin-plugin-eclipse) if you need to execute Kotlin-based tests or develop Kotlin extensions. + - **NOTE**: As of September 21, 2021, it appears that the Kotlin Plugin for Eclipse does not yet work with Eclipse 4.21. +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. + - **NOTE**: As of September 21, 2021, it appears that the AspectJ Development Tools do not yet work with Eclipse 4.21. +1. Optionally install the [TestNG plugin](https://testng.org/doc/eclipse.html) in Eclipse if you need to execute individual TestNG test classes or tests in the `spring-test` module. + - As an alternative to installing the TestNG plugin, you can execute the `org.springframework.test.context.testng.TestNGTestSuite` class as a "JUnit 5" test class in Eclipse. 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. -1. Import into Eclipse (File -> Import -> Gradle -> Existing Gradle Project -> Navigate to the locally cloned `spring-framework` directory -> Select Finish). +1. To apply Spring Framework specific settings, run `./gradlew cleanEclipse eclipse` from the command line. +1. Import all projects into Eclipse (File → Import → Gradle → Existing Gradle Project → Navigate to the locally cloned `spring-framework` directory → Select Finish). - If you have not installed AJDT, exclude the `spring-aspects` project from the import, if prompted, or close it after the import. - - If you run into errors during the import, you may need to set the _Java home_ for Gradle Buildship to the location of your JDK 8 installation in Eclipse (Preferences -> Gradle -> Java home). -1. If you need to execute JAXB-related tests in the `spring-oxm` project and wish to have the generated sources available, add the `build/generated-sources/jaxb` folder to the build path (right click on the `jaxb` folder and select `Build Path -> Use as Source Folder`). - - If you do not see the `build` folder in the `spring-oxm` project, ensure that the "Gradle build folder" is not filtered out from the view. This setting is available under "Filters" in the configuration of the Package Explorer (available by clicking on the small downward facing arrow in the upper right corner of the Package Explorer). + - If you run into errors during the import, you may need to set the _Java home_ for Gradle Buildship to the location of your JDK 8 installation in Eclipse (Preferences → Gradle → Java home). +1. If you need to execute JAXB-related tests in the `spring-oxm` project and wish to have the generated sources available, add the `build/generated-sources/jaxb` folder to the build path (right click on the `jaxb` folder and select "Build Path → Use as Source Folder"). + - If you do not see the `build` folder in the `spring-oxm` project, ensure that the "Gradle build folder" is not filtered out from the view. This setting is available under "Filters" in the configuration of the Package Explorer (available by clicking on the _three vertical dots_ in the upper right corner of the Package Explorer). 1. Code away! ## Known Issues -1. `spring-core` and `spring-oxm` should be pre-compiled due to repackaged dependencies. - - See `*RepackJar` tasks in the build. +1. `spring-core` should be pre-compiled due to repackaged dependencies. + - See `*RepackJar` tasks in the `spring-core.gradle` build file. +1. `spring-oxm` should be pre-compiled due to JAXB types generated for tests. + - Note that executing `./gradlew :spring-oxm:check` as explained in the _Steps_ above will compile `spring-core` and generate JAXB types for `spring-oxm`. 1. `spring-aspects` does not compile due to references to aspect types unknown to Eclipse. - If you installed _AJDT_ into Eclipse it should work. 1. While JUnit tests pass from the command line with Gradle, some may fail when run from diff --git a/integration-tests/integration-tests.gradle b/integration-tests/integration-tests.gradle index 71e23b90604..f7408e5c6ec 100644 --- a/integration-tests/integration-tests.gradle +++ b/integration-tests/integration-tests.gradle @@ -1,26 +1,26 @@ description = "Spring Integration Tests" 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")) - testCompile(project(":spring-test")) - testCompile(project(":spring-tx")) - testCompile(project(":spring-web")) - testCompile("javax.inject:javax.inject") - testCompile("javax.resource:javax.resource-api") - testCompile("javax.servlet:javax.servlet-api") - testCompile("org.aspectj:aspectjweaver") - testCompile("org.hsqldb:hsqldb") - testCompile("org.hibernate:hibernate-core") + testImplementation(project(":spring-aop")) + testImplementation(project(":spring-beans")) + testImplementation(project(":spring-context")) + testImplementation(project(":spring-core")) + testImplementation(testFixtures(project(":spring-aop"))) + testImplementation(testFixtures(project(":spring-beans"))) + testImplementation(testFixtures(project(":spring-core"))) + testImplementation(testFixtures(project(":spring-tx"))) + testImplementation(project(":spring-expression")) + testImplementation(project(":spring-jdbc")) + testImplementation(project(":spring-orm")) + testImplementation(project(":spring-test")) + testImplementation(project(":spring-tx")) + testImplementation(project(":spring-web")) + testImplementation("javax.inject:javax.inject") + testImplementation("javax.resource:javax.resource-api") + testImplementation("javax.servlet:javax.servlet-api") + testImplementation("org.aspectj:aspectjweaver") + testImplementation("org.hsqldb:hsqldb") + testImplementation("org.hibernate:hibernate-core") } normalization { diff --git a/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java b/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java index bd86cc9041d..2f13da238f4 100644 --- a/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java +++ b/integration-tests/src/test/java/org/springframework/context/annotation/jsr330/ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.ScopeMetadata; -import org.springframework.context.annotation.ScopeMetadataResolver; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; @@ -307,29 +306,26 @@ private ApplicationContext createContext(final ScopedProxyMode scopedProxyMode) GenericWebApplicationContext context = new GenericWebApplicationContext(); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setIncludeAnnotationConfig(false); - scanner.setScopeMetadataResolver(new ScopeMetadataResolver() { - @Override - public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { - ScopeMetadata metadata = new ScopeMetadata(); - if (definition instanceof AnnotatedBeanDefinition) { - AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; - for (String type : annDef.getMetadata().getAnnotationTypes()) { - if (type.equals(javax.inject.Singleton.class.getName())) { - metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON); - break; - } - else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) { - metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase()); - metadata.setScopedProxyMode(scopedProxyMode); - break; - } - else if (type.startsWith("javax.inject")) { - metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); - } + scanner.setScopeMetadataResolver(definition -> { + ScopeMetadata metadata = new ScopeMetadata(); + if (definition instanceof AnnotatedBeanDefinition) { + AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; + for (String type : annDef.getMetadata().getAnnotationTypes()) { + if (type.equals(javax.inject.Singleton.class.getName())) { + metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON); + break; + } + else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) { + metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase()); + metadata.setScopedProxyMode(scopedProxyMode); + break; + } + else if (type.startsWith("javax.inject")) { + metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); } } - return metadata; } + return metadata; }); // Scan twice in order to find errors in the bean definition compatibility check. diff --git a/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java b/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java index 16db4fc393a..9062d6e3f61 100644 --- a/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java +++ b/integration-tests/src/test/java/org/springframework/scheduling/annotation/ScheduledAndTransactionalAnnotationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ void succeedsWhenSubclassProxyAndScheduledMethodNotPresentOnInterface() throws I MyRepository repository = ctx.getBean(MyRepository.class); CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class); - assertThat(AopUtils.isCglibProxy(repository)).isEqualTo(true); + assertThat(AopUtils.isCglibProxy(repository)).isTrue(); assertThat(repository.getInvocationCount()).isGreaterThan(0); assertThat(txManager.commits).isGreaterThan(0); } diff --git a/settings.gradle b/settings.gradle index aacebba92d8..e1638d26c9a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,13 +1,14 @@ pluginManagement { repositories { + mavenCentral() gradlePluginPortal() - maven { url 'https://repo.spring.io/plugins-release' } + maven { url "https://repo.spring.io/release" } } } plugins { - id "com.gradle.enterprise" version "3.2" - id "io.spring.gradle-enterprise-conventions" version "0.0.2" + id "com.gradle.enterprise" version "3.8.1" + id "io.spring.ge.conventions" version "0.0.9" } include "spring-aop" @@ -17,8 +18,6 @@ include "spring-context" include "spring-context-indexer" include "spring-context-support" include "spring-core" -include "kotlin-coroutines" -project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines') include "spring-expression" include "spring-instrument" include "spring-jcl" @@ -45,19 +44,13 @@ rootProject.children.each {project -> settings.gradle.projectsLoaded { gradleEnterprise { buildScan { - if (settings.gradle.rootProject.hasProperty('customJavaHome')) { - value("Custom JAVA_HOME", settings.gradle.rootProject.getProperty('customJavaHome')) - } - if (settings.gradle.rootProject.hasProperty('customJavaSourceVersion')) { - value("Custom Java Source Version", settings.gradle.rootProject.getProperty('customJavaSourceVersion')) - } File buildDir = settings.gradle.rootProject.getBuildDir() buildDir.mkdirs() new File(buildDir, "build-scan-uri.txt").text = "(build scan not generated)" buildScanPublished { scan -> if (buildDir.exists()) { new File(buildDir, "build-scan-uri.txt").text = "${scan.buildScanUri}\n" - } + } } } } diff --git a/spring-aop/spring-aop.gradle b/spring-aop/spring-aop.gradle index 73bb378f39a..67bf95e8dd6 100644 --- a/spring-aop/spring-aop.gradle +++ b/spring-aop/spring-aop.gradle @@ -1,12 +1,13 @@ description = "Spring AOP" dependencies { - compile(project(":spring-beans")) - compile(project(":spring-core")) + api(project(":spring-beans")) + api(project(":spring-core")) optional("org.aspectj:aspectjweaver") optional("org.apache.commons:commons-pool2") optional("com.jamonapi:jamon") - testCompile(testFixtures(project(":spring-beans"))) - testCompile(testFixtures(project(":spring-core"))) + testImplementation(testFixtures(project(":spring-beans"))) + testImplementation(testFixtures(project(":spring-core"))) + testFixturesImplementation(testFixtures(project(":spring-beans"))) testFixturesImplementation(testFixtures(project(":spring-core"))) } diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java b/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java index 780275e9782..480d9f087cf 100644 --- a/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java +++ b/spring-aop/src/main/java/org/aopalliance/intercept/Joinpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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 @@ * terminology). * *

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/DynamicIntroductionAdvice.java b/spring-aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java index 08c704857f7..2f46775b945 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/aspectj/AbstractAspectJAdvice.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java index 32b691f4410..8ef1bf148a1 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.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. @@ -350,17 +350,8 @@ protected Class getDiscoveredThrowingType() { return this.discoveredThrowingType; } - private boolean isVariableName(String name) { - char[] chars = name.toCharArray(); - if (!Character.isJavaIdentifierStart(chars[0])) { - return false; - } - for (int i = 1; i < chars.length; i++) { - if (!Character.isJavaIdentifierPart(chars[i])) { - return false; - } - } - return true; + private static boolean isVariableName(String name) { + return AspectJProxyUtils.isVariableName(name); } @@ -640,7 +631,6 @@ protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable } try { ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); - // TODO AopUtils.invokeJoinpointUsingReflection return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); } catch (IllegalArgumentException ex) { 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 ec58afb2965..60a45977513 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-2020 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. @@ -470,22 +470,10 @@ else if (numAnnotationSlots == 1) { */ @Nullable private String maybeExtractVariableName(@Nullable String candidateToken) { - if (!StringUtils.hasLength(candidateToken)) { - return null; - } - if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) && - Character.isLowerCase(candidateToken.charAt(0))) { - char[] tokenChars = candidateToken.toCharArray(); - for (char tokenChar : tokenChars) { - if (!Character.isJavaIdentifierPart(tokenChar)) { - return null; - } - } + if (AspectJProxyUtils.isVariableName(candidateToken)) { return candidateToken; } - else { - return null; - } + return null; } /** @@ -637,7 +625,7 @@ private PointcutBody getPointcutBody(String[] tokens, int startIndex) { StringBuilder sb = new StringBuilder(); if (bodyStart >= 0 && bodyStart != (currentToken.length() - 1)) { sb.append(currentToken.substring(bodyStart + 1)); - sb.append(" "); + sb.append(' '); } numTokensConsumed++; int currentIndex = startIndex + numTokensConsumed; @@ -657,7 +645,7 @@ private PointcutBody getPointcutBody(String[] tokens, int startIndex) { toAppend = toAppend.substring(1); } sb.append(toAppend); - sb.append(" "); + sb.append(' '); currentIndex++; numTokensConsumed++; } diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java index 1cdef3141da..8b5e5f6f3e0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.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. @@ -547,7 +547,7 @@ public String toString() { StringBuilder sb = new StringBuilder("AspectJExpressionPointcut: ("); for (int i = 0; i < this.pointcutParameterTypes.length; i++) { sb.append(this.pointcutParameterTypes[i].getName()); - sb.append(" "); + sb.append(' '); sb.append(this.pointcutParameterNames[i]); if ((i+1) < this.pointcutParameterTypes.length) { sb.append(", "); diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java index 833a109f131..e161007abe9 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.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. @@ -21,6 +21,8 @@ import org.springframework.aop.Advisor; import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; /** * Utility methods for working with AspectJ proxies. @@ -73,4 +75,19 @@ private static boolean isAspectJAdvice(Advisor advisor) { ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)); } + static boolean isVariableName(@Nullable String name) { + if (!StringUtils.hasLength(name)) { + return false; + } + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + return false; + } + for (int i = 1; i < name.length(); i++) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) { + return false; + } + } + return true; + } + } 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 d25ec2eb6bc..ef9016a1572 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-2020 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. @@ -255,19 +255,19 @@ private String toString(boolean includeModifier, boolean includeReturnTypeAndArg StringBuilder sb = new StringBuilder(); if (includeModifier) { sb.append(Modifier.toString(getModifiers())); - sb.append(" "); + sb.append(' '); } if (includeReturnTypeAndArgs) { appendType(sb, getReturnType(), useLongReturnAndArgumentTypeName); - sb.append(" "); + sb.append(' '); } appendType(sb, getDeclaringType(), useLongTypeName); - sb.append("."); + sb.append('.'); sb.append(getMethod().getName()); - sb.append("("); + sb.append('('); Class[] parametersTypes = getParameterTypes(); appendTypes(sb, parametersTypes, includeReturnTypeAndArgs, useLongReturnAndArgumentTypeName); - sb.append(")"); + sb.append(')'); return sb.toString(); } @@ -278,7 +278,7 @@ private void appendTypes(StringBuilder sb, Class[] types, boolean includeArgs for (int size = types.length, i = 0; i < size; i++) { appendType(sb, types[i], useLongReturnAndArgumentTypeName); if (i < size - 1) { - sb.append(","); + sb.append(','); } } } 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 e76156bf826..58f3c23b459 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/ReflectiveAspectJAdvisorFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java index 5355b2bbb37..c1c10c946ed 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 @@ -51,6 +51,7 @@ import org.springframework.core.convert.converter.ConvertingComparator; import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.util.StringUtils; import org.springframework.util.comparator.InstanceComparator; @@ -70,7 +71,11 @@ @SuppressWarnings("serial") public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable { - private static final Comparator METHOD_COMPARATOR; + // Exclude @Pointcut methods + private static final MethodFilter adviceMethodFilter = ReflectionUtils.USER_DECLARED_METHODS + .and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null)); + + private static final Comparator adviceMethodComparator; static { // Note: although @After is ordered before @AfterReturning and @AfterThrowing, @@ -86,7 +91,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto return (ann != null ? ann.getAnnotation() : null); }); Comparator methodNameComparator = new ConvertingComparator<>(Method::getName); - METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator); + adviceMethodComparator = adviceKindComparator.thenComparing(methodNameComparator); } @@ -160,15 +165,10 @@ public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstan } private List getAdvisorMethods(Class aspectClass) { - final List methods = new ArrayList<>(); - ReflectionUtils.doWithMethods(aspectClass, method -> { - // Exclude pointcuts - if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { - methods.add(method); - } - }, ReflectionUtils.USER_DECLARED_METHODS); + List methods = new ArrayList<>(); + ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter); if (methods.size() > 1) { - methods.sort(METHOD_COMPARATOR); + methods.sort(adviceMethodComparator); } return methods; } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java index a3a87f2117f..d941bdac4c9 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.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. @@ -22,6 +22,7 @@ import org.springframework.aop.Advisor; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.SmartClassLoader; import org.springframework.lang.Nullable; /** @@ -89,7 +90,13 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { } proxyFactory.addAdvisor(this.advisor); customizeProxyFactory(proxyFactory); - return proxyFactory.getProxy(getProxyClassLoader()); + + // Use original ClassLoader if bean class not locally loaded in overriding class loader + ClassLoader classLoader = getProxyClassLoader(); + if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) { + classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + } + return proxyFactory.getProxy(classLoader); } // No proxy needed. 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 5664cf7ffd5..b2b8060b8a6 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 @@ -521,8 +521,8 @@ AdvisedSupport getConfigurationOnlyCopy() { copy.copyFrom(this); copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic()); copy.advisorChainFactory = this.advisorChainFactory; - copy.interfaces = this.interfaces; - copy.advisors = this.advisors; + copy.interfaces = new ArrayList<>(this.interfaces); + copy.advisors = new ArrayList<>(this.advisors); return copy; } 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 5dd18747ef3..f6f7bd7acfd 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,9 @@ import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.springframework.aop.SpringProxy; import org.springframework.aop.TargetClassAware; @@ -29,7 +31,9 @@ import org.springframework.core.DecoratingProxy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; /** * Utility methods for AOP proxy factories. @@ -40,10 +44,16 @@ * * @author Rod Johnson * @author Juergen Hoeller + * @author Sam Brannen * @see org.springframework.aop.support.AopUtils */ public abstract class AopProxyUtils { + // JDK 17 Class.isSealed() method available? + @Nullable + private static final Method isSealedMethod = ClassUtils.getMethodIfAvailable(Class.class, "isSealed"); + + /** * Obtain the singleton target object behind the given proxy, if any. * @param candidate the (potential) proxy to check @@ -124,40 +134,29 @@ static Class[] completeProxiedInterfaces(AdvisedSupport advised, boolean deco if (targetClass.isInterface()) { advised.setInterfaces(targetClass); } - else if (Proxy.isProxyClass(targetClass)) { + else if (Proxy.isProxyClass(targetClass) || isLambda(targetClass)) { advised.setInterfaces(targetClass.getInterfaces()); } specifiedInterfaces = advised.getProxiedInterfaces(); } } - boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); - boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); - boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); - int nonUserIfcCount = 0; - if (addSpringProxy) { - nonUserIfcCount++; - } - if (addAdvised) { - nonUserIfcCount++; - } - if (addDecoratingProxy) { - nonUserIfcCount++; + List> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3); + for (Class ifc : specifiedInterfaces) { + // Only non-sealed interfaces are actually eligible for JDK proxying (on JDK 17) + if (isSealedMethod == null || Boolean.FALSE.equals(ReflectionUtils.invokeMethod(isSealedMethod, ifc))) { + proxiedInterfaces.add(ifc); + } } - Class[] proxiedInterfaces = new Class[specifiedInterfaces.length + nonUserIfcCount]; - System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); - int index = specifiedInterfaces.length; - if (addSpringProxy) { - proxiedInterfaces[index] = SpringProxy.class; - index++; + if (!advised.isInterfaceProxied(SpringProxy.class)) { + proxiedInterfaces.add(SpringProxy.class); } - if (addAdvised) { - proxiedInterfaces[index] = Advised.class; - index++; + if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) { + proxiedInterfaces.add(Advised.class); } - if (addDecoratingProxy) { - proxiedInterfaces[index] = DecoratingProxy.class; + if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) { + proxiedInterfaces.add(DecoratingProxy.class); } - return proxiedInterfaces; + return ClassUtils.toClassArray(proxiedInterfaces); } /** @@ -246,4 +245,18 @@ static Object[] adaptArgumentsIfNecessary(Method method, @Nullable Object[] argu return arguments; } + /** + * Determine if the supplied {@link Class} is a JVM-generated implementation + * class for a lambda expression or method reference. + *

This method makes a best-effort attempt at determining this, based on + * checks that work on modern, main stream JVMs. + * @param clazz the class to check + * @return {@code true} if the class is a lambda implementation class + * @since 5.3.16 + */ + static boolean isLambda(Class clazz) { + return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) && + (clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda")); + } + } 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 69fe9a04877..022cc0fddf2 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-2020 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. @@ -48,6 +48,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 (!this.advised.isOptimize() && logger.isInfoEnabled()) { synchronized (validatedClasses) { if (!validatedClasses.containsKey(proxySuperClass)) { doValidateClass(proxySuperClass, proxyClassLoader, @@ -678,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... @@ -736,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 @@ -752,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); } } @@ -768,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); } } } 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 d84df2f4c9e..5f1acad9a9a 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-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.lang.reflect.Proxy; import org.springframework.aop.SpringProxy; +import org.springframework.core.NativeDetector; /** * Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy @@ -39,32 +40,27 @@ * @author Rod Johnson * @author Juergen Hoeller * @author Sebastien Deleuze + * @author Sam Brannen * @since 12.03.2004 * @see AdvisedSupport#setOptimize * @see AdvisedSupport#setProxyTargetClass * @see AdvisedSupport#setInterfaces */ -@SuppressWarnings("serial") public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { - /** - * Whether this environment lives within a native image. - * Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594. - * @see ImageInfo.java - */ - private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null); + private static final long serialVersionUID = 7930414337282325166L; @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { - if (!IN_NATIVE_IMAGE && + if (!NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } - if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { + if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java index 38665cafbc6..3b6010f8f58 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.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. @@ -73,11 +73,9 @@ public boolean isProxyTargetClass() { * The exact meaning of "aggressive optimizations" will differ * between proxies, but there is usually some tradeoff. * Default is "false". - *

For example, optimization will usually mean that advice changes won't - * take effect after a proxy has been created. For this reason, optimization - * is disabled by default. An optimize value of "true" may be ignored - * if other settings preclude optimization: for example, if "exposeProxy" - * is set to "true" and that's not compatible with the optimization. + *

With Spring's current proxy options, this flag effectively + * enforces CGLIB proxies (similar to {@link #setProxyTargetClass}) + * but without any class validation checks (for final methods etc). */ public void setOptimize(boolean optimize) { this.optimize = optimize; 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 6c9efc49f0d..5e1ed1603fa 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-2020 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. @@ -421,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)); @@ -464,9 +460,9 @@ private synchronized void initializeAdvisorChain() throws AopConfigException, Be addAdvisorOnChainCreation(advice); } } - } - this.advisorChainInitialized = true; + this.advisorChainInitialized = true; + } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java index dd557215c56..a6b09c63cbb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.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. @@ -25,8 +25,8 @@ import org.springframework.aop.ThrowsAdvice; /** - * Adapter to enable {@link org.springframework.aop.MethodBeforeAdvice} - * to be used in the Spring AOP framework. + * Adapter to enable {@link org.springframework.aop.ThrowsAdvice} to be used + * in the Spring AOP framework. * * @author Rod Johnson * @author Juergen Hoeller diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java index d4ffb04faf1..3e68f820ecb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -17,6 +17,7 @@ package org.springframework.aop.framework.autoproxy; import java.lang.reflect.Constructor; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,6 +47,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; +import org.springframework.core.SmartClassLoader; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -439,7 +441,17 @@ protected Object createProxy(Class beanClass, @Nullable String beanName, ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); - if (!proxyFactory.isProxyTargetClass()) { + if (proxyFactory.isProxyTargetClass()) { + // Explicit handling of JDK proxy targets (for introduction advice scenarios) + if (Proxy.isProxyClass(beanClass)) { + // Must allow for introductions; can't just set interfaces to the proxy's interfaces only. + for (Class ifc : beanClass.getInterfaces()) { + proxyFactory.addInterface(ifc); + } + } + } + else { + // No proxyTargetClass flag enforced, let's apply our default checks... if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } @@ -458,7 +470,12 @@ protected Object createProxy(Class beanClass, @Nullable String beanName, proxyFactory.setPreFiltered(true); } - return proxyFactory.getProxy(getProxyClassLoader()); + // Use original ClassLoader if bean class not locally loaded in overriding class loader + ClassLoader classLoader = getProxyClassLoader(); + if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { + classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); + } + return proxyFactory.getProxy(classLoader); } /** @@ -503,7 +520,10 @@ protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] List allInterceptors = new ArrayList<>(); if (specificInterceptors != null) { - allInterceptors.addAll(Arrays.asList(specificInterceptors)); + if (specificInterceptors.length > 0) { + // specificInterceptors may equal PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS + allInterceptors.addAll(Arrays.asList(specificInterceptors)); + } if (commonInterceptors.length > 0) { if (this.applyCommonInterceptorsFirst) { allInterceptors.addAll(0, Arrays.asList(commonInterceptors)); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java index 8c49d4ff7d2..297ab0cd1f6 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java @@ -61,7 +61,7 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator { * FactoryBean will get proxied. This default behavior applies as of Spring 2.0. * If you intend to proxy a FactoryBean instance itself (a rare use case, but * Spring 1.2's default behavior), specify the bean name of the FactoryBean - * including the factory-bean prefix "&": e.g. "&myFactoryBean". + * including the factory-bean prefix "&": e.g. "&myFactoryBean". * @see org.springframework.beans.factory.FactoryBean * @see org.springframework.beans.factory.BeanFactory#FACTORY_BEAN_PREFIX */ diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java index 4fd173f2893..fcef74b56ef 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.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. @@ -125,12 +125,8 @@ public final TargetSource getTargetSource(Class beanClass, String beanName) { */ protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) { synchronized (this.internalBeanFactories) { - DefaultListableBeanFactory internalBeanFactory = this.internalBeanFactories.get(beanName); - if (internalBeanFactory == null) { - internalBeanFactory = buildInternalBeanFactory(this.beanFactory); - this.internalBeanFactories.put(beanName, internalBeanFactory); - } - return internalBeanFactory; + return this.internalBeanFactories.computeIfAbsent(beanName, + name -> buildInternalBeanFactory(this.beanFactory)); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java index a6a741e0f87..f7df6c30249 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java @@ -25,9 +25,11 @@ /** * Convenient TargetSourceCreator using bean name prefixes to create one of three * well-known TargetSource types: - *
  • : CommonsPool2TargetSource - *
  • % ThreadLocalTargetSource - *
  • ! PrototypeTargetSource + *
      + *
    • : CommonsPool2TargetSource
    • + *
    • % ThreadLocalTargetSource
    • + *
    • ! PrototypeTargetSource
    • + *
    * * @author Rod Johnson * @author Stephane Nicoll diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 8908cab491d..a559a9f765f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -233,7 +233,8 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) { return beanFactory.getBean(TaskExecutor.class); } catch (NoUniqueBeanDefinitionException ex) { - logger.debug("Could not find unique TaskExecutor bean", ex); + logger.debug("Could not find unique TaskExecutor bean. " + + "Continuing search for an Executor bean named 'taskExecutor'", ex); try { return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); } @@ -246,7 +247,8 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) { } } catch (NoSuchBeanDefinitionException ex) { - logger.debug("Could not find default TaskExecutor bean", ex); + logger.debug("Could not find default TaskExecutor bean. " + + "Continuing search for an Executor bean named 'taskExecutor'", ex); try { return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java index ac69e3962b0..ccb008ebd01 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -137,7 +137,7 @@ public String toString() { StringBuilder sb = new StringBuilder(getClass().getName()); sb.append(": advice "); if (this.adviceBeanName != null) { - sb.append("bean '").append(this.adviceBeanName).append("'"); + sb.append("bean '").append(this.adviceBeanName).append('\''); } else { sb.append(this.advice); diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java index 6adeea04775..1c4dd8bf43f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -146,7 +146,7 @@ public int hashCode() { @Override public String toString() { - return getClass().getName() + ": class = " + this.clazz.getName() + "; methodName = " + methodName; + return getClass().getName() + ": class = " + this.clazz.getName() + "; methodName = " + this.methodName; } } diff --git a/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java b/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java index d76cdd72537..ff2370a3223 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.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. @@ -71,7 +71,8 @@ public DefaultIntroductionAdvisor(Advice advice, @Nullable IntroductionInfo intr if (introductionInfo != null) { Class[] introducedInterfaces = introductionInfo.getInterfaces(); if (introducedInterfaces.length == 0) { - throw new IllegalArgumentException("IntroductionAdviceSupport implements no interfaces"); + throw new IllegalArgumentException( + "IntroductionInfo defines no interfaces to introduce: " + introductionInfo); } for (Class ifc : introducedInterfaces) { addInterface(ifc); diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java index cf32c396d84..78c6d3abf6d 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java @@ -191,9 +191,9 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()); - sb.append(" for target bean '").append(this.targetBeanName).append("'"); + sb.append(" for target bean '").append(this.targetBeanName).append('\''); if (this.targetClass != null) { - sb.append(" of type [").append(this.targetClass.getName()).append("]"); + sb.append(" of type [").append(this.targetClass.getName()).append(']'); } return sb.toString(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java index 59017bfabeb..a5cfedbcbe0 100644 --- a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.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. @@ -116,7 +116,7 @@ public final void setBeanFactory(BeanFactory beanFactory) throws BeansException /** - * Return an IntroductionAdvisor that providing a mixin + * Return an IntroductionAdvisor that provides a mixin * exposing statistics about the pool maintained by this object. */ public DefaultIntroductionAdvisor getPoolingConfigMixin() { diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java index bd37f5d3770..40a5de33103 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscovererTests.java @@ -276,14 +276,14 @@ protected void assertException(Method method, String pointcut, String returning, private static String format(String[] names) { StringBuilder sb = new StringBuilder(); - sb.append("("); + sb.append('('); for (int i = 0; i < names.length; i++) { sb.append(names[i]); if ((i + 1) < names.length) { - sb.append(","); + sb.append(','); } } - sb.append(")"); + sb.append(')'); return sb.toString(); } diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java index 612aa61af66..3f5a7b562c5 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,7 @@ void rejectsPerCflowBelowAspect() { } @Test - void perTargetAspect() throws SecurityException, NoSuchMethodException { + void perTargetAspect() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -130,7 +130,7 @@ void perTargetAspect() throws SecurityException, NoSuchMethodException { } @Test - void multiplePerTargetAspects() throws SecurityException, NoSuchMethodException { + void multiplePerTargetAspects() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -158,7 +158,7 @@ void multiplePerTargetAspects() throws SecurityException, NoSuchMethodException } @Test - void multiplePerTargetAspectsWithOrderAnnotation() throws SecurityException, NoSuchMethodException { + void multiplePerTargetAspectsWithOrderAnnotation() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -184,7 +184,7 @@ void multiplePerTargetAspectsWithOrderAnnotation() throws SecurityException, NoS } @Test - void perThisAspect() throws SecurityException, NoSuchMethodException { + void perThisAspect() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -220,7 +220,7 @@ void perThisAspect() throws SecurityException, NoSuchMethodException { } @Test - void perTypeWithinAspect() throws SecurityException, NoSuchMethodException { + void perTypeWithinAspect() throws Exception { TestBean target = new TestBean(); int realAge = 65; target.setAge(realAge); @@ -322,7 +322,7 @@ void bindingWithMultipleArgsDifferentlyOrdered() { int b = 12; int c = 25; String d = "d"; - StringBuffer e = new StringBuffer("stringbuf"); + StringBuilder e = new StringBuilder("stringbuf"); String expectedResult = a + b+ c + d + e; assertThat(mva.mungeArgs(a, b, c, d, e)).isEqualTo(expectedResult); } @@ -728,12 +728,12 @@ void changeReturnType(ProceedingJoinPoint pjp, int age) throws Throwable { @Aspect static class ManyValuedArgs { - String mungeArgs(String a, int b, int c, String d, StringBuffer e) { + String mungeArgs(String a, int b, int c, String d, StringBuilder e) { return a + b + c + d + e; } @Around(value="execution(String mungeArgs(..)) && args(a, b, c, d, e)", argNames="b,c,d,e,a") - String reverseAdvice(ProceedingJoinPoint pjp, int b, int c, String d, StringBuffer e, String a) throws Throwable { + String reverseAdvice(ProceedingJoinPoint pjp, int b, int c, String d, StringBuilder e, String a) throws Throwable { assertThat(pjp.proceed()).isEqualTo(a + b+ c+ d+ e); return a + b + c + d + e; } diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java index 637baa2450a..2554895430f 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectMetadataTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** - * @since 2.0 * @author Rod Johnson * @author Chris Beams * @author Sam Brannen @@ -56,7 +55,7 @@ void perTargetAspect() { assertThat(am.getAjType().getPerClause().getKind()).isEqualTo(PerClauseKind.PERTARGET); assertThat(am.getPerClausePointcut()).isInstanceOf(AspectJExpressionPointcut.class); assertThat(((AspectJExpressionPointcut) am.getPerClausePointcut()).getExpression()) - .isEqualTo("execution(* *.getSpouse())"); + .isEqualTo("execution(* *.getSpouse())"); } @Test @@ -67,7 +66,7 @@ void perThisAspect() { assertThat(am.getAjType().getPerClause().getKind()).isEqualTo(PerClauseKind.PERTHIS); assertThat(am.getPerClausePointcut()).isInstanceOf(AspectJExpressionPointcut.class); assertThat(((AspectJExpressionPointcut) am.getPerClausePointcut()).getExpression()) - .isEqualTo("execution(* *.getSpouse())"); + .isEqualTo("execution(* *.getSpouse())"); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java index d4c774424d0..ffb270cdd9b 100644 --- a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerEventTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.aop.config; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -40,7 +41,7 @@ * @author Juergen Hoeller * @author Chris Beams */ -public class AopNamespaceHandlerEventTests { +class AopNamespaceHandlerEventTests { private static final Class CLASS = AopNamespaceHandlerEventTests.class; @@ -57,25 +58,24 @@ public class AopNamespaceHandlerEventTests { @BeforeEach - public void setup() { + void setup() { this.reader = new XmlBeanDefinitionReader(this.beanFactory); this.reader.setEventListener(this.eventListener); } @Test - public void testPointcutEvents() { + void pointcutEvents() { this.reader.loadBeanDefinitions(POINTCUT_EVENTS_CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(1); - boolean condition = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(1); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(2); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(2); PointcutComponentDefinition pcd = null; for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof PointcutComponentDefinition) { @@ -84,84 +84,77 @@ public void testPointcutEvents() { } } assertThat(pcd).as("PointcutComponentDefinition not found").isNotNull(); - assertThat(pcd.getBeanDefinitions().length).as("Incorrect number of BeanDefinitions").isEqualTo(1); + assertThat(pcd.getBeanDefinitions()).as("Incorrect number of BeanDefinitions").hasSize(1); } @Test - public void testAdvisorEventsWithPointcutRef() { + void advisorEventsWithPointcutRef() { this.reader.loadBeanDefinitions(POINTCUT_REF_CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(2); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(2); - boolean condition1 = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition1).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(3); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(3); AdvisorComponentDefinition acd = null; - for (int i = 0; i < nestedComponentDefs.length; i++) { - ComponentDefinition componentDefinition = nestedComponentDefs[i]; + for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof AdvisorComponentDefinition) { acd = (AdvisorComponentDefinition) componentDefinition; break; } } assertThat(acd).as("AdvisorComponentDefinition not found").isNotNull(); - assertThat(acd.getBeanDefinitions().length).isEqualTo(1); - assertThat(acd.getBeanReferences().length).isEqualTo(2); + assertThat(acd.getBeanDefinitions()).hasSize(1); + assertThat(acd.getBeanReferences()).hasSize(2); - boolean condition = componentDefinitions[1] instanceof BeanComponentDefinition; - assertThat(condition).as("No advice bean found").isTrue(); + assertThat(componentDefinitions[1]).as("No advice bean found").isInstanceOf(BeanComponentDefinition.class); BeanComponentDefinition adviceDef = (BeanComponentDefinition) componentDefinitions[1]; assertThat(adviceDef.getBeanName()).isEqualTo("countingAdvice"); } @Test - public void testAdvisorEventsWithDirectPointcut() { + void advisorEventsWithDirectPointcut() { this.reader.loadBeanDefinitions(DIRECT_POINTCUT_EVENTS_CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(2); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(2); - boolean condition1 = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition1).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(2); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(2); AdvisorComponentDefinition acd = null; - for (int i = 0; i < nestedComponentDefs.length; i++) { - ComponentDefinition componentDefinition = nestedComponentDefs[i]; + for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof AdvisorComponentDefinition) { acd = (AdvisorComponentDefinition) componentDefinition; break; } } assertThat(acd).as("AdvisorComponentDefinition not found").isNotNull(); - assertThat(acd.getBeanDefinitions().length).isEqualTo(2); - assertThat(acd.getBeanReferences().length).isEqualTo(1); + assertThat(acd.getBeanDefinitions()).hasSize(2); + assertThat(acd.getBeanReferences()).hasSize(1); - boolean condition = componentDefinitions[1] instanceof BeanComponentDefinition; - assertThat(condition).as("No advice bean found").isTrue(); + assertThat(componentDefinitions[1]).as("No advice bean found").isInstanceOf(BeanComponentDefinition.class); BeanComponentDefinition adviceDef = (BeanComponentDefinition) componentDefinitions[1]; assertThat(adviceDef.getBeanName()).isEqualTo("countingAdvice"); } @Test - public void testAspectEvent() { + void aspectEvent() { this.reader.loadBeanDefinitions(CONTEXT); ComponentDefinition[] componentDefinitions = this.eventListener.getComponentDefinitions(); - assertThat(componentDefinitions.length).as("Incorrect number of events fired").isEqualTo(5); + assertThat(componentDefinitions).as("Incorrect number of events fired").hasSize(2); - boolean condition = componentDefinitions[0] instanceof CompositeComponentDefinition; - assertThat(condition).as("No holder with nested components").isTrue(); + assertThat(componentDefinitions[0]).as("No holder with nested components").isInstanceOf(CompositeComponentDefinition.class); CompositeComponentDefinition compositeDef = (CompositeComponentDefinition) componentDefinitions[0]; assertThat(compositeDef.getName()).isEqualTo("aop:config"); ComponentDefinition[] nestedComponentDefs = compositeDef.getNestedComponents(); - assertThat(nestedComponentDefs.length).as("Incorrect number of inner components").isEqualTo(2); + assertThat(nestedComponentDefs).as("Incorrect number of inner components").hasSize(2); AspectComponentDefinition acd = null; for (ComponentDefinition componentDefinition : nestedComponentDefs) { if (componentDefinition instanceof AspectComponentDefinition) { @@ -172,9 +165,9 @@ public void testAspectEvent() { assertThat(acd).as("AspectComponentDefinition not found").isNotNull(); BeanDefinition[] beanDefinitions = acd.getBeanDefinitions(); - assertThat(beanDefinitions.length).isEqualTo(5); + assertThat(beanDefinitions).hasSize(5); BeanReference[] beanReferences = acd.getBeanReferences(); - assertThat(beanReferences.length).isEqualTo(6); + assertThat(beanReferences).hasSize(6); Set expectedReferences = new HashSet<>(); expectedReferences.add("pc"); @@ -182,19 +175,16 @@ public void testAspectEvent() { for (BeanReference beanReference : beanReferences) { expectedReferences.remove(beanReference.getBeanName()); } - assertThat(expectedReferences.size()).as("Incorrect references found").isEqualTo(0); + assertThat(expectedReferences).as("Incorrect references found").isEmpty(); - for (int i = 1; i < componentDefinitions.length; i++) { - boolean condition1 = componentDefinitions[i] instanceof BeanComponentDefinition; - assertThat(condition1).isTrue(); - } + Arrays.stream(componentDefinitions).skip(1).forEach(definition -> + assertThat(definition).isInstanceOf(BeanComponentDefinition.class)); ComponentDefinition[] nestedComponentDefs2 = acd.getNestedComponents(); - assertThat(nestedComponentDefs2.length).as("Inner PointcutComponentDefinition not found").isEqualTo(1); - boolean condition1 = nestedComponentDefs2[0] instanceof PointcutComponentDefinition; - assertThat(condition1).isTrue(); + assertThat(nestedComponentDefs2).as("Inner PointcutComponentDefinition not found").hasSize(1); + assertThat(nestedComponentDefs2[0]).isInstanceOf(PointcutComponentDefinition.class); PointcutComponentDefinition pcd = (PointcutComponentDefinition) nestedComponentDefs2[0]; - assertThat(pcd.getBeanDefinitions().length).as("Incorrect number of BeanDefinitions").isEqualTo(1); + assertThat(pcd.getBeanDefinitions()).as("Incorrect number of BeanDefinitions").hasSize(1); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java index 6a01b03ad7b..db5fdd5fdc5 100644 --- a/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,10 @@ * @author Mark Fisher * @author Chris Beams */ -public class AopNamespaceHandlerPointcutErrorTests { +class AopNamespaceHandlerPointcutErrorTests { @Test - public void testDuplicatePointcutConfig() { + void duplicatePointcutConfig() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> new XmlBeanDefinitionReader(bf).loadBeanDefinitions( @@ -42,7 +42,7 @@ public void testDuplicatePointcutConfig() { } @Test - public void testMissingPointcutConfig() { + void missingPointcutConfig() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> new XmlBeanDefinitionReader(bf).loadBeanDefinitions( diff --git a/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java b/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java index f1ab7f273db..7e34bc23ef3 100644 --- a/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/config/TopLevelAopTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,10 @@ * @author Rob Harrop * @author Chris Beams */ -public class TopLevelAopTagTests { +class TopLevelAopTagTests { @Test - public void testParse() { + void parse() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions( qualifiedResource(TopLevelAopTagTests.class, "context.xml")); diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java index c8475794f33..3dbf550a121 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -32,6 +33,7 @@ /** * @author Rod Johnson * @author Chris Beams + * @author Sam Brannen */ public class AopProxyUtilsTests { @@ -132,4 +134,61 @@ public void testProxiedUserInterfacesWithNoInterface() { AopProxyUtils.proxiedUserInterfaces(proxy)); } + @Test + void isLambda() { + assertIsLambda(AopProxyUtilsTests.staticLambdaExpression); + assertIsLambda(AopProxyUtilsTests::staticStringFactory); + + assertIsLambda(this.instanceLambdaExpression); + assertIsLambda(this::instanceStringFactory); + } + + @Test + void isNotLambda() { + assertIsNotLambda(new EnigmaSupplier()); + + assertIsNotLambda(new Supplier() { + @Override + public String get() { + return "anonymous inner class"; + } + }); + + assertIsNotLambda(new Fake$$LambdaSupplier()); + } + + private static void assertIsLambda(Supplier supplier) { + assertThat(AopProxyUtils.isLambda(supplier.getClass())).isTrue(); + } + + private static void assertIsNotLambda(Supplier supplier) { + assertThat(AopProxyUtils.isLambda(supplier.getClass())).isFalse(); + } + + private static final Supplier staticLambdaExpression = () -> "static lambda expression"; + + private final Supplier instanceLambdaExpression = () -> "instance lambda expressions"; + + private static String staticStringFactory() { + return "static string factory"; + } + + private String instanceStringFactory() { + return "instance string factory"; + } + + private static class EnigmaSupplier implements Supplier { + @Override + public String get() { + return "enigma"; + } + } + + private static class Fake$$LambdaSupplier implements Supplier { + @Override + public String get() { + return "fake lambda"; + } + } + } diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java index 2ae1d635116..417d1dbbd84 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.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. @@ -183,7 +183,7 @@ public void testAddRepeatedInterface() { } @Test - public void testGetsAllInterfaces() throws Exception { + public void testGetsAllInterfaces() { // Extend to get new interface class TestBeanSubclass extends TestBean implements Comparable { @Override @@ -240,6 +240,16 @@ public Object invoke(MethodInvocation invocation) throws Throwable { assertThat(factory.countAdvicesOfType(NopInterceptor.class) == 2).isTrue(); } + @Test + public void testSealedInterfaceExclusion() { + // String implements ConstantDesc on JDK 12+, sealed as of JDK 17 + ProxyFactory factory = new ProxyFactory(new String()); + NopInterceptor di = new NopInterceptor(); + factory.addAdvice(0, di); + Object proxy = factory.getProxy(); + assertThat(proxy).isInstanceOf(CharSequence.class); + } + /** * Should see effect immediately on behavior. */ diff --git a/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java b/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java index 9936573300d..d135689decc 100644 --- a/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptorTests.java @@ -125,16 +125,7 @@ public void run() { try { this.proxy.exceptional(this.ex); } - catch (RuntimeException ex) { - if (ex == this.ex) { - logger.debug("Expected exception thrown", ex); - } - else { - // should never happen - ex.printStackTrace(); - } - } - catch (Error err) { + catch (RuntimeException | Error err) { if (err == this.ex) { logger.debug("Expected exception thrown", err); } diff --git a/spring-aop/src/test/java/org/springframework/aop/support/ClassUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/support/ClassUtilsTests.java index 6eac64eb77b..f1fffbdf9cb 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/ClassUtilsTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/ClassUtilsTests.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.aop.support; import org.junit.jupiter.api.Test; @@ -29,10 +30,10 @@ * @author Rob Harrop * @author Rick Evans */ -public class ClassUtilsTests { +class ClassUtilsTests { @Test - public void getShortNameForCglibClass() { + void getShortNameForCglibClass() { TestBean tb = new TestBean(); ProxyFactory pf = new ProxyFactory(); pf.setTarget(tb); @@ -41,4 +42,5 @@ public void getShortNameForCglibClass() { String className = ClassUtils.getShortName(proxy.getClass()); assertThat(className).as("Class name did not match").isEqualTo("TestBean"); } + } diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml index 64f222cdd37..1c2ba7c237b 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-context.xml @@ -16,12 +16,6 @@ - - - - - - diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml index 852f7479377..2f2024ada6c 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-directPointcutEvents.xml @@ -9,6 +9,6 @@ - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml index 8300b280512..0b12eb945e5 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerEventTests-pointcutRefEvents.xml @@ -10,6 +10,6 @@ - + diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml index 33693b7c1c4..75ca4e9d364 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutDuplication.xml @@ -12,10 +12,6 @@ - - - - diff --git a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml index 3d2037805e4..0524b1071c6 100644 --- a/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml +++ b/spring-aop/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerPointcutErrorTests-pointcutMissing.xml @@ -12,10 +12,6 @@ - - - - diff --git a/spring-aop/src/testFixtures/java/org/springframework/aop/testfixture/advice/MethodCounter.java b/spring-aop/src/testFixtures/java/org/springframework/aop/testfixture/advice/MethodCounter.java index 384973bd9aa..3cdba737e89 100644 --- a/spring-aop/src/testFixtures/java/org/springframework/aop/testfixture/advice/MethodCounter.java +++ b/spring-aop/src/testFixtures/java/org/springframework/aop/testfixture/advice/MethodCounter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.HashMap; +import java.util.Map; /** * Abstract superclass for counting advices etc. @@ -31,7 +32,7 @@ public class MethodCounter implements Serializable { /** Method name --> count, does not understand overloading */ - private HashMap map = new HashMap<>(); + private Map map = new HashMap<>(); private int allCount; diff --git a/spring-aspects/spring-aspects.gradle b/spring-aspects/spring-aspects.gradle index 12adbfb7e5f..4ee0ef3fda6 100644 --- a/spring-aspects/spring-aspects.gradle +++ b/spring-aspects/spring-aspects.gradle @@ -11,7 +11,7 @@ sourceSets.test.java.srcDirs = files() aspectj.version = dependencyManagement.managedVersions['org.aspectj:aspectjweaver'] dependencies { - compile("org.aspectj:aspectjweaver") + api("org.aspectj:aspectjweaver") compileOnly("org.aspectj:aspectjrt") optional(project(":spring-aop")) // for @Async support optional(project(":spring-beans")) // for @Configurable support @@ -21,13 +21,13 @@ dependencies { optional(project(":spring-tx")) // for JPA, @Transactional support optional("javax.cache:cache-api") // for JCache aspect optional("javax.transaction:javax.transaction-api") // for @javax.transaction.Transactional support - testCompile(project(":spring-core")) // for CodeStyleAspect - testCompile(project(":spring-test")) - testCompile(testFixtures(project(":spring-context"))) - testCompile(testFixtures(project(":spring-context-support"))) - testCompile(testFixtures(project(":spring-core"))) - testCompile(testFixtures(project(":spring-tx"))) - testCompile("javax.mail:javax.mail-api") + testImplementation(project(":spring-core")) // for CodeStyleAspect + testImplementation(project(":spring-test")) + testImplementation(testFixtures(project(":spring-context"))) + testImplementation(testFixtures(project(":spring-context-support"))) + testImplementation(testFixtures(project(":spring-core"))) + testImplementation(testFixtures(project(":spring-tx"))) + testImplementation("javax.mail:javax.mail-api") testCompileOnly("org.aspectj:aspectjrt") } diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/ConfigurableObject.java b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/ConfigurableObject.java index c670dfc0692..02e0d6391fb 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/ConfigurableObject.java +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/ConfigurableObject.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. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.aspectj; /** - * Marker interface for domain object that need DI through aspects. + * Marker interface for domain objects that need DI through aspects. * * @author Ramnivas Laddad * @since 2.5 */ public interface ConfigurableObject { - } diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj index aed8e4ab65a..782ca35e077 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj @@ -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. @@ -59,7 +59,8 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor @Override public void destroy() { - clearTransactionManagerCache(); // An aspect is basically a singleton + // An aspect is basically a singleton -> cleanup on destruction + clearTransactionManagerCache(); } @SuppressAjWarnings("adviceDidNotMatch") diff --git a/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java b/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java index 7ca1037efbc..4235f801df7 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.java +++ b/spring-aspects/src/test/java/org/springframework/cache/aspectj/AspectJEnableCachingIsolatedTests.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. @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; @@ -107,10 +106,7 @@ public void multipleCachingConfigurers() { try { load(MultiCacheManagerConfigurer.class, EnableCachingConfig.class); } - catch (BeanCreationException ex) { - Throwable root = ex.getRootCause(); - boolean condition = root instanceof IllegalStateException; - assertThat(condition).isTrue(); + catch (IllegalStateException ex) { assertThat(ex.getMessage().contains("implementations of CachingConfigurer")).isTrue(); } } diff --git a/spring-aspects/src/test/java/org/springframework/cache/aspectj/JCacheAspectJNamespaceConfigTests.java b/spring-aspects/src/test/java/org/springframework/cache/aspectj/JCacheAspectJNamespaceConfigTests.java index 81ba6d0faff..c755d8c3f4a 100644 --- a/spring-aspects/src/test/java/org/springframework/cache/aspectj/JCacheAspectJNamespaceConfigTests.java +++ b/spring-aspects/src/test/java/org/springframework/cache/aspectj/JCacheAspectJNamespaceConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,18 @@ /** * @author Stephane Nicoll + * @author Sam Brannen */ public class JCacheAspectJNamespaceConfigTests extends AbstractJCacheAnnotationTests { @Override protected ApplicationContext getApplicationContext() { - return new GenericXmlApplicationContext( - "/org/springframework/cache/config/annotation-jcache-aspectj.xml"); + GenericXmlApplicationContext context = new GenericXmlApplicationContext(); + // Disallow bean definition overriding to test https://github.com/spring-projects/spring-framework/pull/27499 + context.setAllowBeanDefinitionOverriding(false); + context.load("/org/springframework/cache/config/annotation-jcache-aspectj.xml"); + context.refresh(); + return context; } } diff --git a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithPrivateAnnotatedMember.java b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithPrivateAnnotatedMember.java index 7d5b2e6d837..d0d824c1db7 100644 --- a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithPrivateAnnotatedMember.java +++ b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithPrivateAnnotatedMember.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.transaction.aspectj; import org.springframework.transaction.annotation.Transactional; @@ -29,4 +30,5 @@ public void doSomething() { @Transactional private void doInTransaction() {} + } diff --git a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithProtectedAnnotatedMember.java b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithProtectedAnnotatedMember.java index 359eab233cb..12c5db965d8 100644 --- a/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithProtectedAnnotatedMember.java +++ b/spring-aspects/src/test/java/org/springframework/transaction/aspectj/ClassWithProtectedAnnotatedMember.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.transaction.aspectj; import org.springframework.transaction.annotation.Transactional; @@ -29,4 +30,5 @@ public void doSomething() { @Transactional protected void doInTransaction() {} + } diff --git a/spring-beans/spring-beans.gradle b/spring-beans/spring-beans.gradle index 73e9942f8ef..e3f6f73b763 100644 --- a/spring-beans/spring-beans.gradle +++ b/spring-beans/spring-beans.gradle @@ -4,14 +4,14 @@ apply plugin: "groovy" apply plugin: "kotlin" dependencies { - compile(project(":spring-core")) + api(project(":spring-core")) optional("javax.inject:javax.inject") optional("org.yaml:snakeyaml") optional("org.codehaus.groovy:groovy-xml") optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.jetbrains.kotlin:kotlin-stdlib") - testCompile(testFixtures(project(":spring-core"))) - testCompile("javax.annotation:javax.annotation-api") + testImplementation(testFixtures(project(":spring-core"))) + testImplementation("javax.annotation:javax.annotation-api") testFixturesApi("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation("org.assertj:assertj-core") } @@ -23,8 +23,6 @@ sourceSets { } compileGroovy { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 options.compilerArgs += "-Werror" } diff --git a/spring-beans/src/jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml b/spring-beans/src/jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml index 8dceadf0727..d9336a02b9e 100644 --- a/spring-beans/src/jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml +++ b/spring-beans/src/jmh/resources/org/springframework/beans/factory/ConcurrentBeanFactoryBenchmark-context.xml @@ -1,14 +1,13 @@ - + - - - - diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index 16ab258a14e..b56ec6b9533 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -305,8 +305,10 @@ private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) Class componentType = propValue.getClass().getComponentType(); Object newArray = Array.newInstance(componentType, arrayIndex + 1); System.arraycopy(propValue, 0, newArray, 0, length); - setPropertyValue(tokens.actualName, newArray); - propValue = getPropertyValue(tokens.actualName); + int lastKeyIndex = tokens.canonicalName.lastIndexOf('['); + String propName = tokens.canonicalName.substring(0, lastKeyIndex); + setPropertyValue(propName, newArray); + propValue = getPropertyValue(propName); } Array.set(propValue, arrayIndex, convertedValue); } diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index 3980a24dd71..77aff7ffe08 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -41,8 +41,6 @@ import kotlin.reflect.full.KClasses; import kotlin.reflect.jvm.KCallablesJvm; import kotlin.reflect.jvm.ReflectJvmMapping; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.KotlinDetector; @@ -75,8 +73,6 @@ */ public abstract class BeanUtils { - private static final Log logger = LogFactory.getLog(BeanUtils.class); - private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @@ -91,7 +87,10 @@ public abstract class BeanUtils { values.put(byte.class, (byte) 0); values.put(short.class, (short) 0); values.put(int.class, 0); - values.put(long.class, (long) 0); + values.put(long.class, 0L); + values.put(float.class, 0F); + values.put(double.class, 0D); + values.put(char.class, '\0'); DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values); } @@ -101,9 +100,9 @@ public abstract class BeanUtils { * @param clazz class to instantiate * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated + * @see Class#newInstance() * @deprecated as of Spring 5.0, following the deprecation of * {@link Class#newInstance()} in JDK 9 - * @see Class#newInstance() */ @Deprecated public static T instantiate(Class clazz) throws BeanInstantiationException { @@ -227,32 +226,45 @@ public static T instantiateClass(Constructor ctor, Object... args) throws } /** - * Return a resolvable constructor for the provided class, either a primary constructor - * or single public constructor or simply a default constructor. Callers have to be - * prepared to resolve arguments for the returned constructor's parameters, if any. + * Return a resolvable constructor for the provided class, either a primary or single + * public constructor with arguments, or a single non-public constructor with arguments, + * or simply a default constructor. Callers have to be prepared to resolve arguments + * for the returned constructor's parameters, if any. * @param clazz the class to check + * @throws IllegalStateException in case of no unique constructor found at all * @since 5.3 * @see #findPrimaryConstructor */ @SuppressWarnings("unchecked") public static Constructor getResolvableConstructor(Class clazz) { Constructor ctor = findPrimaryConstructor(clazz); - if (ctor == null) { - Constructor[] ctors = clazz.getConstructors(); + if (ctor != null) { + return ctor; + } + + Constructor[] ctors = clazz.getConstructors(); + if (ctors.length == 1) { + // A single public constructor + return (Constructor) ctors[0]; + } + else if (ctors.length == 0){ + ctors = clazz.getDeclaredConstructors(); if (ctors.length == 1) { - ctor = (Constructor) ctors[0]; - } - else { - try { - ctor = clazz.getDeclaredConstructor(); - } - catch (NoSuchMethodException ex) { - throw new IllegalStateException("No primary or single public constructor found for " + - clazz + " - and no default constructor found either"); - } + // A single non-public constructor, e.g. from a non-public record type + return (Constructor) ctors[0]; } } - return ctor; + + // Several constructors -> let's try to take the default constructor + try { + return clazz.getDeclaredConstructor(); + } + catch (NoSuchMethodException ex) { + // Giving up... + } + + // No unique constructor at all + throw new IllegalStateException("No primary or single unique constructor found for " + clazz); } /** @@ -268,10 +280,7 @@ public static Constructor getResolvableConstructor(Class clazz) { public static Constructor findPrimaryConstructor(Class clazz) { Assert.notNull(clazz, "Class must not be null"); if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) { - Constructor kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz); - if (kotlinPrimaryConstructor != null) { - return kotlinPrimaryConstructor; - } + return KotlinDelegate.findPrimaryConstructor(clazz); } return null; } @@ -531,7 +540,7 @@ public static PropertyDescriptor findPropertyForMethod(Method method, Class c /** * Find a JavaBeans PropertyEditor following the 'Editor' suffix convention - * (e.g. "mypackage.MyDomainClass" -> "mypackage.MyDomainClassEditor"). + * (e.g. "mypackage.MyDomainClass" → "mypackage.MyDomainClassEditor"). *

    Compatible to the standard JavaBeans convention as implemented by * {@link java.beans.PropertyEditorManager} but isolated from the latter's * registered default editors for primitive types. @@ -543,6 +552,7 @@ public static PropertyEditor findEditorByConvention(@Nullable Class targetTyp if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) { return null; } + ClassLoader cl = targetType.getClassLoader(); if (cl == null) { try { @@ -553,34 +563,29 @@ public static PropertyEditor findEditorByConvention(@Nullable Class targetTyp } catch (Throwable ex) { // e.g. AccessControlException on Google App Engine - if (logger.isDebugEnabled()) { - logger.debug("Could not access system ClassLoader: " + ex); - } return null; } } + String targetTypeName = targetType.getName(); String editorName = targetTypeName + "Editor"; try { Class editorClass = cl.loadClass(editorName); - if (!PropertyEditor.class.isAssignableFrom(editorClass)) { - if (logger.isInfoEnabled()) { - logger.info("Editor class [" + editorName + - "] does not implement [java.beans.PropertyEditor] interface"); + if (editorClass != null) { + if (!PropertyEditor.class.isAssignableFrom(editorClass)) { + unknownEditorTypes.add(targetType); + return null; } - unknownEditorTypes.add(targetType); - return null; + return (PropertyEditor) instantiateClass(editorClass); } - return (PropertyEditor) instantiateClass(editorClass); + // Misbehaving ClassLoader returned null instead of ClassNotFoundException + // - fall back to unknown editor type registration below } catch (ClassNotFoundException ex) { - if (logger.isTraceEnabled()) { - logger.trace("No property editor [" + editorName + "] found for type " + - targetTypeName + " according to 'Editor' suffix convention"); - } - unknownEditorTypes.add(targetType); - return null; + // Ignore - fall back to unknown editor type registration below } + unknownEditorTypes.add(targetType); + return null; } /** @@ -772,7 +777,14 @@ private static void copyProperties(Object source, Object target, @Nullable Class if (readMethod != null) { ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod); ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0); - if (targetResolvableType.isAssignableFrom(sourceResolvableType)) { + + // Ignore generic types in assignable check if either ResolvableType has unresolvable generics. + boolean isAssignable = + (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ? + ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : + targetResolvableType.isAssignableFrom(sourceResolvableType)); + + if (isAssignable) { try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); diff --git a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 603f5aae150..b4052b7b84e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -138,7 +138,7 @@ public Method getWriteMethodForActualAccess() { Set ambiguousCandidates = this.ambiguousWriteMethods; if (ambiguousCandidates != null) { this.ambiguousWriteMethods = null; - LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).warn("Invalid JavaBean property '" + + LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).debug("Non-unique JavaBean property '" + getName() + "' being accessed! Ambiguous write methods found next to actually used [" + this.writeMethod + "]: " + ambiguousCandidates); } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java index 55f50e5a275..564194e2f0a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessorUtils.java @@ -134,8 +134,8 @@ public static boolean matchesProperty(String registeredPath, String propertyPath /** * Determine the canonical name for the given property path. * Removes surrounding quotes from map keys:
    - * {@code map['key']} -> {@code map[key]}
    - * {@code map["key"]} -> {@code map[key]} + * {@code map['key']} → {@code map[key]}
    + * {@code map["key"]} → {@code map[key]} * @param propertyName the bean property path * @return the canonical representation of the property path */ diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index d1354e1d89b..e17a4e52e69 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -422,10 +422,13 @@ private PropertyEditor getCustomEditor(@Nullable Class requiredType) { } if (editor == null) { // Find editor for superclass or interface. - for (Iterator> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) { - Class key = it.next(); + for (Map.Entry, PropertyEditor> entry : this.customEditors.entrySet()) { + if (editor != null) { + break; + } + Class key = entry.getKey(); if (key.isAssignableFrom(requiredType)) { - editor = this.customEditors.get(key); + editor = entry.getValue(); // Cache editor for search type, to avoid the overhead // of repeated assignable-from checks. if (this.customEditorCache == null) { diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index 38a59ba3d68..2c419b1c109 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -247,14 +247,14 @@ else if (conversionService != null && typeDescriptor != null) { // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException StringBuilder msg = new StringBuilder(); msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue)); - msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'"); + msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append('\''); if (propertyName != null) { - msg.append(" for property '").append(propertyName).append("'"); + msg.append(" for property '").append(propertyName).append('\''); } if (editor != null) { msg.append(": PropertyEditor [").append(editor.getClass().getName()).append( "] returned inappropriate value of type '").append( - ClassUtils.getDescriptiveType(convertedValue)).append("'"); + ClassUtils.getDescriptiveType(convertedValue)).append('\''); throw new IllegalArgumentException(msg.toString()); } else { diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeMismatchException.java b/spring-beans/src/main/java/org/springframework/beans/TypeMismatchException.java index 3177c4b20ed..4a6f52400e8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeMismatchException.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeMismatchException.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. @@ -41,10 +41,10 @@ public class TypeMismatchException extends PropertyAccessException { private String propertyName; @Nullable - private transient Object value; + private final transient Object value; @Nullable - private Class requiredType; + private final Class requiredType; /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index f31c29d8517..93c909e047b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -84,7 +84,7 @@ * (only applicable when running in a web application context) *

  • {@code postProcessBeforeInitialization} methods of BeanPostProcessors *
  • InitializingBean's {@code afterPropertiesSet} - *
  • a custom init-method definition + *
  • a custom {@code init-method} definition *
  • {@code postProcessAfterInitialization} methods of BeanPostProcessors * * @@ -92,7 +92,7 @@ *
      *
    1. {@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors *
    2. DisposableBean's {@code destroy} - *
    3. a custom destroy-method definition + *
    4. a custom {@code destroy-method} definition *
    * * @author Rod Johnson @@ -102,6 +102,8 @@ * @see BeanNameAware#setBeanName * @see BeanClassLoaderAware#setBeanClassLoader * @see BeanFactoryAware#setBeanFactory + * @see org.springframework.context.EnvironmentAware#setEnvironment + * @see org.springframework.context.EmbeddedValueResolverAware#setEmbeddedValueResolver * @see org.springframework.context.ResourceLoaderAware#setResourceLoader * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher * @see org.springframework.context.MessageSourceAware#setMessageSource @@ -111,6 +113,7 @@ * @see InitializingBean#afterPropertiesSet * @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization + * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor#postProcessBeforeDestruction * @see DisposableBean#destroy * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName */ @@ -211,6 +214,7 @@ public interface BeanFactory { /** * Return a provider for the specified bean, allowing for lazy on-demand retrieval * of instances, including availability and uniqueness options. + *

    For matching a generic type, consider {@link #getBeanProvider(ResolvableType)}. * @param requiredType type the bean must match; can be an interface or superclass * @return a corresponding provider handle * @since 5.1 @@ -220,13 +224,20 @@ public interface BeanFactory { /** * Return a provider for the specified bean, allowing for lazy on-demand retrieval - * of instances, including availability and uniqueness options. - * @param requiredType type the bean must match; can be a generic type declaration. - * Note that collection types are not supported here, in contrast to reflective + * of instances, including availability and uniqueness options. This variant allows + * for specifying a generic type to match, similar to reflective injection points + * with generic type declarations in method/constructor parameters. + *

    Note that collections of beans are not supported here, in contrast to reflective * injection points. For programmatically retrieving a list of beans matching a * specific type, specify the actual bean type as an argument here and subsequently * use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options. + *

    Also, generics matching is strict here, as per the Java assignment rules. + * For lenient fallback matching with unchecked semantics (similar to the ´unchecked´ + * Java compiler warning), consider calling {@link #getBeanProvider(Class)} with the + * raw type as a second step if no full generic match is + * {@link ObjectProvider#getIfAvailable() available} with this variant. * @return a corresponding provider handle + * @param requiredType type the bean must match; can be a generic type declaration * @since 5.1 * @see ObjectProvider#iterator() * @see ObjectProvider#stream() diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java index 389f19c9e48..2642e72d7ae 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -353,9 +353,31 @@ Map getBeansOfType(@Nullable Class type, boolean includeNonSin * @since 3.0 * @see #getBeanNamesForAnnotation * @see #getBeansWithAnnotation + * @see #getType(String) */ @Nullable A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException; + /** + * Find an {@link Annotation} of {@code annotationType} on the specified bean, + * traversing its interfaces and super classes if no annotation can be found on + * the given class itself, as well as checking the bean's factory method (if any). + * @param beanName the name of the bean to look for annotations on + * @param annotationType the type of annotation to look for + * (at class, interface or factory method level of the specified bean) + * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized + * just for the purpose of determining its object type + * @return the annotation of the given type if found, or {@code null} otherwise + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 5.3.14 + * @see #getBeanNamesForAnnotation + * @see #getBeansWithAnnotation + * @see #getType(String, boolean) + */ + @Nullable + A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException; + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index bd42f89d301..f31d48191a4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -459,7 +459,7 @@ private InjectionMetadata findAutowiringMetadata(String beanName, Class clazz return metadata; } - private InjectionMetadata buildAutowiringMetadata(final Class clazz) { + private InjectionMetadata buildAutowiringMetadata(Class clazz) { if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) { return InjectionMetadata.EMPTY; } @@ -537,11 +537,10 @@ private MergedAnnotation findAutowiredAnnotation(AccessibleObject ao) { * @param ann the Autowired annotation * @return whether the annotation indicates that a dependency is required */ - @SuppressWarnings({"deprecation", "cast"}) + @SuppressWarnings("deprecation") protected boolean determineRequiredStatus(MergedAnnotation ann) { - // The following (AnnotationAttributes) cast is required on JDK 9+. - return determineRequiredStatus((AnnotationAttributes) - ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()))); + return determineRequiredStatus(ann. asMap( + mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()))); } /** @@ -628,45 +627,58 @@ protected void inject(Object bean, @Nullable String beanName, @Nullable Property Field field = (Field) this.member; Object value; if (this.cached) { - value = resolvedCachedArgument(beanName, this.cachedFieldValue); - } - else { - DependencyDescriptor desc = new DependencyDescriptor(field, this.required); - desc.setContainingClass(bean.getClass()); - Set autowiredBeanNames = new LinkedHashSet<>(1); - Assert.state(beanFactory != null, "No BeanFactory available"); - TypeConverter typeConverter = beanFactory.getTypeConverter(); try { - value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); - } - catch (BeansException ex) { - throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); + value = resolvedCachedArgument(beanName, this.cachedFieldValue); } - synchronized (this) { - if (!this.cached) { - Object cachedFieldValue = null; - if (value != null || this.required) { - cachedFieldValue = desc; - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == 1) { - String autowiredBeanName = autowiredBeanNames.iterator().next(); - if (beanFactory.containsBean(autowiredBeanName) && - beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { - cachedFieldValue = new ShortcutDependencyDescriptor( - desc, autowiredBeanName, field.getType()); - } - } - } - this.cachedFieldValue = cachedFieldValue; - this.cached = true; - } + catch (NoSuchBeanDefinitionException ex) { + // Unexpected removal of target bean for cached argument -> re-resolve + value = resolveFieldValue(field, bean, beanName); } } + else { + value = resolveFieldValue(field, bean, beanName); + } if (value != null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); } } + + @Nullable + private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) { + DependencyDescriptor desc = new DependencyDescriptor(field, this.required); + desc.setContainingClass(bean.getClass()); + Set autowiredBeanNames = new LinkedHashSet<>(1); + Assert.state(beanFactory != null, "No BeanFactory available"); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + Object value; + try { + value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); + } + synchronized (this) { + if (!this.cached) { + Object cachedFieldValue = null; + if (value != null || this.required) { + cachedFieldValue = desc; + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == 1) { + String autowiredBeanName = autowiredBeanNames.iterator().next(); + if (beanFactory.containsBean(autowiredBeanName) && + beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { + cachedFieldValue = new ShortcutDependencyDescriptor( + desc, autowiredBeanName, field.getType()); + } + } + } + this.cachedFieldValue = cachedFieldValue; + this.cached = true; + } + } + return value; + } } @@ -695,59 +707,17 @@ protected void inject(Object bean, @Nullable String beanName, @Nullable Property Method method = (Method) this.member; Object[] arguments; if (this.cached) { - // Shortcut for avoiding synchronization... - arguments = resolveCachedArguments(beanName); - } - else { - int argumentCount = method.getParameterCount(); - arguments = new Object[argumentCount]; - DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount]; - Set autowiredBeans = new LinkedHashSet<>(argumentCount); - Assert.state(beanFactory != null, "No BeanFactory available"); - TypeConverter typeConverter = beanFactory.getTypeConverter(); - for (int i = 0; i < arguments.length; i++) { - MethodParameter methodParam = new MethodParameter(method, i); - DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); - currDesc.setContainingClass(bean.getClass()); - descriptors[i] = currDesc; - try { - Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); - if (arg == null && !this.required) { - arguments = null; - break; - } - arguments[i] = arg; - } - catch (BeansException ex) { - throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); - } + try { + arguments = resolveCachedArguments(beanName); } - synchronized (this) { - if (!this.cached) { - if (arguments != null) { - DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length); - registerDependentBeans(beanName, autowiredBeans); - if (autowiredBeans.size() == argumentCount) { - Iterator it = autowiredBeans.iterator(); - Class[] paramTypes = method.getParameterTypes(); - for (int i = 0; i < paramTypes.length; i++) { - String autowiredBeanName = it.next(); - if (beanFactory.containsBean(autowiredBeanName) && - beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { - cachedMethodArguments[i] = new ShortcutDependencyDescriptor( - descriptors[i], autowiredBeanName, paramTypes[i]); - } - } - } - this.cachedMethodArguments = cachedMethodArguments; - } - else { - this.cachedMethodArguments = null; - } - this.cached = true; - } + catch (NoSuchBeanDefinitionException ex) { + // Unexpected removal of target bean for cached argument -> re-resolve + arguments = resolveMethodArguments(method, bean, beanName); } } + else { + arguments = resolveMethodArguments(method, bean, beanName); + } if (arguments != null) { try { ReflectionUtils.makeAccessible(method); @@ -771,6 +741,59 @@ private Object[] resolveCachedArguments(@Nullable String beanName) { } return arguments; } + + @Nullable + private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) { + int argumentCount = method.getParameterCount(); + Object[] arguments = new Object[argumentCount]; + DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount]; + Set autowiredBeans = new LinkedHashSet<>(argumentCount); + Assert.state(beanFactory != null, "No BeanFactory available"); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + for (int i = 0; i < arguments.length; i++) { + MethodParameter methodParam = new MethodParameter(method, i); + DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); + currDesc.setContainingClass(bean.getClass()); + descriptors[i] = currDesc; + try { + Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); + if (arg == null && !this.required) { + arguments = null; + break; + } + arguments[i] = arg; + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); + } + } + synchronized (this) { + if (!this.cached) { + if (arguments != null) { + DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length); + registerDependentBeans(beanName, autowiredBeans); + if (autowiredBeans.size() == argumentCount) { + Iterator it = autowiredBeans.iterator(); + Class[] paramTypes = method.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + String autowiredBeanName = it.next(); + if (beanFactory.containsBean(autowiredBeanName) && + beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { + cachedMethodArguments[i] = new ShortcutDependencyDescriptor( + descriptors[i], autowiredBeanName, paramTypes[i]); + } + } + } + this.cachedMethodArguments = cachedMethodArguments; + } + else { + this.cachedMethodArguments = null; + } + this.cached = true; + } + } + return arguments; + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java index f5cc0f9d528..f7dcb8d18cf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java @@ -141,8 +141,7 @@ public void clear(@Nullable PropertyValues pvs) { * Return an {@code InjectionMetadata} instance, possibly for empty elements. * @param elements the elements to inject (possibly empty) * @param clazz the target class - * @return a new {@link #InjectionMetadata(Class, Collection)} instance, - * or {@link #EMPTY} in case of no elements + * @return a new {@link #InjectionMetadata(Class, Collection)} instance * @since 5.2 */ public static InjectionMetadata forElements(Collection elements, Class clazz) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java index 9978f979b50..a4cf5f314ce 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -217,13 +217,13 @@ private String buildExceptionMessage(List invalidProperties, String bean sb.append(" and"); } else { - sb.append(","); + sb.append(','); } } - sb.append(" '").append(propertyName).append("'"); + sb.append(" '").append(propertyName).append('\''); } sb.append(size == 1 ? " is" : " are"); - sb.append(" required for bean '").append(beanName).append("'"); + sb.append(" required for bean '").append(beanName).append('\''); return sb.toString(); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java index 4fbfb435875..c4d779e697b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -351,7 +351,9 @@ public ValueHolder getArgumentValue(int index, Class requiredType, String req * @return the ValueHolder for the argument, or {@code null} if none set */ @Nullable - public ValueHolder getArgumentValue(int index, @Nullable Class requiredType, @Nullable String requiredName, @Nullable Set usedValueHolders) { + public ValueHolder getArgumentValue(int index, @Nullable Class requiredType, + @Nullable String requiredName, @Nullable Set usedValueHolders) { + Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName); if (valueHolder == null) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java index c4e21122ed8..03e192e72e4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.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. @@ -34,9 +34,7 @@ * *

    NOTE: This interface is a special purpose interface, mainly for * internal use within the framework. It is recommended to implement the plain - * {@link BeanPostProcessor} interface as far as possible, or to derive from - * {@link InstantiationAwareBeanPostProcessorAdapter} in order to be shielded - * from extensions to this interface. + * {@link BeanPostProcessor} interface as far as possible. * * @author Juergen Hoeller * @author Rod Johnson diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingBean.java index 1374c4105e9..57fa7ab222c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingBean.java @@ -47,17 +47,17 @@ * which uses this class to call a static initialization method: * *

    - * <bean id="myObject" class="org.springframework.beans.factory.config.MethodInvokingBean">
    - *   <property name="staticMethod" value="com.whatever.MyClass.init"/>
    - * </bean>
    + * <bean id="myObject" class="org.springframework.beans.factory.config.MethodInvokingBean"> + * <property name="staticMethod" value="com.whatever.MyClass.init"/> + * </bean> * *

    An example of calling an instance method to start some server bean: * *

    - * <bean id="myStarter" class="org.springframework.beans.factory.config.MethodInvokingBean">
    - *   <property name="targetObject" ref="myServer"/>
    - *   <property name="targetMethod" value="start"/>
    - * </bean>
    + * <bean id="myStarter" class="org.springframework.beans.factory.config.MethodInvokingBean"> + * <property name="targetObject" ref="myServer"/> + * <property name="targetMethod" value="start"/> + * </bean> * * @author Juergen Hoeller * @since 4.0.3 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java index 86a6be98564..3ff39d81e5e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/MethodInvokingFactoryBean.java @@ -56,24 +56,24 @@ * which uses this class to call a static factory method: * *
    - * <bean id="myObject" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    - *   <property name="staticMethod" value="com.whatever.MyClassFactory.getInstance"/>
    - * </bean>
    + * <bean id="myObject" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> + * <property name="staticMethod" value="com.whatever.MyClassFactory.getInstance"/> + * </bean> * *

    An example of calling a static method then an instance method to get at a * Java system property. Somewhat verbose, but it works. * *

    - * <bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    - *   <property name="targetClass" value="java.lang.System"/>
    - *   <property name="targetMethod" value="getProperties"/>
    - * </bean>
    + * <bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    + *   <property name="targetClass" value="java.lang.System"/>
    + *   <property name="targetMethod" value="getProperties"/>
    + * </bean>
      *
    - * <bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    - *   <property name="targetObject" ref="sysProps"/>
    - *   <property name="targetMethod" value="getProperty"/>
    - *   <property name="arguments" value="java.version"/>
    - * </bean>
    + * <bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> + * <property name="targetObject" ref="sysProps"/> + * <property name="targetMethod" value="getProperty"/> + * <property name="arguments" value="java.version"/> + * </bean> * * @author Colin Sampaleanu * @author Juergen Hoeller diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java index fc29a9eda99..9a11f7af3ff 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.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. @@ -36,15 +36,16 @@ * Example XML bean definition: * *
    - * <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
    - *   <property name="driverClassName" value="${driver}"/>
    - *   <property name="url" value="jdbc:${dbname}"/>
    + * <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    + *   <property name="driverClassName" value="${driver}" />
    + *   <property name="url" value="jdbc:${dbname}" />
      * </bean>
      * 
    * * Example properties file: * - *
    driver=com.mysql.jdbc.Driver
    + * 
    + * driver=com.mysql.jdbc.Driver
      * dbname=mysql:mydb
    * * Annotated bean definitions may take advantage of property replacement using @@ -56,7 +57,8 @@ * in bean references. Furthermore, placeholder values can also cross-reference * other placeholders, like: * - *
    rootPath=myrootdir
    + * 
    + * rootPath=myrootdir
      * subPath=${rootPath}/subdir
    * * In contrast to {@link PropertyOverrideConfigurer}, subclasses of this type allow @@ -71,13 +73,13 @@ * *

    Default property values can be defined globally for each configurer instance * via the {@link #setProperties properties} property, or on a property-by-property basis - * using the default value separator which is {@code ":"} by default and - * customizable via {@link #setValueSeparator(String)}. + * using the value separator which is {@code ":"} by default and customizable via + * {@link #setValueSeparator(String)}. * *

    Example XML property with default value: * *

    - *   
    + *   <property name="url" value="jdbc:${dbname:defaultdb}" />
      * 
    * * @author Chris Beams diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java index e0ee01e6e3d..272e97f5b74 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java @@ -83,22 +83,22 @@ *

    A sample config in an XML-based * {@link org.springframework.beans.factory.BeanFactory} might look as follows: * - *

    <beans>
    + * 
    <beans>
      *
    - *   <!-- Prototype bean since we have state -->
    - *   <bean id="myService" class="a.b.c.MyService" singleton="false"/>
    + *   <!-- Prototype bean since we have state -->
    + *   <bean id="myService" class="a.b.c.MyService" singleton="false"/>
      *
    - *   <!-- will lookup the above 'myService' bean by *TYPE* -->
    + *   <!-- will lookup the above 'myService' bean by *TYPE* -->
      *   <bean id="myServiceFactory"
    - *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    - *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
    - *   </bean>
    + *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    + *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
    + *   </bean>
      *
    - *   <bean id="clientBean" class="a.b.c.MyClientBean">
    - *     <property name="myServiceFactory" ref="myServiceFactory"/>
    - *   </bean>
    + *   <bean id="clientBean" class="a.b.c.MyClientBean">
    + *     <property name="myServiceFactory" ref="myServiceFactory"/>
    + *   </bean>
      *
    - *</beans>
    + *</beans>
    * *

    The attendant {@code MyClientBean} class implementation might then * look something like this: @@ -135,22 +135,22 @@ *

    A sample config in an XML-based * {@link org.springframework.beans.factory.BeanFactory} might look as follows: * - *

    <beans>
    + * 
    <beans>
      *
    - *   <!-- Prototype beans since we have state (both extend MyService) -->
    - *   <bean id="specialService" class="a.b.c.SpecialService" singleton="false"/>
    - *   <bean id="anotherService" class="a.b.c.AnotherService" singleton="false"/>
    + *   <!-- Prototype beans since we have state (both extend MyService) -->
    + *   <bean id="specialService" class="a.b.c.SpecialService" singleton="false"/>
    + *   <bean id="anotherService" class="a.b.c.AnotherService" singleton="false"/>
      *
      *   <bean id="myServiceFactory"
    - *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    - *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
    - *   </bean>
    + *            class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
    + *     <property name="serviceLocatorInterface" value="a.b.c.ServiceFactory"/>
    + *   </bean>
      *
    - *   <bean id="clientBean" class="a.b.c.MyClientBean">
    - *     <property name="myServiceFactory" ref="myServiceFactory"/>
    - *   </bean>
    + *   <bean id="clientBean" class="a.b.c.MyClientBean">
    + *     <property name="myServiceFactory" ref="myServiceFactory"/>
    + *   </bean>
      *
    - *</beans>
    + *</beans>
    * *

    The attendant {@code MyClientBean} class implementation might then * look something like this: diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java index a7ea8c8d1fe..7949003c68b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -52,6 +52,7 @@ * @author Dave Syer * @author Juergen Hoeller * @author Sam Brannen + * @author Brian Clozel * @since 4.1 */ public abstract class YamlProcessor { @@ -85,7 +86,7 @@ public abstract class YamlProcessor { *

    * when mapped with *
    -	 * setDocumentMatchers(properties ->
    +	 * setDocumentMatchers(properties ->
     	 *     ("prod".equals(properties.getProperty("environment")) ? MatchStatus.FOUND : MatchStatus.NOT_FOUND));
     	 * 
    * would end up as @@ -128,10 +129,11 @@ public void setResources(Resource... resources) { /** * Set the supported types that can be loaded from YAML documents. - *

    If no supported types are configured, all types encountered in YAML - * documents will be supported. If an unsupported type is encountered, an - * {@link IllegalStateException} will be thrown when the corresponding YAML - * node is processed. + *

    If no supported types are configured, only Java standard classes + * (as defined in {@link org.yaml.snakeyaml.constructor.SafeConstructor}) + * encountered in YAML documents will be supported. + * If an unsupported type is encountered, an {@link IllegalStateException} + * will be thrown when the corresponding YAML node is processed. * @param supportedTypes the supported types, or an empty array to clear the * supported types * @since 5.1.16 @@ -182,12 +184,8 @@ protected void process(MatchCallback callback) { protected Yaml createYaml() { LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowDuplicateKeys(false); - - if (!this.supportedTypes.isEmpty()) { - return new Yaml(new FilteringConstructor(loaderOptions), new Representer(), - new DumperOptions(), loaderOptions); - } - return new Yaml(loaderOptions); + return new Yaml(new FilteringConstructor(loaderOptions), new Representer(), + new DumperOptions(), loaderOptions); } private boolean process(MatchCallback callback, Yaml yaml, Resource resource) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java index cf13a408173..9ef7aa3799f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/groovy/GroovyBeanDefinitionReader.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. @@ -75,18 +75,18 @@ * * def reader = new GroovyBeanDefinitionReader(myApplicationContext) * reader.beans { - * dataSource(BasicDataSource) { // <--- invokeMethod + * dataSource(BasicDataSource) { // <--- invokeMethod * driverClassName = "org.hsqldb.jdbcDriver" * url = "jdbc:hsqldb:mem:grailsDB" - * username = "sa" // <-- setProperty + * username = "sa" // <-- setProperty * password = "" * settings = [mynew:"setting"] * } * sessionFactory(SessionFactory) { - * dataSource = dataSource // <-- getProperty for retrieving references + * dataSource = dataSource // <-- getProperty for retrieving references * } * myService(MyService) { - * nestedBean = { AnotherBean bean -> // <-- setProperty with closure for nested bean + * nestedBean = { AnotherBean bean -> // <-- setProperty with closure for nested bean * dataSource = dataSource * } * } @@ -113,7 +113,7 @@ * dataSource = dataSource * } * myService(MyService) { - * nestedBean = { AnotherBean bean -> + * nestedBean = { AnotherBean bean -> * dataSource = dataSource * } * } @@ -489,9 +489,9 @@ else if (args[0] instanceof Map) { resolveConstructorArguments(args, 2, hasClosureArgument ? args.length - 1 : args.length); this.currentBeanDefinition = new GroovyBeanDefinitionWrapper(beanName, (Class) args[1], constructorArgs); Map namedArgs = (Map) args[0]; - for (Object o : namedArgs.keySet()) { - String propName = (String) o; - setProperty(propName, namedArgs.get(propName)); + for (Map.Entry entity : namedArgs.entrySet()) { + String propName = (String) entity.getKey(); + setProperty(propName, entity.getValue()); } } // factory method syntax diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 4defe323d46..7a94a91c86a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,6 +73,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.NamedThreadLocal; +import org.springframework.core.NativeDetector; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.PriorityOrdered; import org.springframework.core.ResolvableType; @@ -123,14 +124,6 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { - /** - * Whether this environment lives within a native image. - * Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594. - * @see ImageInfo.java - */ - private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null); - - /** Strategy for creating bean instances. */ private InstantiationStrategy instantiationStrategy; @@ -184,7 +177,7 @@ public AbstractAutowireCapableBeanFactory() { ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); - if (IN_NATIVE_IMAGE) { + if (NativeDetector.inNativeImage()) { this.instantiationStrategy = new SimpleInstantiationStrategy(); } else { @@ -253,6 +246,15 @@ public void setAllowCircularReferences(boolean allowCircularReferences) { this.allowCircularReferences = allowCircularReferences; } + /** + * Return whether to allow circular references between beans. + * @since 5.3.10 + * @see #setAllowCircularReferences + */ + public boolean isAllowCircularReferences() { + return this.allowCircularReferences; + } + /** * Set whether to allow the raw injection of a bean instance into some other * bean's property, despite the injected bean eventually getting wrapped @@ -271,6 +273,15 @@ public void setAllowRawInjectionDespiteWrapping(boolean allowRawInjectionDespite this.allowRawInjectionDespiteWrapping = allowRawInjectionDespiteWrapping; } + /** + * Return whether to allow the raw injection of a bean instance. + * @since 5.3.10 + * @see #setAllowRawInjectionDespiteWrapping + */ + public boolean isAllowRawInjectionDespiteWrapping() { + return this.allowRawInjectionDespiteWrapping; + } + /** * Ignore the given dependency type for autowiring: * for example, String. Default is none. @@ -1022,6 +1033,11 @@ private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, Root throw ex; } catch (BeanCreationException ex) { + // Don't swallow a linkage error since it contains a full stacktrace on + // first occurrence... and just a plain NoClassDefFoundError afterwards. + if (ex.contains(LinkageError.class)) { + throw ex; + } // Instantiation failure, maybe too early... if (logger.isDebugEnabled()) { logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex); @@ -1326,7 +1342,7 @@ protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { * @param beanName the name of the bean * @param mbd the bean definition for the bean * @param explicitArgs argument values passed in programmatically via the getBean method, - * or {@code null} if none (-> use constructor argument values from bean definition) + * or {@code null} if none (implying the use of constructor argument values from bean definition) * @return a BeanWrapper for the new instance * @see #getBean(String, Object[]) */ @@ -1347,7 +1363,7 @@ protected BeanWrapper instantiateUsingFactoryMethod( * @param mbd the bean definition for the bean * @param ctors the chosen candidate constructors * @param explicitArgs argument values passed in programmatically via the getBean method, - * or {@code null} if none (-> use constructor argument values from bean definition) + * or {@code null} if none (implying the use of constructor argument values from bean definition) * @return a BeanWrapper for the new instance */ protected BeanWrapper autowireConstructor( @@ -1892,7 +1908,7 @@ protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefi if (logger.isTraceEnabled()) { logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); } - Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod); + Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass()); if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction) () -> { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index c30bc3271a1..2ceff658dbc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -894,7 +894,7 @@ public MutablePropertyValues getPropertyValues() { } /** - * Return if there are property values values defined for this bean. + * Return if there are property values defined for this bean. * @since 5.0.2 */ @Override @@ -1194,8 +1194,8 @@ public boolean equals(@Nullable Object other) { this.primary == that.primary && this.nonPublicAccessAllowed == that.nonPublicAccessAllowed && this.lenientConstructorResolution == that.lenientConstructorResolution && - ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues) && - ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues) && + equalsConstructorArgumentValues(that) && + equalsPropertyValues(that) && ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) && ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) && ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) && @@ -1208,12 +1208,30 @@ public boolean equals(@Nullable Object other) { super.equals(other)); } + private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) { + if (!hasConstructorArgumentValues()) { + return !other.hasConstructorArgumentValues(); + } + return ObjectUtils.nullSafeEquals(this.constructorArgumentValues, other.constructorArgumentValues); + } + + private boolean equalsPropertyValues(AbstractBeanDefinition other) { + if (!hasPropertyValues()) { + return !other.hasPropertyValues(); + } + return ObjectUtils.nullSafeEquals(this.propertyValues, other.propertyValues); + } + @Override public int hashCode() { int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName()); hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope); - hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues); - hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues); + if (hasConstructorArgumentValues()) { + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues); + } + if (hasPropertyValues()) { + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues); + } hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName); hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName); hashCode = 29 * hashCode + super.hashCode(); @@ -1223,7 +1241,7 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder("class ["); - sb.append(getBeanClassName()).append("]"); + sb.append(getBeanClassName()).append(']'); sb.append("; scope=").append(this.scope); sb.append("; abstract=").append(this.abstractFlag); sb.append("; lazyInit=").append(this.lazyInit); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java index a9a556caea4..c5fa1335218 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.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. @@ -103,6 +103,14 @@ protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { } + /** + * Return the bean factory to register the bean definitions with. + *

    The factory is exposed through the BeanDefinitionRegistry interface, + * encapsulating the methods that are relevant for bean definition handling. + * @deprecated as of Spring Framework 5.3.15 in favor of {@link #getRegistry()}, + * to be removed in Spring Framework 6.0 + */ + @Deprecated public final BeanDefinitionRegistry getBeanFactory() { return this.registry; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 443b7ee586d..e36cef41573 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -250,7 +250,7 @@ protected T doGetBean( throws BeansException { String beanName = transformedBeanName(name); - Object bean; + Object beanInstance; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); @@ -264,7 +264,7 @@ protected T doGetBean( logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } - bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); + beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { @@ -342,7 +342,7 @@ else if (requiredType != null) { throw ex; } }); - bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { @@ -355,13 +355,13 @@ else if (mbd.isPrototype()) { finally { afterPrototypeCreation(beanName); } - bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); + beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { - throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); + throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { @@ -377,7 +377,7 @@ else if (mbd.isPrototype()) { afterPrototypeCreation(beanName); } }); - bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); @@ -395,14 +395,19 @@ else if (mbd.isPrototype()) { } } + return adaptBeanInstance(name, beanInstance, requiredType); + } + + @SuppressWarnings("unchecked") + T adaptBeanInstance(String name, Object bean, @Nullable Class requiredType) { // Check if required type matches the type of the actual bean instance. if (requiredType != null && !requiredType.isInstance(bean)) { try { - T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); + Object convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } - return convertedBean; + return (T) convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java index dab55e957da..bd19259339e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.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,6 +21,7 @@ import org.springframework.beans.factory.config.AutowiredPropertyMarker; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; @@ -102,7 +103,7 @@ public static BeanDefinitionBuilder rootBeanDefinition(String beanClassName, @Nu * @param beanClass the {@code Class} of the bean that the definition is being created for */ public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass) { - return rootBeanDefinition(beanClass, null); + return rootBeanDefinition(beanClass, (String) null); } /** @@ -117,6 +118,30 @@ public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass, @Null return builder; } + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link RootBeanDefinition}. + * @param beanType the {@link ResolvableType type} of the bean that the definition is being created for + * @param instanceSupplier a callback for creating an instance of the bean + * @since 5.3.9 + */ + public static BeanDefinitionBuilder rootBeanDefinition(ResolvableType beanType, Supplier instanceSupplier) { + RootBeanDefinition beanDefinition = new RootBeanDefinition(); + beanDefinition.setTargetType(beanType); + beanDefinition.setInstanceSupplier(instanceSupplier); + return new BeanDefinitionBuilder(beanDefinition); + } + + /** + * Create a new {@code BeanDefinitionBuilder} used to construct a {@link RootBeanDefinition}. + * @param beanClass the {@code Class} of the bean that the definition is being created for + * @param instanceSupplier a callback for creating an instance of the bean + * @since 5.3.9 + * @see #rootBeanDefinition(ResolvableType, Supplier) + */ + public static BeanDefinitionBuilder rootBeanDefinition(Class beanClass, Supplier instanceSupplier) { + return rootBeanDefinition(ResolvableType.forClass(beanClass), instanceSupplier); + } + /** * Create a new {@code BeanDefinitionBuilder} used to construct a {@link ChildBeanDefinition}. * @param parentName the name of the parent bean @@ -331,6 +356,16 @@ public BeanDefinitionBuilder setRole(int role) { return this; } + /** + * Set whether this bean is 'synthetic', that is, not defined by + * the application itself. + * @since 5.3.9 + */ + public BeanDefinitionBuilder setSynthetic(boolean synthetic) { + this.beanDefinition.setSynthetic(synthetic); + return this; + } + /** * Apply the given customizers to the underlying bean definition. * @since 5.0 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java index a6fedff0379..8441abbc766 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,15 +22,15 @@ import org.springframework.lang.Nullable; /** - * Simple interface for bean definition readers. - * Specifies load methods with Resource and String location parameters. + * Simple interface for bean definition readers that specifies load methods with + * {@link Resource} and {@link String} location parameters. * *

    Concrete bean definition readers can of course add additional * load and register methods for bean definitions, specific to * their bean definition format. * *

    Note that a bean definition reader does not have to implement - * this interface. It only serves as suggestion for bean definition + * this interface. It only serves as a suggestion for bean definition * readers that want to follow standard naming conventions. * * @author Juergen Hoeller @@ -41,14 +41,14 @@ public interface BeanDefinitionReader { /** * Return the bean factory to register the bean definitions with. - *

    The factory is exposed through the BeanDefinitionRegistry interface, + *

    The factory is exposed through the {@link BeanDefinitionRegistry} interface, * encapsulating the methods that are relevant for bean definition handling. */ BeanDefinitionRegistry getRegistry(); /** - * Return the resource loader to use for resource locations. - * Can be checked for the ResourcePatternResolver interface and cast + * Return the {@link ResourceLoader} to use for resource locations. + *

    Can be checked for the {@code ResourcePatternResolver} interface and cast * accordingly, for loading multiple resources for a given resource pattern. *

    A {@code null} return value suggests that absolute resource loading * is not available for this bean definition reader. @@ -56,10 +56,10 @@ public interface BeanDefinitionReader { * from within a bean definition resource, for example via the "import" * tag in XML bean definitions. It is recommended, however, to apply * such imports relative to the defining resource; only explicit full - * resource locations will trigger absolute resource loading. + * resource locations will trigger absolute path based resource loading. *

    There is also a {@code loadBeanDefinitions(String)} method available, * for loading bean definitions from a resource location (or location pattern). - * This is a convenience to avoid explicit ResourceLoader handling. + * This is a convenience to avoid explicit {@code ResourceLoader} handling. * @see #loadBeanDefinitions(String) * @see org.springframework.core.io.support.ResourcePatternResolver */ @@ -70,13 +70,13 @@ public interface BeanDefinitionReader { * Return the class loader to use for bean classes. *

    {@code null} suggests to not load bean classes eagerly * but rather to just register bean definitions with class names, - * with the corresponding Classes to be resolved later (or never). + * with the corresponding classes to be resolved later (or never). */ @Nullable ClassLoader getBeanClassLoader(); /** - * Return the BeanNameGenerator to use for anonymous beans + * Return the {@link BeanNameGenerator} to use for anonymous beans * (without explicit bean name specified). */ BeanNameGenerator getBeanNameGenerator(); @@ -101,9 +101,10 @@ public interface BeanDefinitionReader { /** * Load bean definitions from the specified resource location. *

    The location can also be a location pattern, provided that the - * ResourceLoader of this bean definition reader is a ResourcePatternResolver. - * @param location the resource location, to be loaded with the ResourceLoader - * (or ResourcePatternResolver) of this bean definition reader + * {@link ResourceLoader} of this bean definition reader is a + * {@code ResourcePatternResolver}. + * @param location the resource location, to be loaded with the {@code ResourceLoader} + * (or {@code ResourcePatternResolver}) of this bean definition reader * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #getResourceLoader() @@ -114,8 +115,8 @@ public interface BeanDefinitionReader { /** * Load bean definitions from the specified resource locations. - * @param locations the resource locations, to be loaded with the ResourceLoader - * (or ResourcePatternResolver) of this bean definition reader + * @param locations the resource locations, to be loaded with the {@code ResourceLoader} + * (or {@code ResourcePatternResolver}) of this bean definition reader * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 698a04d2498..77f5301c486 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -35,6 +35,7 @@ import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.cglib.proxy.NoOp; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -244,8 +245,10 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp return (bean.equals(null) ? null : bean); } else { - return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : - this.owner.getBean(method.getReturnType())); + // Find target bean matching the (potentially generic) method return type + ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method); + return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) : + this.owner.getBeanProvider(genericReturnType).getObject()); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index f49690677a9..36190c3b817 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -67,6 +67,7 @@ /** * Delegate for resolving constructors and factory methods. + * *

    Performs constructor resolution through argument matching. * * @author Juergen Hoeller @@ -85,7 +86,7 @@ class ConstructorResolver { private static final Object[] EMPTY_ARGS = new Object[0]; /** - * Marker for autowired arguments in a cached argument array, to be later replaced + * Marker for autowired arguments in a cached argument array, to be replaced * by a {@linkplain #resolveAutowiredArgument resolved autowired argument}. */ private static final Object autowiredArgumentMarker = new Object(); @@ -149,7 +150,7 @@ public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, } } if (argsToResolve != null) { - argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true); + argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); } } @@ -276,12 +277,12 @@ else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { throw ex; } throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "Could not resolve matching constructor " + + "Could not resolve matching constructor on bean class [" + mbd.getBeanClassName() + "] " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)"); } else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "Ambiguous constructor matches found in bean '" + beanName + "' " + + "Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousConstructors); } @@ -410,6 +411,7 @@ public BeanWrapper instantiateUsingFactoryMethod( if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) { throw new ImplicitlyAppearedSingletonException(); } + this.beanFactory.registerDependentBean(factoryBeanName, beanName); factoryClass = factoryBean.getClass(); isStatic = false; } @@ -444,7 +446,7 @@ public BeanWrapper instantiateUsingFactoryMethod( } } if (argsToResolve != null) { - argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true); + argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve); } } @@ -606,7 +608,7 @@ else if (resolvedValues != null) { } String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes); throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "No matching factory method found: " + + "No matching factory method found on class [" + factoryClass.getName() + "]: " + (mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + "factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " + @@ -617,12 +619,12 @@ else if (resolvedValues != null) { } else if (void.class == factoryMethodToUse.getReturnType()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "Invalid factory method '" + mbd.getFactoryMethodName() + - "': needs to have a non-void return type!"); + "Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" + + factoryClass.getName() + "]: needs to have a non-void return type!"); } else if (ambiguousFactoryMethods != null) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, - "Ambiguous factory method matches found in bean '" + beanName + "' " + + "Ambiguous factory method matches found on class [" + factoryClass.getName() + "] " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousFactoryMethods); } @@ -816,7 +818,7 @@ private ArgumentsHolder createArgumentArray( * Resolve the prepared arguments stored in the given bean definition. */ private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, - Executable executable, Object[] argsToResolve, boolean fallback) { + Executable executable, Object[] argsToResolve) { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = (customConverter != null ? customConverter : bw); @@ -829,7 +831,7 @@ private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mb Object argValue = argsToResolve[argIndex]; MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); if (argValue == autowiredArgumentMarker) { - argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback); + argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true); } else if (argValue instanceof BeanMetadataElement) { argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index dc79d3c6ad7..b019ef5361f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -730,14 +730,23 @@ public Map getBeansWithAnnotation(Class an public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { - return findMergedAnnotationOnBean(beanName, annotationType) + return findAnnotationOnBean(beanName, annotationType, true); + } + + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return findMergedAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit) .synthesize(MergedAnnotation::isPresent).orElse(null); } private MergedAnnotation findMergedAnnotationOnBean( - String beanName, Class annotationType) { + String beanName, Class annotationType, boolean allowFactoryBeanInit) { - Class beanType = getType(beanName); + Class beanType = getType(beanName, allowFactoryBeanInit); if (beanType != null) { MergedAnnotation annotation = MergedAnnotations.from(beanType, SearchStrategy.TYPE_HIERARCHY).get(annotationType); @@ -1231,8 +1240,7 @@ private NamedBeanHolder resolveNamedBean( } if (candidateNames.length == 1) { - String beanName = candidateNames[0]; - return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args)); + return resolveNamedBean(candidateNames[0], requiredType, args); } else if (candidateNames.length > 1) { Map candidates = CollectionUtils.newLinkedHashMap(candidateNames.length); @@ -1251,8 +1259,11 @@ else if (candidateNames.length > 1) { } if (candidateName != null) { Object beanInstance = candidates.get(candidateName); - if (beanInstance == null || beanInstance instanceof Class) { - beanInstance = getBean(candidateName, requiredType.toClass(), args); + if (beanInstance == null) { + return null; + } + if (beanInstance instanceof Class) { + return resolveNamedBean(candidateName, requiredType, args); } return new NamedBeanHolder<>(candidateName, (T) beanInstance); } @@ -1264,6 +1275,17 @@ else if (candidateNames.length > 1) { return null; } + @Nullable + private NamedBeanHolder resolveNamedBean( + String beanName, ResolvableType requiredType, @Nullable Object[] args) throws BeansException { + + Object bean = getBean(beanName, null, args); + if (bean instanceof NullBean) { + return null; + } + return new NamedBeanHolder(beanName, adaptBeanInstance(beanName, bean, requiredType.toClass())); + } + @Override @Nullable public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index fb277b62b8a..b5fdef4dd81 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,6 +61,8 @@ @SuppressWarnings("serial") class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { + private static final String DESTROY_METHOD_NAME = "destroy"; + private static final String CLOSE_METHOD_NAME = "close"; private static final String SHUTDOWN_METHOD_NAME = "shutdown"; @@ -72,12 +74,11 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private final String beanName; - private final boolean invokeDisposableBean; - private final boolean nonPublicAccessAllowed; - @Nullable - private final AccessControlContext acc; + private final boolean invokeDisposableBean; + + private boolean invokeAutoCloseable; @Nullable private String destroyMethodName; @@ -88,6 +89,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { @Nullable private final List beanPostProcessors; + @Nullable + private final AccessControlContext acc; + /** * Create a new DisposableBeanAdapter for the given bean. @@ -103,36 +107,45 @@ public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition be Assert.notNull(bean, "Disposable bean must not be null"); this.bean = bean; this.beanName = beanName; - this.invokeDisposableBean = - (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy")); this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed(); - this.acc = acc; + this.invokeDisposableBean = (bean instanceof DisposableBean && + !beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME)); + String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); - if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) && + if (destroyMethodName != null && + !(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) && !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { - this.destroyMethodName = destroyMethodName; - Method destroyMethod = determineDestroyMethod(destroyMethodName); - if (destroyMethod == null) { - if (beanDefinition.isEnforceDestroyMethod()) { - throw new BeanDefinitionValidationException("Could not find a destroy method named '" + - destroyMethodName + "' on bean with name '" + beanName + "'"); - } - } - else { - Class[] paramTypes = destroyMethod.getParameterTypes(); - if (paramTypes.length > 1) { - throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + - beanName + "' has more than one parameter - not supported as destroy method"); + + this.invokeAutoCloseable = (bean instanceof AutoCloseable && CLOSE_METHOD_NAME.equals(destroyMethodName)); + if (!this.invokeAutoCloseable) { + this.destroyMethodName = destroyMethodName; + Method destroyMethod = determineDestroyMethod(destroyMethodName); + if (destroyMethod == null) { + if (beanDefinition.isEnforceDestroyMethod()) { + throw new BeanDefinitionValidationException("Could not find a destroy method named '" + + destroyMethodName + "' on bean with name '" + beanName + "'"); + } } - else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) { - throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + - beanName + "' has a non-boolean parameter - not supported as destroy method"); + else { + if (destroyMethod.getParameterCount() > 0) { + Class[] paramTypes = destroyMethod.getParameterTypes(); + if (paramTypes.length > 1) { + throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + + beanName + "' has more than one parameter - not supported as destroy method"); + } + else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) { + throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + + beanName + "' has a non-boolean parameter - not supported as destroy method"); + } + } + destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass()); } - destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod); + this.destroyMethod = destroyMethod; } - this.destroyMethod = destroyMethod; } + this.beanPostProcessors = filterPostProcessors(postProcessors, bean); + this.acc = acc; } /** @@ -147,86 +160,27 @@ public DisposableBeanAdapter( Assert.notNull(bean, "Disposable bean must not be null"); this.bean = bean; this.beanName = bean.getClass().getName(); - this.invokeDisposableBean = (this.bean instanceof DisposableBean); this.nonPublicAccessAllowed = true; - this.acc = acc; + this.invokeDisposableBean = (this.bean instanceof DisposableBean); this.beanPostProcessors = filterPostProcessors(postProcessors, bean); + this.acc = acc; } /** * Create a new DisposableBeanAdapter for the given bean. */ - private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean, - boolean nonPublicAccessAllowed, @Nullable String destroyMethodName, + private DisposableBeanAdapter(Object bean, String beanName, boolean nonPublicAccessAllowed, + boolean invokeDisposableBean, boolean invokeAutoCloseable, @Nullable String destroyMethodName, @Nullable List postProcessors) { this.bean = bean; this.beanName = beanName; - this.invokeDisposableBean = invokeDisposableBean; this.nonPublicAccessAllowed = nonPublicAccessAllowed; - this.acc = null; + this.invokeDisposableBean = invokeDisposableBean; + this.invokeAutoCloseable = invokeAutoCloseable; this.destroyMethodName = destroyMethodName; this.beanPostProcessors = postProcessors; - } - - - /** - * If the current value of the given beanDefinition's "destroyMethodName" property is - * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. - * Candidate methods are currently limited to public, no-arg methods named "close" or - * "shutdown" (whether declared locally or inherited). The given BeanDefinition's - * "destroyMethodName" is updated to be null if no such method is found, otherwise set - * to the name of the inferred method. This constant serves as the default for the - * {@code @Bean#destroyMethod} attribute and the value of the constant may also be - * used in XML within the {@code } or {@code - * } attributes. - *

    Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} - * interfaces, reflectively calling the "close" method on implementing beans as well. - */ - @Nullable - private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { - String destroyMethodName = beanDefinition.getDestroyMethodName(); - if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || - (destroyMethodName == null && bean instanceof AutoCloseable)) { - // Only perform destroy method inference or Closeable detection - // in case of the bean not explicitly implementing DisposableBean - if (!(bean instanceof DisposableBean)) { - try { - return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); - } - catch (NoSuchMethodException ex) { - try { - return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); - } - catch (NoSuchMethodException ex2) { - // no candidate destroy method found - } - } - } - return null; - } - return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); - } - - /** - * Search for all DestructionAwareBeanPostProcessors in the List. - * @param processors the List to search - * @return the filtered List of DestructionAwareBeanPostProcessors - */ - @Nullable - private List filterPostProcessors( - List processors, Object bean) { - - List filteredPostProcessors = null; - if (!CollectionUtils.isEmpty(processors)) { - filteredPostProcessors = new ArrayList<>(processors.size()); - for (DestructionAwareBeanPostProcessor processor : processors) { - if (processor.requiresDestruction(bean)) { - filteredPostProcessors.add(processor); - } - } - } - return filteredPostProcessors; + this.acc = null; } @@ -269,13 +223,38 @@ public void destroy() { } } - if (this.destroyMethod != null) { + if (this.invokeAutoCloseable) { + if (logger.isTraceEnabled()) { + logger.trace("Invoking close() on bean with name '" + this.beanName + "'"); + } + try { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + ((AutoCloseable) this.bean).close(); + return null; + }, this.acc); + } + else { + ((AutoCloseable) this.bean).close(); + } + } + catch (Throwable ex) { + String msg = "Invocation of close method failed on bean with name '" + this.beanName + "'"; + if (logger.isDebugEnabled()) { + logger.warn(msg, ex); + } + else { + logger.warn(msg + ": " + ex); + } + } + } + else if (this.destroyMethod != null) { invokeCustomDestroyMethod(this.destroyMethod); } else if (this.destroyMethodName != null) { - Method methodToInvoke = determineDestroyMethod(this.destroyMethodName); - if (methodToInvoke != null) { - invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke)); + Method destroyMethod = determineDestroyMethod(this.destroyMethodName); + if (destroyMethod != null) { + invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass())); } } } @@ -317,7 +296,7 @@ private void invokeCustomDestroyMethod(final Method destroyMethod) { args[0] = Boolean.TRUE; } if (logger.isTraceEnabled()) { - logger.trace("Invoking destroy method '" + this.destroyMethodName + + logger.trace("Invoking custom destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "'"); } try { @@ -340,7 +319,7 @@ private void invokeCustomDestroyMethod(final Method destroyMethod) { } } catch (InvocationTargetException ex) { - String msg = "Destroy method '" + this.destroyMethodName + "' on bean with name '" + + String msg = "Custom destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "' threw an exception"; if (logger.isDebugEnabled()) { logger.warn(msg, ex.getTargetException()); @@ -350,7 +329,7 @@ private void invokeCustomDestroyMethod(final Method destroyMethod) { } } catch (Throwable ex) { - logger.warn("Failed to invoke destroy method '" + this.destroyMethodName + + logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "'", ex); } } @@ -370,8 +349,9 @@ protected Object writeReplace() { } } } - return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean, - this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors); + return new DisposableBeanAdapter( + this.bean, this.beanName, this.nonPublicAccessAllowed, this.invokeDisposableBean, + this.invokeAutoCloseable, this.destroyMethodName, serializablePostProcessors); } @@ -381,15 +361,56 @@ protected Object writeReplace() { * @param beanDefinition the corresponding bean definition */ public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) { - if (bean instanceof DisposableBean || bean instanceof AutoCloseable) { - return true; - } - String destroyMethodName = beanDefinition.getDestroyMethodName(); - if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { - return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) || - ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME)); + return (bean instanceof DisposableBean || inferDestroyMethodIfNecessary(bean, beanDefinition) != null); + } + + + /** + * If the current value of the given beanDefinition's "destroyMethodName" property is + * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. + * Candidate methods are currently limited to public, no-arg methods named "close" or + * "shutdown" (whether declared locally or inherited). The given BeanDefinition's + * "destroyMethodName" is updated to be null if no such method is found, otherwise set + * to the name of the inferred method. This constant serves as the default for the + * {@code @Bean#destroyMethod} attribute and the value of the constant may also be + * used in XML within the {@code } or {@code + * } attributes. + *

    Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} + * interfaces, reflectively calling the "close" method on implementing beans as well. + */ + @Nullable + private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { + String destroyMethodName = beanDefinition.resolvedDestroyMethodName; + if (destroyMethodName == null) { + destroyMethodName = beanDefinition.getDestroyMethodName(); + boolean autoCloseable = (bean instanceof AutoCloseable); + if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || + (destroyMethodName == null && autoCloseable)) { + // Only perform destroy method inference in case of the bean + // not explicitly implementing the DisposableBean interface + destroyMethodName = null; + if (!(bean instanceof DisposableBean)) { + if (autoCloseable) { + destroyMethodName = CLOSE_METHOD_NAME; + } + else { + try { + destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex) { + try { + destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex2) { + // no candidate destroy method found + } + } + } + } + } + beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : ""); } - return StringUtils.hasLength(destroyMethodName); + return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); } /** @@ -408,4 +429,25 @@ public static boolean hasApplicableProcessors(Object bean, List filterPostProcessors( + List processors, Object bean) { + + List filteredPostProcessors = null; + if (!CollectionUtils.isEmpty(processors)) { + filteredPostProcessors = new ArrayList<>(processors.size()); + for (DestructionAwareBeanPostProcessor processor : processors) { + if (processor.requiresDestruction(bean)) { + filteredPostProcessors.add(processor); + } + } + } + return filteredPostProcessors; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java index b424c38bb08..c441bf7e9cc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -19,17 +19,25 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** - * Represents an override of a method that looks up an object in the same IoC context. + * Represents an override of a method that looks up an object in the same IoC context, + * either by bean name or by bean type (based on the declared method return type). * - *

    Methods eligible for lookup override must not have arguments. + *

    Methods eligible for lookup override may declare arguments in which case the + * given arguments are passed to the bean retrieval operation. * * @author Rod Johnson * @author Juergen Hoeller * @since 1.1 + * @see org.springframework.beans.factory.BeanFactory#getBean(String) + * @see org.springframework.beans.factory.BeanFactory#getBean(Class) + * @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...) + * @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...) + * @see org.springframework.beans.factory.BeanFactory#getBeanProvider(ResolvableType) */ public class LookupOverride extends MethodOverride { @@ -43,8 +51,8 @@ public class LookupOverride extends MethodOverride { /** * Construct a new LookupOverride. * @param methodName the name of the method to override - * @param beanName the name of the bean in the current {@code BeanFactory} - * that the overridden method should return (may be {@code null}) + * @param beanName the name of the bean in the current {@code BeanFactory} that the + * overridden method should return (may be {@code null} for type-based bean retrieval) */ public LookupOverride(String methodName, @Nullable String beanName) { super(methodName); @@ -53,9 +61,9 @@ public LookupOverride(String methodName, @Nullable String beanName) { /** * Construct a new LookupOverride. - * @param method the method to override - * @param beanName the name of the bean in the current {@code BeanFactory} - * that the overridden method should return (may be {@code null}) + * @param method the method declaration to override + * @param beanName the name of the bean in the current {@code BeanFactory} that the + * overridden method should return (may be {@code null} for type-based bean retrieval) */ public LookupOverride(Method method, @Nullable String beanName) { super(method.getName()); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java index 7a282ed7fce..28146701944 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedList.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.beans.BeanMetadataElement; @@ -53,6 +54,20 @@ public ManagedList(int initialCapacity) { } + /** + * Return a new instance containing an arbitrary number of elements. + * @param elements the elements to be contained in the list + * @param the {@code List}'s element type + * @return a {@code List} containing the specified elements + * @since 5.3.16 + */ + @SuppressWarnings("unchecked") + public static ManagedList of(E... elements) { + ManagedList list = new ManagedList<>(); + list.addAll(Arrays.asList(elements)); + return list; + } + /** * Set the configuration source {@code Object} for this metadata element. *

    The exact type of the object will depend on the configuration mechanism used. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java index 55b76f0317f..acd80074bf7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Map.Entry; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.Mergeable; @@ -56,6 +57,25 @@ public ManagedMap(int initialCapacity) { } + /** + * Return a new instance containing keys and values extracted from the + * given entries. The entries themselves are not stored in the map. + * @param entries {@code Map.Entry}s containing the keys and values + * from which the map is populated + * @param the {@code Map}'s key type + * @param the {@code Map}'s value type + * @return a {@code Map} containing the specified mappings + * @since 5.3.16 + */ + @SuppressWarnings("unchecked") + public static ManagedMap ofEntries(Entry... entries) { + ManagedMap map = new ManagedMap<>(); + for (Entry entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } + /** * Set the configuration source {@code Object} for this metadata element. *

    The exact type of the object will depend on the configuration mechanism used. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java index 7f84ec7f445..f5c72462379 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ManagedSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.beans.factory.support; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; @@ -52,6 +53,20 @@ public ManagedSet(int initialCapacity) { } + /** + * Return a new instance containing an arbitrary number of elements. + * @param elements the elements to be contained in the set + * @param the {@code Set}'s element type + * @return a {@code Set} containing the specified elements + * @since 5.3.16 + */ + @SuppressWarnings("unchecked") + public static ManagedSet of(E... elements) { + ManagedSet set = new ManagedSet<>(); + set.addAll(Arrays.asList(elements)); + return set; + } + /** * Set the configuration source {@code Object} for this metadata element. *

    The exact type of the object will depend on the configuration mechanism used. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java index 5677d944ff3..e4e5df879e5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.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. @@ -19,9 +19,8 @@ import java.lang.reflect.Method; /** - * Interface to be implemented by classes that can reimplement any method - * on an IoC-managed object: the Method Injection form of - * Dependency Injection. + * Interface to be implemented by classes that can reimplement any method on an + * IoC-managed object: the Method Injection form of Dependency Injection. * *

    Such methods may be (but need not be) abstract, in which case the * container will create a concrete subclass to instantiate. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java index 1b51bf70655..80f866d21cc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -25,7 +25,7 @@ import org.springframework.util.ObjectUtils; /** - * Extension of MethodOverride that represents an arbitrary + * Extension of {@link MethodOverride} that represents an arbitrary * override of a method by the IoC container. * *

    Any non-final method can be overridden, irrespective of its @@ -45,7 +45,7 @@ public class ReplaceOverride extends MethodOverride { /** * Construct a new ReplaceOverride. * @param methodName the name of the method to override - * @param methodReplacerBeanName the bean name of the MethodReplacer + * @param methodReplacerBeanName the bean name of the {@link MethodReplacer} */ public ReplaceOverride(String methodName, String methodReplacerBeanName) { super(methodName); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index d70c8040bde..f4fdafa3767 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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,7 +21,8 @@ import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; -import java.util.HashSet; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Supplier; @@ -86,6 +87,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition { @Nullable volatile Method factoryMethodToIntrospect; + /** Package-visible field for caching a resolved destroy method name (also for inferred). */ + @Nullable + volatile String resolvedDestroyMethodName; + /** Common lock for the four constructor fields below. */ final Object constructorArgumentLock = new Object(); @@ -418,15 +423,21 @@ public Method getResolvedFactoryMethod() { return this.factoryMethodToIntrospect; } + /** + * Register an externally managed configuration method or field. + */ public void registerExternallyManagedConfigMember(Member configMember) { synchronized (this.postProcessingLock) { if (this.externallyManagedConfigMembers == null) { - this.externallyManagedConfigMembers = new HashSet<>(1); + this.externallyManagedConfigMembers = new LinkedHashSet<>(1); } this.externallyManagedConfigMembers.add(configMember); } } + /** + * Check whether the given method or field is an externally managed configuration member. + */ public boolean isExternallyManagedConfigMember(Member configMember) { synchronized (this.postProcessingLock) { return (this.externallyManagedConfigMembers != null && @@ -434,15 +445,33 @@ public boolean isExternallyManagedConfigMember(Member configMember) { } } + /** + * Return all externally managed configuration methods and fields (as an immutable Set). + * @since 5.3.11 + */ + public Set getExternallyManagedConfigMembers() { + synchronized (this.postProcessingLock) { + return (this.externallyManagedConfigMembers != null ? + Collections.unmodifiableSet(new LinkedHashSet<>(this.externallyManagedConfigMembers)) : + Collections.emptySet()); + } + } + + /** + * Register an externally managed configuration initialization method. + */ public void registerExternallyManagedInitMethod(String initMethod) { synchronized (this.postProcessingLock) { if (this.externallyManagedInitMethods == null) { - this.externallyManagedInitMethods = new HashSet<>(1); + this.externallyManagedInitMethods = new LinkedHashSet<>(1); } this.externallyManagedInitMethods.add(initMethod); } } + /** + * Check whether the given method name indicates an externally managed initialization method. + */ public boolean isExternallyManagedInitMethod(String initMethod) { synchronized (this.postProcessingLock) { return (this.externallyManagedInitMethods != null && @@ -450,15 +479,33 @@ public boolean isExternallyManagedInitMethod(String initMethod) { } } + /** + * Return all externally managed initialization methods (as an immutable Set). + * @since 5.3.11 + */ + public Set getExternallyManagedInitMethods() { + synchronized (this.postProcessingLock) { + return (this.externallyManagedInitMethods != null ? + Collections.unmodifiableSet(new LinkedHashSet<>(this.externallyManagedInitMethods)) : + Collections.emptySet()); + } + } + + /** + * Register an externally managed configuration destruction method. + */ public void registerExternallyManagedDestroyMethod(String destroyMethod) { synchronized (this.postProcessingLock) { if (this.externallyManagedDestroyMethods == null) { - this.externallyManagedDestroyMethods = new HashSet<>(1); + this.externallyManagedDestroyMethods = new LinkedHashSet<>(1); } this.externallyManagedDestroyMethods.add(destroyMethod); } } + /** + * Check whether the given method name indicates an externally managed destruction method. + */ public boolean isExternallyManagedDestroyMethod(String destroyMethod) { synchronized (this.postProcessingLock) { return (this.externallyManagedDestroyMethods != null && @@ -466,6 +513,18 @@ public boolean isExternallyManagedDestroyMethod(String destroyMethod) { } } + /** + * Return all externally managed destruction methods (as an immutable Set). + * @since 5.3.11 + */ + public Set getExternallyManagedDestroyMethods() { + synchronized (this.postProcessingLock) { + return (this.externallyManagedDestroyMethods != null ? + Collections.unmodifiableSet(new LinkedHashSet<>(this.externallyManagedDestroyMethods)) : + Collections.emptySet()); + } + } + @Override public RootBeanDefinition cloneBeanDefinition() { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index a5430120dfd..1105ead3e12 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -459,7 +459,16 @@ public Map getBeansWithAnnotation(Class an public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { - Class beanType = getType(beanName); + return findAnnotationOnBean(beanName, annotationType, true); + } + + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + Class beanType = getType(beanName, allowFactoryBeanInit); return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java index a443e7b1d66..08b1d16f277 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java @@ -39,7 +39,7 @@ * when starting your JVM. For example, to use the Oracle {@link DocumentBuilder}, * you might start your application like as follows: * - *

    java -Djavax.xml.parsers.DocumentBuilderFactory=oracle.xml.jaxp.JXDocumentBuilderFactory MyMainClass
    + *
    java -Djavax.xml.parsers.DocumentBuilderFactory=oracle.xml.jaxp.JXDocumentBuilderFactory MyMainClass
    * * @author Rob Harrop * @author Juergen Hoeller diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java index 2223f789ae1..4bd6ef58e96 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.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. @@ -64,24 +64,24 @@ public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegat } - public final XmlReaderContext getReaderContext() { + public XmlReaderContext getReaderContext() { return this.readerContext; } - public final BeanDefinitionRegistry getRegistry() { + public BeanDefinitionRegistry getRegistry() { return this.readerContext.getRegistry(); } - public final BeanDefinitionParserDelegate getDelegate() { + public BeanDefinitionParserDelegate getDelegate() { return this.delegate; } @Nullable - public final BeanDefinition getContainingBeanDefinition() { + public BeanDefinition getContainingBeanDefinition() { return this.containingBeanDefinition; } - public final boolean isNested() { + public boolean isNested() { return (this.containingBeanDefinition != null); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java index 3b1bff1febc..82d632302d5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java @@ -71,13 +71,13 @@ public class PluggableSchemaResolver implements EntityResolver { private final String schemaMappingsLocation; - /** Stores the mapping of schema URL -> local schema path. */ + /** Stores the mapping of schema URL → local schema path. */ @Nullable private volatile Map schemaMappings; /** - * Loads the schema URL -> schema file location mappings using the default + * Loads the schema URL → schema file location mappings using the default * mapping file pattern "META-INF/spring.schemas". * @param classLoader the ClassLoader to use for loading * (can be {@code null}) to use the default ClassLoader) @@ -89,7 +89,7 @@ public PluggableSchemaResolver(@Nullable ClassLoader classLoader) { } /** - * Loads the schema URL -> schema file location mappings using the given + * Loads the schema URL → schema file location mappings using the given * mapping file pattern. * @param classLoader the ClassLoader to use for loading * (can be {@code null}) to use the default ClassLoader) diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.java index 29bf43af385..5a327f710e0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/LocaleEditor.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. @@ -23,9 +23,9 @@ /** * Editor for {@code java.util.Locale}, to directly populate a Locale property. * - *

    Expects the same syntax as Locale's {@code toString}, i.e. language + + *

    Expects the same syntax as Locale's {@code toString()}, i.e. language + * optionally country + optionally variant, separated by "_" (e.g. "en", "en_US"). - * Also accepts spaces as separators, as alternative to underscores. + * Also accepts spaces as separators, as an alternative to underscores. * * @author Juergen Hoeller * @since 26.05.2003 diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java index f1edae00c79..0ee0f5e80f6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.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. @@ -26,8 +26,8 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceEditor; -import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; /** * Editor for {@code java.nio.file.Path}, to directly populate a Path @@ -74,7 +74,7 @@ public PathEditor(ResourceEditor resourceEditor) { @Override public void setAsText(String text) throws IllegalArgumentException { - boolean nioPathCandidate = !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX); + boolean nioPathCandidate = !text.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX); if (nioPathCandidate && !text.startsWith("/")) { try { URI uri = new URI(text); @@ -85,9 +85,13 @@ public void setAsText(String text) throws IllegalArgumentException { return; } } - catch (URISyntaxException | FileSystemNotFoundException ex) { - // Not a valid URI (let's try as Spring resource location), - // or a URI scheme not registered for NIO (let's try URL + catch (URISyntaxException ex) { + // Not a valid URI; potentially a Windows-style path after + // a file prefix (let's try as Spring resource location) + nioPathCandidate = !text.startsWith(ResourceUtils.FILE_URL_PREFIX); + } + catch (FileSystemNotFoundException ex) { + // URI scheme not registered for NIO (let's try URL // protocol handlers via Spring's resource mechanism). } } @@ -97,7 +101,7 @@ public void setAsText(String text) throws IllegalArgumentException { if (resource == null) { setValue(null); } - else if (!resource.exists() && nioPathCandidate) { + else if (nioPathCandidate && !resource.exists()) { setValue(Paths.get(text).normalize()); } else { diff --git a/spring-beans/src/main/java/org/springframework/beans/support/PropertyComparator.java b/spring-beans/src/main/java/org/springframework/beans/support/PropertyComparator.java index 43e927f2830..519ead57987 100644 --- a/spring-beans/src/main/java/org/springframework/beans/support/PropertyComparator.java +++ b/spring-beans/src/main/java/org/springframework/beans/support/PropertyComparator.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. @@ -44,8 +44,6 @@ public class PropertyComparator implements Comparator { private final SortDefinition sortDefinition; - private final BeanWrapperImpl beanWrapper = new BeanWrapperImpl(false); - /** * Create a new PropertyComparator for the given SortDefinition. @@ -115,8 +113,9 @@ private Object getPropertyValue(Object obj) { // (similar to JSTL EL). If the property doesn't exist in the // first place, let the exception through. try { - this.beanWrapper.setWrappedInstance(obj); - return this.beanWrapper.getPropertyValue(this.sortDefinition.getProperty()); + BeanWrapperImpl beanWrapper = new BeanWrapperImpl(false); + beanWrapper.setWrappedInstance(obj); + return beanWrapper.getPropertyValue(this.sortDefinition.getProperty()); } catch (BeansException ex) { logger.debug("PropertyComparator could not access property - treating as null for sorting", ex); diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java index 0bb5febde05..2dd52740b6a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -68,25 +68,25 @@ * @author Dave Syer * @author Stephane Nicoll */ -public abstract class AbstractPropertyAccessorTests { +abstract class AbstractPropertyAccessorTests { protected abstract AbstractPropertyAccessor createAccessor(Object target); @Test - public void createWithNullTarget() { + void createWithNullTarget() { assertThatIllegalArgumentException().isThrownBy(() -> createAccessor(null)); } @Test - public void isReadableProperty() { + void isReadableProperty() { AbstractPropertyAccessor accessor = createAccessor(new Simple("John", 2)); assertThat(accessor.isReadableProperty("name")).isTrue(); } @Test - public void isReadablePropertyNotReadable() { + void isReadablePropertyNotReadable() { AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertThat(accessor.isReadableProperty("age")).isFalse(); @@ -96,42 +96,42 @@ public void isReadablePropertyNotReadable() { * Shouldn't throw an exception: should just return false */ @Test - public void isReadablePropertyNoSuchProperty() { + void isReadablePropertyNoSuchProperty() { AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertThat(accessor.isReadableProperty("xxxxx")).isFalse(); } @Test - public void isReadablePropertyNull() { + void isReadablePropertyNull() { AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertThatIllegalArgumentException().isThrownBy(() -> accessor.isReadableProperty(null)); } @Test - public void isWritableProperty() { + void isWritableProperty() { AbstractPropertyAccessor accessor = createAccessor(new Simple("John", 2)); assertThat(accessor.isWritableProperty("name")).isTrue(); } @Test - public void isWritablePropertyNull() { + void isWritablePropertyNull() { AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertThatIllegalArgumentException().isThrownBy(() -> accessor.isWritableProperty(null)); } @Test - public void isWritablePropertyNoSuchProperty() { + void isWritablePropertyNoSuchProperty() { AbstractPropertyAccessor accessor = createAccessor(new NoRead()); assertThat(accessor.isWritableProperty("xxxxx")).isFalse(); } @Test - public void isReadableWritableForIndexedProperties() { + void isReadableWritableForIndexedProperties() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); @@ -177,28 +177,28 @@ public void isReadableWritableForIndexedProperties() { } @Test - public void getSimpleProperty() { + void getSimpleProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyValue("name")).isEqualTo("John"); } @Test - public void getNestedProperty() { + void getNestedProperty() { Person target = createPerson("John", "London", "UK"); AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyValue("address.city")).isEqualTo("London"); } @Test - public void getNestedDeepProperty() { + void getNestedDeepProperty() { Person target = createPerson("John", "London", "UK"); AbstractPropertyAccessor accessor = createAccessor(target); assertThat(accessor.getPropertyValue("address.country.name")).isEqualTo("UK"); } @Test - public void getAnotherNestedDeepProperty() { + void getAnotherNestedDeepProperty() { ITestBean target = new TestBean("rod", 31); ITestBean kerry = new TestBean("kerry", 35); target.setSpouse(kerry); @@ -213,7 +213,7 @@ public void getAnotherNestedDeepProperty() { } @Test - public void getPropertyIntermediatePropertyIsNull() { + void getPropertyIntermediatePropertyIsNull() { Person target = createPerson("John", "London", "UK"); target.address = null; AbstractPropertyAccessor accessor = createAccessor(target); @@ -226,7 +226,7 @@ public void getPropertyIntermediatePropertyIsNull() { } @Test - public void getPropertyIntermediatePropertyIsNullWithAutoGrow() { + void getPropertyIntermediatePropertyIsNullWithAutoGrow() { Person target = createPerson("John", "London", "UK"); target.address = null; AbstractPropertyAccessor accessor = createAccessor(target); @@ -236,7 +236,7 @@ public void getPropertyIntermediatePropertyIsNullWithAutoGrow() { } @Test - public void getPropertyIntermediateMapEntryIsNullWithAutoGrow() { + void getPropertyIntermediateMapEntryIsNullWithAutoGrow() { Foo target = new Foo(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setConversionService(new DefaultConversionService()); @@ -246,7 +246,7 @@ public void getPropertyIntermediateMapEntryIsNullWithAutoGrow() { } @Test - public void getUnknownProperty() { + void getUnknownProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); assertThatExceptionOfType(NotReadablePropertyException.class).isThrownBy(() -> @@ -258,7 +258,7 @@ public void getUnknownProperty() { } @Test - public void getUnknownNestedProperty() { + void getUnknownNestedProperty() { Person target = createPerson("John", "London", "UK"); AbstractPropertyAccessor accessor = createAccessor(target); @@ -267,7 +267,7 @@ public void getUnknownNestedProperty() { } @Test - public void setSimpleProperty() { + void setSimpleProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); @@ -278,7 +278,7 @@ public void setSimpleProperty() { } @Test - public void setNestedProperty() { + void setNestedProperty() { Person target = createPerson("John", "Paris", "FR"); AbstractPropertyAccessor accessor = createAccessor(target); @@ -287,7 +287,7 @@ public void setNestedProperty() { } @Test - public void setNestedPropertyPolymorphic() throws Exception { + void setNestedPropertyPolymorphic() throws Exception { ITestBean target = new TestBean("rod", 31); ITestBean kerry = new Employee(); @@ -296,10 +296,10 @@ public void setNestedPropertyPolymorphic() throws Exception { accessor.setPropertyValue("spouse.age", 35); accessor.setPropertyValue("spouse.name", "Kerry"); accessor.setPropertyValue("spouse.company", "Lewisham"); - assertThat(kerry.getName().equals("Kerry")).as("kerry name is Kerry").isTrue(); + assertThat(kerry.getName()).as("kerry name is Kerry").isEqualTo("Kerry"); assertThat(target.getSpouse() == kerry).as("nested set worked").isTrue(); - assertThat(kerry.getSpouse() == null).as("no back relation").isTrue(); + assertThat(kerry.getSpouse()).as("no back relation").isNull(); accessor.setPropertyValue(new PropertyValue("spouse.spouse", target)); assertThat(kerry.getSpouse() == target).as("nested set worked").isTrue(); @@ -308,7 +308,7 @@ public void setNestedPropertyPolymorphic() throws Exception { } @Test - public void setAnotherNestedProperty() throws Exception { + void setAnotherNestedProperty() throws Exception { ITestBean target = new TestBean("rod", 31); ITestBean kerry = new TestBean("kerry", 0); @@ -316,7 +316,7 @@ public void setAnotherNestedProperty() throws Exception { accessor.setPropertyValue("spouse", kerry); assertThat(target.getSpouse() == kerry).as("nested set worked").isTrue(); - assertThat(kerry.getSpouse() == null).as("no back relation").isTrue(); + assertThat(kerry.getSpouse()).as("no back relation").isNull(); accessor.setPropertyValue(new PropertyValue("spouse.spouse", target)); assertThat(kerry.getSpouse() == target).as("nested set worked").isTrue(); assertThat(kerry.getAge() == 0).as("kerry age not set").isTrue(); @@ -328,7 +328,7 @@ public void setAnotherNestedProperty() throws Exception { } @Test - public void setYetAnotherNestedProperties() { + void setYetAnotherNestedProperties() { String doctorCompany = ""; String lawyerCompany = "Dr. Sueem"; TestBean target = new TestBean(); @@ -340,7 +340,7 @@ public void setYetAnotherNestedProperties() { } @Test - public void setNestedDeepProperty() { + void setNestedDeepProperty() { Person target = createPerson("John", "Paris", "FR"); AbstractPropertyAccessor accessor = createAccessor(target); @@ -349,7 +349,7 @@ public void setNestedDeepProperty() { } @Test - public void testErrorMessageOfNestedProperty() { + void testErrorMessageOfNestedProperty() { ITestBean target = new TestBean(); ITestBean child = new DifferentTestBean(); child.setName("test"); @@ -364,7 +364,7 @@ public void testErrorMessageOfNestedProperty() { } @Test - public void setPropertyIntermediatePropertyIsNull() { + void setPropertyIntermediatePropertyIsNull() { Person target = createPerson("John", "Paris", "FR"); target.address.country = null; AbstractPropertyAccessor accessor = createAccessor(target); @@ -378,7 +378,7 @@ public void setPropertyIntermediatePropertyIsNull() { } @Test - public void setAnotherPropertyIntermediatePropertyIsNull() throws Exception { + void setAnotherPropertyIntermediatePropertyIsNull() throws Exception { ITestBean target = new TestBean("rod", 31); AbstractPropertyAccessor accessor = createAccessor(target); assertThatExceptionOfType(NullValueInNestedPathException.class).isThrownBy(() -> @@ -387,7 +387,7 @@ public void setAnotherPropertyIntermediatePropertyIsNull() throws Exception { } @Test - public void setPropertyIntermediatePropertyIsNullWithAutoGrow() { + void setPropertyIntermediatePropertyIsNullWithAutoGrow() { Person target = createPerson("John", "Paris", "FR"); target.address.country = null; AbstractPropertyAccessor accessor = createAccessor(target); @@ -398,7 +398,7 @@ public void setPropertyIntermediatePropertyIsNullWithAutoGrow() { } @Test - public void setPropertyIntermediateListIsNullWithAutoGrow() { + void setPropertyIntermediateListIsNullWithAutoGrow() { Foo target = new Foo(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setConversionService(new DefaultConversionService()); @@ -410,7 +410,7 @@ public void setPropertyIntermediateListIsNullWithAutoGrow() { } @Test - public void setPropertyIntermediateListIsNullWithNoConversionService() { + void setPropertyIntermediateListIsNullWithNoConversionService() { Foo target = new Foo(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setAutoGrowNestedPaths(true); @@ -419,7 +419,7 @@ public void setPropertyIntermediateListIsNullWithNoConversionService() { } @Test - public void setPropertyIntermediateListIsNullWithBadConversionService() { + void setPropertyIntermediateListIsNullWithBadConversionService() { Foo target = new Foo(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setConversionService(new GenericConversionService() { @@ -435,7 +435,7 @@ public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceTy @Test - public void setEmptyPropertyValues() { + void setEmptyPropertyValues() { TestBean target = new TestBean(); int age = 50; String name = "Tony"; @@ -452,7 +452,7 @@ public void setEmptyPropertyValues() { @Test - public void setValidPropertyValues() { + void setValidPropertyValues() { TestBean target = new TestBean(); String newName = "tony"; int newAge = 65; @@ -469,7 +469,7 @@ public void setValidPropertyValues() { } @Test - public void setIndividualValidPropertyValues() { + void setIndividualValidPropertyValues() { TestBean target = new TestBean(); String newName = "tony"; int newAge = 65; @@ -478,39 +478,39 @@ public void setIndividualValidPropertyValues() { accessor.setPropertyValue("age", newAge); accessor.setPropertyValue(new PropertyValue("name", newName)); accessor.setPropertyValue(new PropertyValue("touchy", newTouchy)); - assertThat(target.getName().equals(newName)).as("Name property should have changed").isTrue(); - assertThat(target.getTouchy().equals(newTouchy)).as("Touchy property should have changed").isTrue(); + assertThat(target.getName()).as("Name property should have changed").isEqualTo(newName); + assertThat(target.getTouchy()).as("Touchy property should have changed").isEqualTo(newTouchy); assertThat(target.getAge() == newAge).as("Age property should have changed").isTrue(); } @Test - public void setPropertyIsReflectedImmediately() { + void setPropertyIsReflectedImmediately() { TestBean target = new TestBean(); int newAge = 33; AbstractPropertyAccessor accessor = createAccessor(target); target.setAge(newAge); Object bwAge = accessor.getPropertyValue("age"); - assertThat(bwAge instanceof Integer).as("Age is an integer").isTrue(); + assertThat(bwAge).as("Age is an integer").isInstanceOf(Integer.class); assertThat(bwAge).as("Bean wrapper must pick up changes").isEqualTo(newAge); } @Test - public void setPropertyToNull() { + void setPropertyToNull() { TestBean target = new TestBean(); target.setName("Frank"); // we need to change it back target.setSpouse(target); AbstractPropertyAccessor accessor = createAccessor(target); - assertThat(target.getName() != null).as("name is not null to start off").isTrue(); + assertThat(target.getName()).as("name is not null to start off").isNotNull(); accessor.setPropertyValue("name", null); - assertThat(target.getName() == null).as("name is now null").isTrue(); + assertThat(target.getName()).as("name is now null").isNull(); // now test with non-string - assertThat(target.getSpouse() != null).as("spouse is not null to start off").isTrue(); + assertThat(target.getSpouse()).as("spouse is not null to start off").isNotNull(); accessor.setPropertyValue("spouse", null); - assertThat(target.getSpouse() == null).as("spouse is now null").isTrue(); + assertThat(target.getSpouse()).as("spouse is now null").isNull(); } @Test - public void setIndexedPropertyIgnored() { + void setIndexedPropertyIgnored() { MutablePropertyValues values = new MutablePropertyValues(); values.add("toBeIgnored[0]", 42); AbstractPropertyAccessor accessor = createAccessor(new Object()); @@ -518,7 +518,7 @@ public void setIndexedPropertyIgnored() { } @Test - public void setPropertyWithPrimitiveConversion() { + void setPropertyWithPrimitiveConversion() { MutablePropertyValues values = new MutablePropertyValues(); values.add("name", 42); TestBean target = new TestBean(); @@ -528,7 +528,7 @@ public void setPropertyWithPrimitiveConversion() { } @Test - public void setPropertyWithCustomEditor() { + void setPropertyWithCustomEditor() { MutablePropertyValues values = new MutablePropertyValues(); values.add("name", Integer.class); TestBean target = new TestBean(); @@ -544,7 +544,7 @@ public void setValue(Object value) { } @Test - public void setStringPropertyWithCustomEditor() throws Exception { + void setStringPropertyWithCustomEditor() throws Exception { TestBean target = new TestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(String.class, "name", new PropertyEditorSupport() { @@ -567,7 +567,7 @@ public void setValue(Object value) { } @Test - public void setBooleanProperty() { + void setBooleanProperty() { BooleanTestBean target = new BooleanTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); @@ -581,7 +581,7 @@ public void setBooleanProperty() { } @Test - public void setNumberProperties() { + void setNumberProperties() { NumberTestBean target = new NumberTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("short2", "2"); @@ -591,24 +591,24 @@ public void setNumberProperties() { accessor.setPropertyValue("float2", "8.1"); accessor.setPropertyValue("double2", "6.1"); accessor.setPropertyValue("bigDecimal", "4.0"); - assertThat(new Short("2").equals(accessor.getPropertyValue("short2"))).as("Correct short2 value").isTrue(); - assertThat(new Short("2").equals(target.getShort2())).as("Correct short2 value").isTrue(); - assertThat(new Integer("8").equals(accessor.getPropertyValue("int2"))).as("Correct int2 value").isTrue(); - assertThat(new Integer("8").equals(target.getInt2())).as("Correct int2 value").isTrue(); - assertThat(new Long("6").equals(accessor.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); - assertThat(new Long("6").equals(target.getLong2())).as("Correct long2 value").isTrue(); - assertThat(new BigInteger("3").equals(accessor.getPropertyValue("bigInteger"))).as("Correct bigInteger value").isTrue(); - assertThat(new BigInteger("3").equals(target.getBigInteger())).as("Correct bigInteger value").isTrue(); - assertThat(new Float("8.1").equals(accessor.getPropertyValue("float2"))).as("Correct float2 value").isTrue(); - assertThat(new Float("8.1").equals(target.getFloat2())).as("Correct float2 value").isTrue(); - assertThat(new Double("6.1").equals(accessor.getPropertyValue("double2"))).as("Correct double2 value").isTrue(); - assertThat(new Double("6.1").equals(target.getDouble2())).as("Correct double2 value").isTrue(); - assertThat(new BigDecimal("4.0").equals(accessor.getPropertyValue("bigDecimal"))).as("Correct bigDecimal value").isTrue(); - assertThat(new BigDecimal("4.0").equals(target.getBigDecimal())).as("Correct bigDecimal value").isTrue(); - } - - @Test - public void setNumberPropertiesWithCoercion() { + assertThat(Short.valueOf("2")).as("Correct short2 value").isEqualTo(accessor.getPropertyValue("short2")); + assertThat(Short.valueOf("2")).as("Correct short2 value").isEqualTo(target.getShort2()); + assertThat(Integer.valueOf("8")).as("Correct int2 value").isEqualTo(accessor.getPropertyValue("int2")); + assertThat(Integer.valueOf("8")).as("Correct int2 value").isEqualTo(target.getInt2()); + assertThat(Long.valueOf("6")).as("Correct long2 value").isEqualTo(accessor.getPropertyValue("long2")); + assertThat(Long.valueOf("6")).as("Correct long2 value").isEqualTo(target.getLong2()); + assertThat(new BigInteger("3")).as("Correct bigInteger value").isEqualTo(accessor.getPropertyValue("bigInteger")); + assertThat(new BigInteger("3")).as("Correct bigInteger value").isEqualTo(target.getBigInteger()); + assertThat(Float.valueOf("8.1")).as("Correct float2 value").isEqualTo(accessor.getPropertyValue("float2")); + assertThat(Float.valueOf("8.1")).as("Correct float2 value").isEqualTo(target.getFloat2()); + assertThat(Double.valueOf("6.1").equals(accessor.getPropertyValue("double2"))).as("Correct double2 value").isTrue(); + assertThat(Double.valueOf("6.1")).as("Correct double2 value").isEqualTo(target.getDouble2()); + assertThat(new BigDecimal("4.0")).as("Correct bigDecimal value").isEqualTo(accessor.getPropertyValue("bigDecimal")); + assertThat(new BigDecimal("4.0")).as("Correct bigDecimal value").isEqualTo(target.getBigDecimal()); + } + + @Test + void setNumberPropertiesWithCoercion() { NumberTestBean target = new NumberTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("short2", 2); @@ -616,26 +616,26 @@ public void setNumberPropertiesWithCoercion() { accessor.setPropertyValue("long2", new BigInteger("6")); accessor.setPropertyValue("bigInteger", 3L); accessor.setPropertyValue("float2", 8.1D); - accessor.setPropertyValue("double2", new BigDecimal(6.1)); + accessor.setPropertyValue("double2", new BigDecimal("6.1")); accessor.setPropertyValue("bigDecimal", 4.0F); - assertThat(new Short("2").equals(accessor.getPropertyValue("short2"))).as("Correct short2 value").isTrue(); - assertThat(new Short("2").equals(target.getShort2())).as("Correct short2 value").isTrue(); - assertThat(new Integer("8").equals(accessor.getPropertyValue("int2"))).as("Correct int2 value").isTrue(); - assertThat(new Integer("8").equals(target.getInt2())).as("Correct int2 value").isTrue(); - assertThat(new Long("6").equals(accessor.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); - assertThat(new Long("6").equals(target.getLong2())).as("Correct long2 value").isTrue(); + assertThat(Short.valueOf("2")).as("Correct short2 value").isEqualTo(accessor.getPropertyValue("short2")); + assertThat(Short.valueOf("2")).as("Correct short2 value").isEqualTo(target.getShort2()); + assertThat(Integer.valueOf("8")).as("Correct int2 value").isEqualTo(accessor.getPropertyValue("int2")); + assertThat(Integer.valueOf("8")).as("Correct int2 value").isEqualTo(target.getInt2()); + assertThat(Long.valueOf("6")).as("Correct long2 value").isEqualTo(accessor.getPropertyValue("long2")); + assertThat(Long.valueOf("6")).as("Correct long2 value").isEqualTo(target.getLong2()); assertThat(new BigInteger("3").equals(accessor.getPropertyValue("bigInteger"))).as("Correct bigInteger value").isTrue(); - assertThat(new BigInteger("3").equals(target.getBigInteger())).as("Correct bigInteger value").isTrue(); - assertThat(new Float("8.1").equals(accessor.getPropertyValue("float2"))).as("Correct float2 value").isTrue(); - assertThat(new Float("8.1").equals(target.getFloat2())).as("Correct float2 value").isTrue(); - assertThat(new Double("6.1").equals(accessor.getPropertyValue("double2"))).as("Correct double2 value").isTrue(); - assertThat(new Double("6.1").equals(target.getDouble2())).as("Correct double2 value").isTrue(); - assertThat(new BigDecimal("4.0").equals(accessor.getPropertyValue("bigDecimal"))).as("Correct bigDecimal value").isTrue(); - assertThat(new BigDecimal("4.0").equals(target.getBigDecimal())).as("Correct bigDecimal value").isTrue(); + assertThat(new BigInteger("3")).as("Correct bigInteger value").isEqualTo(target.getBigInteger()); + assertThat(Float.valueOf("8.1")).as("Correct float2 value").isEqualTo(accessor.getPropertyValue("float2")); + assertThat(Float.valueOf("8.1")).as("Correct float2 value").isEqualTo(target.getFloat2()); + assertThat(Double.valueOf("6.1")).as("Correct double2 value").isEqualTo(accessor.getPropertyValue("double2")); + assertThat(Double.valueOf("6.1")).as("Correct double2 value").isEqualTo(target.getDouble2()); + assertThat(new BigDecimal("4.0")).as("Correct bigDecimal value").isEqualTo(accessor.getPropertyValue("bigDecimal")); + assertThat(new BigDecimal("4.0")).as("Correct bigDecimal value").isEqualTo(target.getBigDecimal()); } @Test - public void setPrimitiveProperties() { + void setPrimitiveProperties() { NumberPropertyBean target = new NumberPropertyBean(); AbstractPropertyAccessor accessor = createAccessor(target); @@ -684,7 +684,7 @@ public void setPrimitiveProperties() { } @Test - public void setEnumProperty() { + void setEnumProperty() { EnumTester target = new EnumTester(); AbstractPropertyAccessor accessor = createAccessor(target); @@ -699,7 +699,7 @@ public void setEnumProperty() { } @Test - public void setGenericEnumProperty() { + void setGenericEnumProperty() { EnumConsumer target = new EnumConsumer(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE"); @@ -707,7 +707,7 @@ public void setGenericEnumProperty() { } @Test - public void setWildcardEnumProperty() { + void setWildcardEnumProperty() { WildcardEnumConsumer target = new WildcardEnumConsumer(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("enumValue", TestEnum.class.getName() + ".TEST_VALUE"); @@ -715,7 +715,7 @@ public void setWildcardEnumProperty() { } @Test - public void setPropertiesProperty() throws Exception { + void setPropertiesProperty() throws Exception { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("name", "ptest"); @@ -724,53 +724,41 @@ public void setPropertiesProperty() throws Exception { String ps = "peace=war\nfreedom=slavery"; accessor.setPropertyValue("properties", ps); - assertThat(target.name.equals("ptest")).as("name was set").isTrue(); - assertThat(target.properties != null).as("properties non null").isTrue(); + assertThat(target.name).as("name was set").isEqualTo("ptest"); + assertThat(target.properties).as("properties non null").isNotNull(); String freedomVal = target.properties.getProperty("freedom"); String peaceVal = target.properties.getProperty("peace"); - assertThat(peaceVal.equals("war")).as("peace==war").isTrue(); + assertThat(peaceVal).as("peace==war").isEqualTo("war"); assertThat(freedomVal.equals("slavery")).as("Freedom==slavery").isTrue(); } @Test - public void setStringArrayProperty() throws Exception { + void setStringArrayProperty() throws Exception { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); - accessor.setPropertyValue("stringArray", new String[] {"foo", "fi", "fi", "fum"}); - assertThat(target.stringArray.length == 4).as("stringArray length = 4").isTrue(); - assertThat(target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && - target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")).as("correct values").isTrue(); + accessor.setPropertyValue("stringArray", new String[]{"foo", "fi", "fi", "fum"}); + assertThat(target.stringArray).containsExactly("foo", "fi", "fi", "fum"); - List list = new ArrayList<>(); - list.add("foo"); - list.add("fi"); - list.add("fi"); - list.add("fum"); - accessor.setPropertyValue("stringArray", list); - assertThat(target.stringArray.length == 4).as("stringArray length = 4").isTrue(); - assertThat(target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && - target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")).as("correct values").isTrue(); + accessor.setPropertyValue("stringArray", Arrays.asList("foo", "fi", "fi", "fum")); + assertThat(target.stringArray).containsExactly("foo", "fi", "fi", "fum"); Set set = new HashSet<>(); set.add("foo"); set.add("fi"); set.add("fum"); accessor.setPropertyValue("stringArray", set); - assertThat(target.stringArray.length == 3).as("stringArray length = 3").isTrue(); - List result = Arrays.asList(target.stringArray); - assertThat(result.contains("foo") && result.contains("fi") && result.contains("fum")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactlyInAnyOrder("foo", "fi", "fum"); accessor.setPropertyValue("stringArray", "one"); - assertThat(target.stringArray.length == 1).as("stringArray length = 1").isTrue(); - assertThat(target.stringArray[0].equals("one")).as("stringArray elt is ok").isTrue(); + assertThat(target.stringArray).containsExactly("one"); accessor.setPropertyValue("stringArray", null); - assertThat(target.stringArray == null).as("stringArray is null").isTrue(); + assertThat(target.stringArray).as("stringArray is null").isNull(); } @Test - public void setStringArrayPropertyWithCustomStringEditor() throws Exception { + void setStringArrayPropertyWithCustomStringEditor() throws Exception { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() { @@ -781,126 +769,90 @@ public void setAsText(String text) { }); accessor.setPropertyValue("stringArray", new String[] {"4foo", "7fi", "6fi", "5fum"}); - assertThat(target.stringArray.length == 4).as("stringArray length = 4").isTrue(); - assertThat(target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && - target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactly("foo", "fi", "fi", "fum"); - List list = new ArrayList<>(); - list.add("4foo"); - list.add("7fi"); - list.add("6fi"); - list.add("5fum"); + List list = Arrays.asList("4foo", "7fi", "6fi", "5fum"); accessor.setPropertyValue("stringArray", list); - assertThat(target.stringArray.length == 4).as("stringArray length = 4").isTrue(); - assertThat(target.stringArray[0].equals("foo") && target.stringArray[1].equals("fi") && - target.stringArray[2].equals("fi") && target.stringArray[3].equals("fum")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactly("foo", "fi", "fi", "fum"); Set set = new HashSet<>(); set.add("4foo"); set.add("7fi"); set.add("6fum"); accessor.setPropertyValue("stringArray", set); - assertThat(target.stringArray.length == 3).as("stringArray length = 3").isTrue(); - List result = Arrays.asList(target.stringArray); - assertThat(result.contains("foo") && result.contains("fi") && result.contains("fum")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactlyInAnyOrder("foo", "fi", "fum"); accessor.setPropertyValue("stringArray", "8one"); - assertThat(target.stringArray.length == 1).as("stringArray length = 1").isTrue(); - assertThat(target.stringArray[0].equals("one")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactly("one"); } @Test - public void setStringArrayPropertyWithStringSplitting() throws Exception { + void setStringArrayPropertyWithStringSplitting() throws Exception { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.useConfigValueEditors(); accessor.setPropertyValue("stringArray", "a1,b2"); - assertThat(target.stringArray.length == 2).as("stringArray length = 2").isTrue(); - assertThat(target.stringArray[0].equals("a1") && target.stringArray[1].equals("b2")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactly("a1", "b2"); } @Test - public void setStringArrayPropertyWithCustomStringDelimiter() throws Exception { + void setStringArrayPropertyWithCustomStringDelimiter() throws Exception { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(String[].class, "stringArray", new StringArrayPropertyEditor("-")); accessor.setPropertyValue("stringArray", "a1-b2"); - assertThat(target.stringArray.length == 2).as("stringArray length = 2").isTrue(); - assertThat(target.stringArray[0].equals("a1") && target.stringArray[1].equals("b2")).as("correct values").isTrue(); + assertThat(target.stringArray).containsExactly("a1", "b2"); } @Test - public void setStringArrayWithAutoGrow() throws Exception { + void setStringArrayWithAutoGrow() throws Exception { StringArrayBean target = new StringArrayBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setAutoGrowNestedPaths(true); accessor.setPropertyValue("array[0]", "Test0"); - assertThat(target.getArray().length).isEqualTo(1); + assertThat(target.getArray()).containsExactly("Test0"); accessor.setPropertyValue("array[2]", "Test2"); - assertThat(target.getArray().length).isEqualTo(3); - assertThat(target.getArray()[0].equals("Test0") && target.getArray()[1] == null && - target.getArray()[2].equals("Test2")).as("correct values").isTrue(); + assertThat(target.getArray()).containsExactly("Test0", null, "Test2"); } @Test - public void setIntArrayProperty() { + void setIntArrayProperty() { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("intArray", new int[] {4, 5, 2, 3}); - assertThat(target.intArray.length == 4).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 4 && target.intArray[1] == 5 && - target.intArray[2] == 2 && target.intArray[3] == 3).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(4, 5, 2, 3); accessor.setPropertyValue("intArray", new String[] {"4", "5", "2", "3"}); - assertThat(target.intArray.length == 4).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 4 && target.intArray[1] == 5 && - target.intArray[2] == 2 && target.intArray[3] == 3).as("correct values").isTrue(); - - List list = new ArrayList<>(); - list.add(4); - list.add("5"); - list.add(2); - list.add("3"); - accessor.setPropertyValue("intArray", list); - assertThat(target.intArray.length == 4).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 4 && target.intArray[1] == 5 && - target.intArray[2] == 2 && target.intArray[3] == 3).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(4, 5, 2, 3); + + accessor.setPropertyValue("intArray", Arrays.asList(4, "5", 2, "3")); + assertThat(target.intArray).containsExactly(4, 5, 2, 3); Set set = new HashSet<>(); set.add("4"); set.add(5); set.add("3"); accessor.setPropertyValue("intArray", set); - assertThat(target.intArray.length == 3).as("intArray length = 3").isTrue(); - List result = new ArrayList<>(); - result.add(target.intArray[0]); - result.add(target.intArray[1]); - result.add(target.intArray[2]); - assertThat(result.contains(4) && result.contains(5) && - result.contains(3)).as("correct values").isTrue(); + assertThat(target.intArray).containsExactlyInAnyOrder(4, 5, 3); accessor.setPropertyValue("intArray", new Integer[] {1}); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(1); accessor.setPropertyValue("intArray", 1); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(1); accessor.setPropertyValue("intArray", new String[] {"1"}); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(1); accessor.setPropertyValue("intArray", "1"); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(1); } @Test - public void setIntArrayPropertyWithCustomEditor() { + void setIntArrayPropertyWithCustomEditor() { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(int.class, new PropertyEditorSupport() { @@ -911,50 +863,40 @@ public void setAsText(String text) { }); accessor.setPropertyValue("intArray", new int[] {4, 5, 2, 3}); - assertThat(target.intArray.length == 4).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 4 && target.intArray[1] == 5 && - target.intArray[2] == 2 && target.intArray[3] == 3).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(4, 5, 2, 3); accessor.setPropertyValue("intArray", new String[] {"3", "4", "1", "2"}); - assertThat(target.intArray.length == 4).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 4 && target.intArray[1] == 5 && - target.intArray[2] == 2 && target.intArray[3] == 3).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(4, 5, 2, 3); accessor.setPropertyValue("intArray", 1); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(1); - accessor.setPropertyValue("intArray", new String[] {"0"}); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + accessor.setPropertyValue("intArray", new String[]{"0"}); + assertThat(target.intArray).containsExactly(1); accessor.setPropertyValue("intArray", "0"); - assertThat(target.intArray.length == 1).as("intArray length = 4").isTrue(); - assertThat(target.intArray[0] == 1).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(1); } @Test - public void setIntArrayPropertyWithStringSplitting() throws Exception { + void setIntArrayPropertyWithStringSplitting() throws Exception { PropsTester target = new PropsTester(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.useConfigValueEditors(); accessor.setPropertyValue("intArray", "4,5"); - assertThat(target.intArray.length == 2).as("intArray length = 2").isTrue(); - assertThat(target.intArray[0] == 4 && target.intArray[1] == 5).as("correct values").isTrue(); + assertThat(target.intArray).containsExactly(4, 5); } @Test - public void setPrimitiveArrayProperty() { + void setPrimitiveArrayProperty() { PrimitiveArrayBean target = new PrimitiveArrayBean(); AbstractPropertyAccessor accessor = createAccessor(target); - accessor.setPropertyValue("array", new String[] {"1", "2"}); - assertThat(target.getArray().length).isEqualTo(2); - assertThat(target.getArray()[0]).isEqualTo(1); - assertThat(target.getArray()[1]).isEqualTo(2); + accessor.setPropertyValue("array", new String[]{"1", "2"}); + assertThat(target.getArray()).containsExactly(1, 2); } @Test - public void setPrimitiveArrayPropertyLargeMatchingWithSpecificEditor() { + void setPrimitiveArrayPropertyLargeMatchingWithSpecificEditor() { PrimitiveArrayBean target = new PrimitiveArrayBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(int.class, "array", new PropertyEditorSupport() { @@ -965,15 +907,14 @@ public void setValue(Object value) { } } }); - int[] input = new int[1024]; + int[] input = new int[10]; accessor.setPropertyValue("array", input); - assertThat(target.getArray().length).isEqualTo(1024); - assertThat(target.getArray()[0]).isEqualTo(1); - assertThat(target.getArray()[1]).isEqualTo(1); + assertThat(target.getArray()).hasSize(10); + assertThat(Arrays.stream(target.getArray())).allMatch(n -> n == 1); } @Test - public void setPrimitiveArrayPropertyLargeMatchingWithIndexSpecificEditor() { + void setPrimitiveArrayPropertyLargeMatchingWithIndexSpecificEditor() { PrimitiveArrayBean target = new PrimitiveArrayBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(int.class, "array[1]", new PropertyEditorSupport() { @@ -984,49 +925,38 @@ public void setValue(Object value) { } } }); - int[] input = new int[1024]; + int[] input = new int[10]; accessor.setPropertyValue("array", input); - assertThat(target.getArray().length).isEqualTo(1024); - assertThat(target.getArray()[0]).isEqualTo(0); + assertThat(target.getArray()).hasSize(10); + assertThat(target.getArray()[0]).isZero(); assertThat(target.getArray()[1]).isEqualTo(1); + assertThat(Arrays.stream(target.getArray()).skip(2)).allMatch(n -> n == 0); } @Test - public void setPrimitiveArrayPropertyWithAutoGrow() throws Exception { + void setPrimitiveArrayPropertyWithAutoGrow() throws Exception { PrimitiveArrayBean target = new PrimitiveArrayBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setAutoGrowNestedPaths(true); accessor.setPropertyValue("array[0]", 1); - assertThat(target.getArray().length).isEqualTo(1); + assertThat(target.getArray()).containsExactly(1); accessor.setPropertyValue("array[2]", 3); - assertThat(target.getArray().length).isEqualTo(3); - assertThat(target.getArray()[0] == 1 && target.getArray()[1] == 0 && - target.getArray()[2] == 3).as("correct values").isTrue(); + assertThat(target.getArray()).containsExactly(1, 0, 3); } @Test - @SuppressWarnings("rawtypes") - public void setGenericArrayProperty() { + void setGenericArrayProperty() { + @SuppressWarnings("rawtypes") SkipReaderStub target = new SkipReaderStub(); AbstractPropertyAccessor accessor = createAccessor(target); - List values = new ArrayList<>(); - values.add("1"); - values.add("2"); - values.add("3"); - values.add("4"); - accessor.setPropertyValue("items", values); - Object[] result = target.items; - assertThat(result.length).isEqualTo(4); - assertThat(result[0]).isEqualTo("1"); - assertThat(result[1]).isEqualTo("2"); - assertThat(result[2]).isEqualTo("3"); - assertThat(result[3]).isEqualTo("4"); + accessor.setPropertyValue("items", Arrays.asList("1", "2", "3", "4")); + assertThat(target.items).containsExactly("1", "2", "3", "4"); } @Test - public void setArrayPropertyToObject() { + void setArrayPropertyToObject() { ArrayToObject target = new ArrayToObject(); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1040,7 +970,7 @@ public void setArrayPropertyToObject() { } @Test - public void setCollectionProperty() { + void setCollectionProperty() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Collection coll = new HashSet<>(); @@ -1061,9 +991,9 @@ public void setCollectionProperty() { assertThat((List) target.getList()).isSameAs(list); } - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests @Test - public void setCollectionPropertyNonMatchingType() { + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + void setCollectionPropertyNonMatchingType() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Collection coll = new ArrayList<>(); @@ -1078,7 +1008,7 @@ public void setCollectionPropertyNonMatchingType() { Set list = new HashSet<>(); list.add("list1"); accessor.setPropertyValue("list", list); - assertThat(target.getCollection().size()).isEqualTo(1); + assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); assertThat(target.getSet().size()).isEqualTo(1); assertThat(target.getSet().containsAll(set)).isTrue(); @@ -1088,9 +1018,9 @@ public void setCollectionPropertyNonMatchingType() { assertThat(target.getList().containsAll(list)).isTrue(); } - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests @Test - public void setCollectionPropertyWithArrayValue() { + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + void setCollectionPropertyWithArrayValue() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Collection coll = new HashSet<>(); @@ -1105,7 +1035,7 @@ public void setCollectionPropertyWithArrayValue() { Set list = new HashSet<>(); list.add("list1"); accessor.setPropertyValue("list", list.toArray()); - assertThat(target.getCollection().size()).isEqualTo(1); + assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); assertThat(target.getSet().size()).isEqualTo(1); assertThat(target.getSet().containsAll(set)).isTrue(); @@ -1115,24 +1045,24 @@ public void setCollectionPropertyWithArrayValue() { assertThat(target.getList().containsAll(list)).isTrue(); } - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests @Test - public void setCollectionPropertyWithIntArrayValue() { + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + void setCollectionPropertyWithIntArrayValue() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Collection coll = new HashSet<>(); coll.add(0); - accessor.setPropertyValue("collection", new int[] {0}); + accessor.setPropertyValue("collection", new int[]{0}); List set = new ArrayList<>(); set.add(1); - accessor.setPropertyValue("set", new int[] {1}); + accessor.setPropertyValue("set", new int[]{1}); List sortedSet = new ArrayList<>(); sortedSet.add(2); - accessor.setPropertyValue("sortedSet", new int[] {2}); + accessor.setPropertyValue("sortedSet", new int[]{2}); Set list = new HashSet<>(); list.add(3); - accessor.setPropertyValue("list", new int[] {3}); - assertThat(target.getCollection().size()).isEqualTo(1); + accessor.setPropertyValue("list", new int[]{3}); + assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); assertThat(target.getSet().size()).isEqualTo(1); assertThat(target.getSet().containsAll(set)).isTrue(); @@ -1142,9 +1072,9 @@ public void setCollectionPropertyWithIntArrayValue() { assertThat(target.getList().containsAll(list)).isTrue(); } - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests @Test - public void setCollectionPropertyWithIntegerValue() { + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + void setCollectionPropertyWithIntegerValue() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Collection coll = new HashSet<>(); @@ -1159,7 +1089,7 @@ public void setCollectionPropertyWithIntegerValue() { Set list = new HashSet<>(); list.add(3); accessor.setPropertyValue("list", 3); - assertThat(target.getCollection().size()).isEqualTo(1); + assertThat(target.getCollection()).hasSize(1); assertThat(target.getCollection().containsAll(coll)).isTrue(); assertThat(target.getSet().size()).isEqualTo(1); assertThat(target.getSet().containsAll(set)).isTrue(); @@ -1169,9 +1099,9 @@ public void setCollectionPropertyWithIntegerValue() { assertThat(target.getList().containsAll(list)).isTrue(); } - @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests @Test - public void setCollectionPropertyWithStringValue() { + @SuppressWarnings("unchecked") // list cannot be properly parameterized as it breaks other tests + void setCollectionPropertyWithStringValue() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); List set = new ArrayList<>(); @@ -1192,7 +1122,7 @@ public void setCollectionPropertyWithStringValue() { } @Test - public void setCollectionPropertyWithStringValueAndCustomEditor() { + void setCollectionPropertyWithStringValueAndCustomEditor() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(String.class, "set", new StringTrimmerEditor(false)); @@ -1213,7 +1143,7 @@ public void setCollectionPropertyWithStringValueAndCustomEditor() { } @Test - public void setMapProperty() { + void setMapProperty() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Map map = new HashMap<>(); @@ -1227,7 +1157,8 @@ public void setMapProperty() { } @Test - public void setMapPropertyNonMatchingType() { + @SuppressWarnings("unchecked") + void setMapPropertyNonMatchingType() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Map map = new TreeMap<>(); @@ -1236,14 +1167,14 @@ public void setMapPropertyNonMatchingType() { Map sortedMap = new TreeMap<>(); sortedMap.put("sortedKey", "sortedValue"); accessor.setPropertyValue("sortedMap", sortedMap); - assertThat(target.getMap().size()).isEqualTo(1); + assertThat(target.getMap()).hasSize(1); assertThat(target.getMap().get("key")).isEqualTo("value"); assertThat(target.getSortedMap().size()).isEqualTo(1); assertThat(target.getSortedMap().get("sortedKey")).isEqualTo("sortedValue"); } @Test - public void setMapPropertyWithTypeConversion() { + void setMapPropertyWithTypeConversion() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(TestBean.class, new PropertyEditorSupport() { @@ -1272,7 +1203,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void setMapPropertyWithUnmodifiableMap() { + void setMapPropertyWithUnmodifiableMap() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() { @@ -1296,7 +1227,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void setMapPropertyWithCustomUnmodifiableMap() { + void setMapPropertyWithCustomUnmodifiableMap() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() { @@ -1319,9 +1250,9 @@ public void setAsText(String text) throws IllegalArgumentException { assertThat(((TestBean) target.getMap().get(2)).getName()).isEqualTo("rob"); } - @SuppressWarnings({ "unchecked", "rawtypes" }) // must work with raw map in this test @Test - public void setRawMapPropertyWithNoEditorRegistered() { + @SuppressWarnings({ "unchecked", "rawtypes" }) // must work with raw map in this test + void setRawMapPropertyWithNoEditorRegistered() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); Map inputMap = new HashMap(); @@ -1336,7 +1267,7 @@ public void setRawMapPropertyWithNoEditorRegistered() { } @Test - public void setUnknownProperty() { + void setUnknownProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); assertThatExceptionOfType(NotWritablePropertyException.class).isThrownBy(() -> @@ -1349,7 +1280,7 @@ public void setUnknownProperty() { } @Test - public void setUnknownPropertyWithPossibleMatches() { + void setUnknownPropertyWithPossibleMatches() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); assertThatExceptionOfType(NotWritablePropertyException.class).isThrownBy(() -> @@ -1361,7 +1292,7 @@ public void setUnknownPropertyWithPossibleMatches() { } @Test - public void setUnknownOptionalProperty() { + void setUnknownOptionalProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); PropertyValue value = new PropertyValue("foo", "value"); @@ -1370,7 +1301,7 @@ public void setUnknownOptionalProperty() { } @Test - public void setPropertyInProtectedBaseBean() { + void setPropertyInProtectedBaseBean() { DerivedFromProtectedBaseBean target = new DerivedFromProtectedBaseBean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("someProperty", "someValue"); @@ -1379,7 +1310,7 @@ public void setPropertyInProtectedBaseBean() { } @Test - public void setPropertyTypeMismatch() { + void setPropertyTypeMismatch() { TestBean target = new TestBean(); AbstractPropertyAccessor accessor = createAccessor(target); assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() -> @@ -1387,7 +1318,7 @@ public void setPropertyTypeMismatch() { } @Test - public void setEmptyValueForPrimitiveProperty() { + void setEmptyValueForPrimitiveProperty() { TestBean target = new TestBean(); AbstractPropertyAccessor accessor = createAccessor(target); assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() -> @@ -1395,7 +1326,7 @@ public void setEmptyValueForPrimitiveProperty() { } @Test - public void setUnknownNestedProperty() { + void setUnknownNestedProperty() { Person target = createPerson("John", "Paris", "FR"); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1404,7 +1335,7 @@ public void setUnknownNestedProperty() { } @Test - public void setPropertyValuesIgnoresInvalidNestedOnRequest() { + void setPropertyValuesIgnoresInvalidNestedOnRequest() { ITestBean target = new TestBean(); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.addPropertyValue(new PropertyValue("name", "rod")); @@ -1418,7 +1349,7 @@ public void setPropertyValuesIgnoresInvalidNestedOnRequest() { } @Test - public void getAndSetIndexedProperties() { + void getAndSetIndexedProperties() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); TestBean tb0 = target.getArray()[0]; @@ -1488,7 +1419,7 @@ public void getAndSetIndexedProperties() { } @Test - public void getAndSetIndexedPropertiesWithDirectAccess() { + void getAndSetIndexedPropertiesWithDirectAccess() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); TestBean tb0 = target.getArray()[0]; @@ -1527,7 +1458,7 @@ public void getAndSetIndexedPropertiesWithDirectAccess() { assertThat((target.getList().get(0))).isEqualTo(tb3); assertThat((target.getList().get(1))).isEqualTo(tb2); assertThat((target.getList().get(2))).isEqualTo(tb0); - assertThat((target.getList().get(3))).isEqualTo(null); + assertThat((target.getList().get(3))).isNull(); assertThat((target.getList().get(4))).isEqualTo(tb1); assertThat((target.getMap().get("key1"))).isEqualTo(tb1); assertThat((target.getMap().get("key2"))).isEqualTo(tb0); @@ -1538,7 +1469,7 @@ public void getAndSetIndexedPropertiesWithDirectAccess() { assertThat(accessor.getPropertyValue("list[0]")).isEqualTo(tb3); assertThat(accessor.getPropertyValue("list[1]")).isEqualTo(tb2); assertThat(accessor.getPropertyValue("list[2]")).isEqualTo(tb0); - assertThat(accessor.getPropertyValue("list[3]")).isEqualTo(null); + assertThat(accessor.getPropertyValue("list[3]")).isNull(); assertThat(accessor.getPropertyValue("list[4]")).isEqualTo(tb1); assertThat(accessor.getPropertyValue("map[\"key1\"]")).isEqualTo(tb1); assertThat(accessor.getPropertyValue("map['key2']")).isEqualTo(tb0); @@ -1547,7 +1478,7 @@ public void getAndSetIndexedPropertiesWithDirectAccess() { } @Test - public void propertyType() { + void propertyType() { Person target = createPerson("John", "Paris", "FR"); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1555,7 +1486,7 @@ public void propertyType() { } @Test - public void propertyTypeUnknownProperty() { + void propertyTypeUnknownProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1563,7 +1494,7 @@ public void propertyTypeUnknownProperty() { } @Test - public void propertyTypeDescriptor() { + void propertyTypeDescriptor() { Person target = createPerson("John", "Paris", "FR"); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1571,7 +1502,7 @@ public void propertyTypeDescriptor() { } @Test - public void propertyTypeDescriptorUnknownProperty() { + void propertyTypeDescriptorUnknownProperty() { Simple target = new Simple("John", 2); AbstractPropertyAccessor accessor = createAccessor(target); @@ -1579,10 +1510,10 @@ public void propertyTypeDescriptorUnknownProperty() { } @Test - public void propertyTypeIndexedProperty() { + void propertyTypeIndexedProperty() { IndexedTestBean target = new IndexedTestBean(); AbstractPropertyAccessor accessor = createAccessor(target); - assertThat(accessor.getPropertyType("map[key0]")).isEqualTo(null); + assertThat(accessor.getPropertyType("map[key0]")).isNull(); accessor = createAccessor(target); accessor.setPropertyValue("map[key0]", "my String"); @@ -1594,7 +1525,7 @@ public void propertyTypeIndexedProperty() { } @Test - public void cornerSpr10115() { + void cornerSpr10115() { Spr10115Bean target = new Spr10115Bean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("prop1", "val1"); @@ -1602,7 +1533,7 @@ public void cornerSpr10115() { } @Test - public void cornerSpr13837() { + void cornerSpr13837() { Spr13837Bean target = new Spr13837Bean(); AbstractPropertyAccessor accessor = createAccessor(target); accessor.setPropertyValue("something", 42); diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java index 9f3bd08ec9f..7569be3d881 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyValuesTests.java @@ -43,13 +43,13 @@ protected void doTestTony(PropertyValues pvs) { m.put("forname", "Tony"); m.put("surname", "Blair"); m.put("age", "50"); - for (int i = 0; i < ps.length; i++) { - Object val = m.get(ps[i].getName()); + for (PropertyValue element : ps) { + Object val = m.get(element.getName()); assertThat(val != null).as("Can't have unexpected value").isTrue(); boolean condition = val instanceof String; assertThat(condition).as("Val i string").isTrue(); - assertThat(val.equals(ps[i].getValue())).as("val matches expected").isTrue(); - m.remove(ps[i].getName()); + assertThat(val.equals(element.getValue())).as("val matches expected").isTrue(); + m.remove(element.getName()); } assertThat(m.size() == 0).as("Map size is 0").isTrue(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java index ef1bbf4616b..b90fd515dc3 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -19,12 +19,16 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.URI; import java.net.URL; import java.time.DayOfWeek; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; @@ -45,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.SoftAssertions.assertSoftly; /** * Unit tests for {@link BeanUtils}. @@ -81,19 +86,43 @@ void instantiateClassWithOptionalNullableType() throws NoSuchMethodException { } @Test // gh-22531 - void instantiateClassWithOptionalPrimitiveType() throws NoSuchMethodException { - Constructor ctor = BeanWithPrimitiveTypes.class.getDeclaredConstructor(int.class, boolean.class, String.class); - BeanWithPrimitiveTypes bean = BeanUtils.instantiateClass(ctor, null, null, "foo"); - assertThat(bean.getCounter()).isEqualTo(0); - assertThat(bean.isFlag()).isEqualTo(false); - assertThat(bean.getValue()).isEqualTo("foo"); + void instantiateClassWithFewerArgsThanParameters() throws NoSuchMethodException { + Constructor constructor = getBeanWithPrimitiveTypesConstructor(); + + assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() -> + BeanUtils.instantiateClass(constructor, null, null, "foo")); } @Test // gh-22531 void instantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException { - Constructor ctor = BeanWithPrimitiveTypes.class.getDeclaredConstructor(int.class, boolean.class, String.class); + Constructor constructor = getBeanWithPrimitiveTypesConstructor(); + assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() -> - BeanUtils.instantiateClass(ctor, null, null, "foo", null)); + BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null)); + } + + @Test // gh-22531, gh-27390 + void instantiateClassWithOptionalPrimitiveTypes() throws NoSuchMethodException { + Constructor constructor = getBeanWithPrimitiveTypesConstructor(); + + BeanWithPrimitiveTypes bean = BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo"); + + assertSoftly(softly -> { + softly.assertThat(bean.isFlag()).isFalse(); + softly.assertThat(bean.getByteCount()).isEqualTo((byte) 0); + softly.assertThat(bean.getShortCount()).isEqualTo((short) 0); + softly.assertThat(bean.getIntCount()).isEqualTo(0); + softly.assertThat(bean.getLongCount()).isEqualTo(0L); + softly.assertThat(bean.getFloatCount()).isEqualTo(0F); + softly.assertThat(bean.getDoubleCount()).isEqualTo(0D); + softly.assertThat(bean.getCharacter()).isEqualTo('\0'); + softly.assertThat(bean.getText()).isEqualTo("foo"); + }); + } + + private Constructor getBeanWithPrimitiveTypesConstructor() throws NoSuchMethodException { + return BeanWithPrimitiveTypes.class.getConstructor(boolean.class, byte.class, short.class, int.class, + long.class, float.class, double.class, char.class, String.class); } @Test @@ -196,6 +225,29 @@ void copyPropertiesDoesNotHonorGenericTypeMismatches() { assertThat(longListHolder.getList()).isEmpty(); } + @Test // gh-26531 + void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception { + Order original = new Order("test", Arrays.asList("foo", "bar")); + + // Create a Proxy that loses the generic type information for the getLineItems() method. + OrderSummary proxy = proxyOrder(original); + assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString()) + .contains("java.util.List"); + assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) + .contains("java.util.List") + .doesNotContain(""); + + // Ensure that our custom Proxy works as expected. + assertThat(proxy.getId()).isEqualTo("test"); + assertThat(proxy.getLineItems()).containsExactly("foo", "bar"); + + // Copy from proxy to target. + Order target = new Order(); + BeanUtils.copyProperties(proxy, target); + assertThat(target.getId()).isEqualTo("test"); + assertThat(target.getLineItems()).containsExactly("foo", "bar"); + } + @Test void copyPropertiesWithEditable() throws Exception { TestBean tb = new TestBean(); @@ -601,30 +653,68 @@ public String getValue() { private static class BeanWithPrimitiveTypes { - private int counter; - private boolean flag; + private byte byteCount; + private short shortCount; + private int intCount; + private long longCount; + private float floatCount; + private double doubleCount; + private char character; + private String text; - private String value; @SuppressWarnings("unused") - public BeanWithPrimitiveTypes(int counter, boolean flag, String value) { - this.counter = counter; - this.flag = flag; - this.value = value; - } + public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount, + float floatCount, double doubleCount, char character, String text) { - public int getCounter() { - return counter; + this.flag = flag; + this.byteCount = byteCount; + this.shortCount = shortCount; + this.intCount = intCount; + this.longCount = longCount; + this.floatCount = floatCount; + this.doubleCount = doubleCount; + this.character = character; + this.text = text; } public boolean isFlag() { return flag; } - public String getValue() { - return value; + public byte getByteCount() { + return byteCount; + } + + public short getShortCount() { + return shortCount; + } + + public int getIntCount() { + return intCount; + } + + public long getLongCount() { + return longCount; + } + + public float getFloatCount() { + return floatCount; + } + + public double getDoubleCount() { + return doubleCount; } + + public char getCharacter() { + return character; + } + + public String getText() { + return text; + } + } private static class PrivateBeanWithPrivateConstructor { @@ -633,4 +723,77 @@ private PrivateBeanWithPrivateConstructor() { } } + @SuppressWarnings("unused") + private static class Order { + + private String id; + private List lineItems; + + + Order() { + } + + Order(String id, List lineItems) { + this.id = id; + this.lineItems = lineItems; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public List getLineItems() { + return this.lineItems; + } + + public void setLineItems(List lineItems) { + this.lineItems = lineItems; + } + + @Override + public String toString() { + return "Order [id=" + this.id + ", lineItems=" + this.lineItems + "]"; + } + } + + private interface OrderSummary { + + String getId(); + + List getLineItems(); + } + + + private OrderSummary proxyOrder(Order order) { + return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { OrderSummary.class }, new OrderInvocationHandler(order)); + } + + + private static class OrderInvocationHandler implements InvocationHandler { + + private final Order order; + + + OrderInvocationHandler(Order order) { + this.order = order; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + // Ignore args since OrderSummary doesn't declare any methods with arguments, + // and we're not supporting equals(Object), etc. + return Order.class.getDeclaredMethod(method.getName()).invoke(this.order); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java index c17e2c7359b..78524e632b0 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.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. @@ -28,6 +28,7 @@ /** * @author Keith Donald * @author Juergen Hoeller + * @author Sam Brannen */ public class BeanWrapperAutoGrowingTests { @@ -37,7 +38,7 @@ public class BeanWrapperAutoGrowingTests { @BeforeEach - public void setUp() { + public void setup() { wrapper.setAutoGrowNestedPaths(true); } @@ -66,11 +67,6 @@ public void getPropertyValueAutoGrowArray() { assertThat(bean.getArray()[0]).isInstanceOf(Bean.class); } - private void assertNotNull(Object propertyValue) { - assertThat(propertyValue).isNotNull(); - } - - @Test public void setPropertyValueAutoGrowArray() { wrapper.setPropertyValue("array[0].prop", "test"); @@ -93,12 +89,39 @@ public void getPropertyValueAutoGrowArrayBySeveralElements() { } @Test - public void getPropertyValueAutoGrowMultiDimensionalArray() { + public void getPropertyValueAutoGrow2dArray() { assertNotNull(wrapper.getPropertyValue("multiArray[0][0]")); assertThat(bean.getMultiArray()[0].length).isEqualTo(1); assertThat(bean.getMultiArray()[0][0]).isInstanceOf(Bean.class); } + @Test + public void getPropertyValueAutoGrow3dArray() { + assertNotNull(wrapper.getPropertyValue("threeDimensionalArray[1][2][3]")); + assertThat(bean.getThreeDimensionalArray()[1].length).isEqualTo(3); + assertThat(bean.getThreeDimensionalArray()[1][2][3]).isInstanceOf(Bean.class); + } + + @Test + public void setPropertyValueAutoGrow2dArray() { + Bean newBean = new Bean(); + newBean.setProp("enigma"); + wrapper.setPropertyValue("multiArray[2][3]", newBean); + assertThat(bean.getMultiArray()[2][3]) + .isInstanceOf(Bean.class) + .extracting(Bean::getProp).isEqualTo("enigma"); + } + + @Test + public void setPropertyValueAutoGrow3dArray() { + Bean newBean = new Bean(); + newBean.setProp("enigma"); + wrapper.setPropertyValue("threeDimensionalArray[2][3][4]", newBean); + assertThat(bean.getThreeDimensionalArray()[2][3][4]) + .isInstanceOf(Bean.class) + .extracting(Bean::getProp).isEqualTo("enigma"); + } + @Test public void getPropertyValueAutoGrowList() { assertNotNull(wrapper.getPropertyValue("list[0]")); @@ -131,7 +154,7 @@ public void getPropertyValueAutoGrowListBySeveralElements() { public void getPropertyValueAutoGrowListFailsAgainstLimit() { wrapper.setAutoGrowCollectionLimit(2); assertThatExceptionOfType(InvalidPropertyException.class).isThrownBy(() -> - assertNotNull(wrapper.getPropertyValue("list[4]"))) + wrapper.getPropertyValue("list[4]")) .withRootCauseInstanceOf(IndexOutOfBoundsException.class); } @@ -161,6 +184,11 @@ public void setNestedPropertyValueAutoGrowMap() { } + private static void assertNotNull(Object propertyValue) { + assertThat(propertyValue).isNotNull(); + } + + @SuppressWarnings("rawtypes") public static class Bean { @@ -174,6 +202,8 @@ public static class Bean { private Bean[][] multiArray; + private Bean[][][] threeDimensionalArray; + private List list; private List> multiList; @@ -214,6 +244,14 @@ public void setMultiArray(Bean[][] multiArray) { this.multiArray = multiArray; } + public Bean[][][] getThreeDimensionalArray() { + return threeDimensionalArray; + } + + public void setThreeDimensionalArray(Bean[][][] threeDimensionalArray) { + this.threeDimensionalArray = threeDimensionalArray; + } + public List getList() { return list; } diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java index 3a5ac371712..95bd30cfb84 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperEnumTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public void testCustomEnumWithNull() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnum", null); - assertThat(gb.getCustomEnum()).isEqualTo(null); + assertThat(gb.getCustomEnum()).isNull(); } @Test @@ -54,7 +54,7 @@ public void testCustomEnumWithEmptyString() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("customEnum", ""); - assertThat(gb.getCustomEnum()).isEqualTo(null); + assertThat(gb.getCustomEnum()).isNull(); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java index be0c2cc5953..760e98cf935 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperGenericsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -16,7 +16,6 @@ package org.springframework.beans; -import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -46,10 +45,10 @@ * @author Chris Beams * @since 18.01.2006 */ -public class BeanWrapperGenericsTests { +class BeanWrapperGenericsTests { @Test - public void testGenericSet() { + void testGenericSet() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); Set input = new HashSet<>(); @@ -61,7 +60,7 @@ public void testGenericSet() { } @Test - public void testGenericLowerBoundedSet() { + void testGenericLowerBoundedSet() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, true)); @@ -74,7 +73,7 @@ public void testGenericLowerBoundedSet() { } @Test - public void testGenericSetWithConversionFailure() { + void testGenericSetWithConversionFailure() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); Set input = new HashSet<>(); @@ -85,7 +84,7 @@ public void testGenericSetWithConversionFailure() { } @Test - public void testGenericList() throws MalformedURLException { + void testGenericList() throws Exception { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); List input = new ArrayList<>(); @@ -97,7 +96,7 @@ public void testGenericList() throws MalformedURLException { } @Test - public void testGenericListElement() throws MalformedURLException { + void testGenericListElement() throws Exception { GenericBean gb = new GenericBean<>(); gb.setResourceList(new ArrayList<>()); BeanWrapper bw = new BeanWrapperImpl(gb); @@ -106,29 +105,29 @@ public void testGenericListElement() throws MalformedURLException { } @Test - public void testGenericMap() { + void testGenericMap() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); Map input = new HashMap<>(); input.put("4", "5"); input.put("6", "7"); bw.setPropertyValue("shortMap", input); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericMapElement() { + void testGenericMapElement() { GenericBean gb = new GenericBean<>(); gb.setShortMap(new HashMap<>()); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("shortMap[4]", "5"); assertThat(bw.getPropertyValue("shortMap[4]")).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); } @Test - public void testGenericMapWithKeyType() { + void testGenericMapWithKeyType() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); Map input = new HashMap<>(); @@ -140,17 +139,17 @@ public void testGenericMapWithKeyType() { } @Test - public void testGenericMapElementWithKeyType() { + void testGenericMapElementWithKeyType() { GenericBean gb = new GenericBean<>(); gb.setLongMap(new HashMap()); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("longMap[4]", "5"); - assertThat(gb.getLongMap().get(new Long("4"))).isEqualTo("5"); + assertThat(gb.getLongMap().get(Long.valueOf("4"))).isEqualTo("5"); assertThat(bw.getPropertyValue("longMap[4]")).isEqualTo("5"); } @Test - public void testGenericMapWithCollectionValue() { + void testGenericMapWithCollectionValue() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)); @@ -162,14 +161,12 @@ public void testGenericMapWithCollectionValue() { value2.add(Boolean.TRUE); input.put("2", value2); bw.setPropertyValue("collectionMap", input); - boolean condition1 = gb.getCollectionMap().get(1) instanceof HashSet; - assertThat(condition1).isTrue(); - boolean condition = gb.getCollectionMap().get(2) instanceof ArrayList; - assertThat(condition).isTrue(); + assertThat(gb.getCollectionMap().get(1) instanceof HashSet).isTrue(); + assertThat(gb.getCollectionMap().get(2) instanceof ArrayList).isTrue(); } @Test - public void testGenericMapElementWithCollectionValue() { + void testGenericMapElementWithCollectionValue() { GenericBean gb = new GenericBean<>(); gb.setCollectionMap(new HashMap<>()); BeanWrapper bw = new BeanWrapperImpl(gb); @@ -177,24 +174,23 @@ public void testGenericMapElementWithCollectionValue() { HashSet value1 = new HashSet<>(); value1.add(1); bw.setPropertyValue("collectionMap[1]", value1); - boolean condition = gb.getCollectionMap().get(1) instanceof HashSet; - assertThat(condition).isTrue(); + assertThat(gb.getCollectionMap().get(1) instanceof HashSet).isTrue(); } @Test - public void testGenericMapFromProperties() { + void testGenericMapFromProperties() { GenericBean gb = new GenericBean<>(); BeanWrapper bw = new BeanWrapperImpl(gb); Properties input = new Properties(); input.setProperty("4", "5"); input.setProperty("6", "7"); bw.setPropertyValue("shortMap", input); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericListOfLists() throws MalformedURLException { + void testGenericListOfLists() { GenericBean gb = new GenericBean<>(); List> list = new ArrayList<>(); list.add(new ArrayList<>()); @@ -206,7 +202,7 @@ public void testGenericListOfLists() throws MalformedURLException { } @Test - public void testGenericListOfListsWithElementConversion() throws MalformedURLException { + void testGenericListOfListsWithElementConversion() { GenericBean gb = new GenericBean<>(); List> list = new ArrayList<>(); list.add(new ArrayList<>()); @@ -218,7 +214,7 @@ public void testGenericListOfListsWithElementConversion() throws MalformedURLExc } @Test - public void testGenericListOfArrays() throws MalformedURLException { + void testGenericListOfArrays() { GenericBean gb = new GenericBean<>(); ArrayList list = new ArrayList<>(); list.add(new String[] {"str1", "str2"}); @@ -230,7 +226,7 @@ public void testGenericListOfArrays() throws MalformedURLException { } @Test - public void testGenericListOfArraysWithElementConversion() throws MalformedURLException { + void testGenericListOfArraysWithElementConversion() { GenericBean gb = new GenericBean<>(); ArrayList list = new ArrayList<>(); list.add(new String[] {"str1", "str2"}); @@ -243,55 +239,55 @@ public void testGenericListOfArraysWithElementConversion() throws MalformedURLEx } @Test - public void testGenericListOfMaps() throws MalformedURLException { + void testGenericListOfMaps() { GenericBean gb = new GenericBean<>(); List> list = new ArrayList<>(); list.add(new HashMap<>()); gb.setListOfMaps(list); BeanWrapper bw = new BeanWrapperImpl(gb); - bw.setPropertyValue("listOfMaps[0][10]", new Long(5)); - assertThat(bw.getPropertyValue("listOfMaps[0][10]")).isEqualTo(new Long(5)); - assertThat(gb.getListOfMaps().get(0).get(10)).isEqualTo(new Long(5)); + bw.setPropertyValue("listOfMaps[0][10]", 5L); + assertThat(bw.getPropertyValue("listOfMaps[0][10]")).isEqualTo(5L); + assertThat(gb.getListOfMaps().get(0).get(10)).isEqualTo(Long.valueOf(5)); } @Test - public void testGenericListOfMapsWithElementConversion() throws MalformedURLException { + void testGenericListOfMapsWithElementConversion() { GenericBean gb = new GenericBean<>(); List> list = new ArrayList<>(); list.add(new HashMap<>()); gb.setListOfMaps(list); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("listOfMaps[0][10]", "5"); - assertThat(bw.getPropertyValue("listOfMaps[0][10]")).isEqualTo(new Long(5)); - assertThat(gb.getListOfMaps().get(0).get(10)).isEqualTo(new Long(5)); + assertThat(bw.getPropertyValue("listOfMaps[0][10]")).isEqualTo(5L); + assertThat(gb.getListOfMaps().get(0).get(10)).isEqualTo(Long.valueOf(5)); } @Test - public void testGenericMapOfMaps() throws MalformedURLException { + void testGenericMapOfMaps() { GenericBean gb = new GenericBean<>(); Map> map = new HashMap<>(); map.put("mykey", new HashMap<>()); gb.setMapOfMaps(map); BeanWrapper bw = new BeanWrapperImpl(gb); - bw.setPropertyValue("mapOfMaps[mykey][10]", new Long(5)); - assertThat(bw.getPropertyValue("mapOfMaps[mykey][10]")).isEqualTo(new Long(5)); - assertThat(gb.getMapOfMaps().get("mykey").get(10)).isEqualTo(new Long(5)); + bw.setPropertyValue("mapOfMaps[mykey][10]", 5L); + assertThat(bw.getPropertyValue("mapOfMaps[mykey][10]")).isEqualTo(5L); + assertThat(gb.getMapOfMaps().get("mykey").get(10)).isEqualTo(Long.valueOf(5)); } @Test - public void testGenericMapOfMapsWithElementConversion() throws MalformedURLException { + void testGenericMapOfMapsWithElementConversion() { GenericBean gb = new GenericBean<>(); Map> map = new HashMap<>(); map.put("mykey", new HashMap<>()); gb.setMapOfMaps(map); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("mapOfMaps[mykey][10]", "5"); - assertThat(bw.getPropertyValue("mapOfMaps[mykey][10]")).isEqualTo(new Long(5)); - assertThat(gb.getMapOfMaps().get("mykey").get(10)).isEqualTo(new Long(5)); + assertThat(bw.getPropertyValue("mapOfMaps[mykey][10]")).isEqualTo(Long.valueOf(5)); + assertThat(gb.getMapOfMaps().get("mykey").get(10)).isEqualTo(Long.valueOf(5)); } @Test - public void testGenericMapOfLists() throws MalformedURLException { + void testGenericMapOfLists() { GenericBean gb = new GenericBean<>(); Map> map = new HashMap<>(); map.put(1, new ArrayList<>()); @@ -303,7 +299,7 @@ public void testGenericMapOfLists() throws MalformedURLException { } @Test - public void testGenericMapOfListsWithElementConversion() throws MalformedURLException { + void testGenericMapOfListsWithElementConversion() { GenericBean gb = new GenericBean<>(); Map> map = new HashMap<>(); map.put(1, new ArrayList<>()); @@ -315,7 +311,7 @@ public void testGenericMapOfListsWithElementConversion() throws MalformedURLExce } @Test - public void testGenericTypeNestingMapOfInteger() throws Exception { + void testGenericTypeNestingMapOfInteger() { Map map = new HashMap<>(); map.put("testKey", "100"); @@ -324,14 +320,13 @@ public void testGenericTypeNestingMapOfInteger() throws Exception { bw.setPropertyValue("mapOfInteger", map); Object obj = gb.getMapOfInteger().get("testKey"); - boolean condition = obj instanceof Integer; - assertThat(condition).isTrue(); + assertThat(obj instanceof Integer).isTrue(); } @Test - public void testGenericTypeNestingMapOfListOfInteger() throws Exception { + void testGenericTypeNestingMapOfListOfInteger() { Map> map = new HashMap<>(); - List list = Arrays.asList(new String[] {"1", "2", "3"}); + List list = Arrays.asList("1", "2", "3"); map.put("testKey", list); NestedGenericCollectionBean gb = new NestedGenericCollectionBean(); @@ -339,13 +334,12 @@ public void testGenericTypeNestingMapOfListOfInteger() throws Exception { bw.setPropertyValue("mapOfListOfInteger", map); Object obj = gb.getMapOfListOfInteger().get("testKey").get(0); - boolean condition = obj instanceof Integer; - assertThat(condition).isTrue(); + assertThat(obj instanceof Integer).isTrue(); assertThat(((Integer) obj).intValue()).isEqualTo(1); } @Test - public void testGenericTypeNestingListOfMapOfInteger() throws Exception { + void testGenericTypeNestingListOfMapOfInteger() { List> list = new ArrayList<>(); Map map = new HashMap<>(); map.put("testKey", "5"); @@ -356,15 +350,14 @@ public void testGenericTypeNestingListOfMapOfInteger() throws Exception { bw.setPropertyValue("listOfMapOfInteger", list); Object obj = gb.getListOfMapOfInteger().get(0).get("testKey"); - boolean condition = obj instanceof Integer; - assertThat(condition).isTrue(); + assertThat(obj instanceof Integer).isTrue(); assertThat(((Integer) obj).intValue()).isEqualTo(5); } @Test - public void testGenericTypeNestingMapOfListOfListOfInteger() throws Exception { + void testGenericTypeNestingMapOfListOfListOfInteger() { Map>> map = new HashMap<>(); - List list = Arrays.asList(new String[] {"1", "2", "3"}); + List list = Arrays.asList("1", "2", "3"); map.put("testKey", Collections.singletonList(list)); NestedGenericCollectionBean gb = new NestedGenericCollectionBean(); @@ -372,13 +365,12 @@ public void testGenericTypeNestingMapOfListOfListOfInteger() throws Exception { bw.setPropertyValue("mapOfListOfListOfInteger", map); Object obj = gb.getMapOfListOfListOfInteger().get("testKey").get(0).get(0); - boolean condition = obj instanceof Integer; - assertThat(condition).isTrue(); + assertThat(obj instanceof Integer).isTrue(); assertThat(((Integer) obj).intValue()).isEqualTo(1); } @Test - public void testComplexGenericMap() { + void testComplexGenericMap() { Map, List> inputMap = new HashMap<>(); List inputKey = new ArrayList<>(); inputKey.add("1"); @@ -391,11 +383,11 @@ public void testComplexGenericMap() { bw.setPropertyValue("genericMap", inputMap); assertThat(holder.getGenericMap().keySet().iterator().next().get(0)).isEqualTo(1); - assertThat(holder.getGenericMap().values().iterator().next().get(0)).isEqualTo(new Long(10)); + assertThat(holder.getGenericMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); } @Test - public void testComplexGenericMapWithCollectionConversion() { + void testComplexGenericMapWithCollectionConversion() { Map, Set> inputMap = new HashMap<>(); Set inputKey = new HashSet<>(); inputKey.add("1"); @@ -408,11 +400,11 @@ public void testComplexGenericMapWithCollectionConversion() { bw.setPropertyValue("genericMap", inputMap); assertThat(holder.getGenericMap().keySet().iterator().next().get(0)).isEqualTo(1); - assertThat(holder.getGenericMap().values().iterator().next().get(0)).isEqualTo(new Long(10)); + assertThat(holder.getGenericMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); } @Test - public void testComplexGenericIndexedMapEntry() { + void testComplexGenericIndexedMapEntry() { List inputValue = new ArrayList<>(); inputValue.add("10"); @@ -421,11 +413,11 @@ public void testComplexGenericIndexedMapEntry() { bw.setPropertyValue("genericIndexedMap[1]", inputValue); assertThat(holder.getGenericIndexedMap().keySet().iterator().next()).isEqualTo(1); - assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(new Long(10)); + assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); } @Test - public void testComplexGenericIndexedMapEntryWithCollectionConversion() { + void testComplexGenericIndexedMapEntryWithCollectionConversion() { Set inputValue = new HashSet<>(); inputValue.add("10"); @@ -434,11 +426,11 @@ public void testComplexGenericIndexedMapEntryWithCollectionConversion() { bw.setPropertyValue("genericIndexedMap[1]", inputValue); assertThat(holder.getGenericIndexedMap().keySet().iterator().next()).isEqualTo(1); - assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(new Long(10)); + assertThat(holder.getGenericIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); } @Test - public void testComplexDerivedIndexedMapEntry() { + void testComplexDerivedIndexedMapEntry() { List inputValue = new ArrayList<>(); inputValue.add("10"); @@ -447,11 +439,11 @@ public void testComplexDerivedIndexedMapEntry() { bw.setPropertyValue("derivedIndexedMap[1]", inputValue); assertThat(holder.getDerivedIndexedMap().keySet().iterator().next()).isEqualTo(1); - assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(new Long(10)); + assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); } @Test - public void testComplexDerivedIndexedMapEntryWithCollectionConversion() { + void testComplexDerivedIndexedMapEntryWithCollectionConversion() { Set inputValue = new HashSet<>(); inputValue.add("10"); @@ -460,11 +452,11 @@ public void testComplexDerivedIndexedMapEntryWithCollectionConversion() { bw.setPropertyValue("derivedIndexedMap[1]", inputValue); assertThat(holder.getDerivedIndexedMap().keySet().iterator().next()).isEqualTo(1); - assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(new Long(10)); + assertThat(holder.getDerivedIndexedMap().values().iterator().next().get(0)).isEqualTo(Long.valueOf(10)); } @Test - public void testGenericallyTypedIntegerBean() throws Exception { + void testGenericallyTypedIntegerBean() { GenericIntegerBean gb = new GenericIntegerBean(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("genericProperty", "10"); @@ -475,7 +467,7 @@ public void testGenericallyTypedIntegerBean() throws Exception { } @Test - public void testGenericallyTypedSetOfIntegerBean() throws Exception { + void testGenericallyTypedSetOfIntegerBean() { GenericSetOfIntegerBean gb = new GenericSetOfIntegerBean(); BeanWrapper bw = new BeanWrapperImpl(gb); bw.setPropertyValue("genericProperty", "10"); @@ -486,23 +478,23 @@ public void testGenericallyTypedSetOfIntegerBean() throws Exception { } @Test - public void testSettingGenericPropertyWithReadOnlyInterface() { + void testSettingGenericPropertyWithReadOnlyInterface() { Bar bar = new Bar(); BeanWrapper bw = new BeanWrapperImpl(bar); bw.setPropertyValue("version", "10"); - assertThat(bar.getVersion()).isEqualTo(new Double(10.0)); + assertThat(bar.getVersion()).isEqualTo(Double.valueOf(10.0)); } @Test - public void testSettingLongPropertyWithGenericInterface() { + void testSettingLongPropertyWithGenericInterface() { Promotion bean = new Promotion(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.setPropertyValue("id", "10"); - assertThat(bean.getId()).isEqualTo(new Long(10)); + assertThat(bean.getId()).isEqualTo(Long.valueOf(10)); } @Test - public void testUntypedPropertyWithMapAtRuntime() { + void testUntypedPropertyWithMapAtRuntime() { class Holder { private final D data; public Holder(D data) { diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 0711189b5f5..8856e31a0e0 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.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. @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; /** * Specific {@link BeanWrapperImpl} tests. @@ -37,7 +38,7 @@ * @author Chris Beams * @author Dave Syer */ -public class BeanWrapperTests extends AbstractPropertyAccessorTests { +class BeanWrapperTests extends AbstractPropertyAccessorTests { @Override protected BeanWrapperImpl createAccessor(Object target) { @@ -46,7 +47,7 @@ protected BeanWrapperImpl createAccessor(Object target) { @Test - public void setterDoesNotCallGetter() { + void setterDoesNotCallGetter() { GetterBean target = new GetterBean(); BeanWrapper accessor = createAccessor(target); accessor.setPropertyValue("name", "tom"); @@ -55,7 +56,7 @@ public void setterDoesNotCallGetter() { } @Test - public void getterSilentlyFailWithOldValueExtraction() { + void getterSilentlyFailWithOldValueExtraction() { GetterBean target = new GetterBean(); BeanWrapper accessor = createAccessor(target); accessor.setExtractOldValueForEditor(true); // This will call the getter @@ -65,7 +66,7 @@ public void getterSilentlyFailWithOldValueExtraction() { } @Test - public void aliasedSetterThroughDefaultMethod() { + void aliasedSetterThroughDefaultMethod() { GetterBean target = new GetterBean(); BeanWrapper accessor = createAccessor(target); accessor.setPropertyValue("aliasedName", "tom"); @@ -74,7 +75,7 @@ public void aliasedSetterThroughDefaultMethod() { } @Test - public void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() { + void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() { TestBean target = new TestBean(); String newName = "tony"; String invalidTouchy = ".valid"; @@ -91,12 +92,12 @@ public void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() { .getNewValue()).isEqualTo(invalidTouchy); }); // Test validly set property matches - assertThat(target.getName().equals(newName)).as("Valid set property must stick").isTrue(); - assertThat(target.getAge() == 0).as("Invalid set property must retain old value").isTrue(); + assertThat(target.getName()).as("Valid set property must stick").isEqualTo(newName); + assertThat(target.getAge()).as("Invalid set property must retain old value").isEqualTo(0); } @Test - public void checkNotWritablePropertyHoldPossibleMatches() { + void checkNotWritablePropertyHoldPossibleMatches() { TestBean target = new TestBean(); BeanWrapper accessor = createAccessor(target); assertThatExceptionOfType(NotWritablePropertyException.class).isThrownBy(() -> @@ -105,15 +106,15 @@ public void checkNotWritablePropertyHoldPossibleMatches() { } @Test // Can't be shared; there is no such thing as a read-only field - public void setReadOnlyMapProperty() { + void setReadOnlyMapProperty() { TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean())); TypedReadOnlyMapClient target = new TypedReadOnlyMapClient(); BeanWrapper accessor = createAccessor(target); - accessor.setPropertyValue("map", map); + assertThatNoException().isThrownBy(() -> accessor.setPropertyValue("map", map)); } @Test - public void notWritablePropertyExceptionContainsAlternativeMatch() { + void notWritablePropertyExceptionContainsAlternativeMatch() { IntelliBean target = new IntelliBean(); BeanWrapper bw = createAccessor(target); try { @@ -121,12 +122,12 @@ public void notWritablePropertyExceptionContainsAlternativeMatch() { } catch (NotWritablePropertyException ex) { assertThat(ex.getPossibleMatches()).as("Possible matches not determined").isNotNull(); - assertThat(ex.getPossibleMatches().length).as("Invalid amount of alternatives").isEqualTo(1); + assertThat(ex.getPossibleMatches()).as("Invalid amount of alternatives").hasSize(1); } } @Test - public void notWritablePropertyExceptionContainsAlternativeMatches() { + void notWritablePropertyExceptionContainsAlternativeMatches() { IntelliBean target = new IntelliBean(); BeanWrapper bw = createAccessor(target); try { @@ -134,23 +135,23 @@ public void notWritablePropertyExceptionContainsAlternativeMatches() { } catch (NotWritablePropertyException ex) { assertThat(ex.getPossibleMatches()).as("Possible matches not determined").isNotNull(); - assertThat(ex.getPossibleMatches().length).as("Invalid amount of alternatives").isEqualTo(3); + assertThat(ex.getPossibleMatches()).as("Invalid amount of alternatives").hasSize(3); } } @Override @Test // Can't be shared: no type mismatch with a field - public void setPropertyTypeMismatch() { + void setPropertyTypeMismatch() { PropertyTypeMismatch target = new PropertyTypeMismatch(); BeanWrapper accessor = createAccessor(target); accessor.setPropertyValue("object", "a String"); assertThat(target.value).isEqualTo("a String"); - assertThat(target.getObject() == 8).isTrue(); + assertThat(target.getObject()).isEqualTo(8); assertThat(accessor.getPropertyValue("object")).isEqualTo(8); } @Test - public void propertyDescriptors() { + void propertyDescriptors() { TestBean target = new TestBean(); target.setSpouse(new TestBean()); BeanWrapper accessor = createAccessor(target); @@ -166,7 +167,7 @@ public void propertyDescriptors() { @Test @SuppressWarnings("unchecked") - public void getPropertyWithOptional() { + void getPropertyWithOptional() { GetterWithOptional target = new GetterWithOptional(); TestBean tb = new TestBean("x"); BeanWrapper accessor = createAccessor(target); @@ -189,7 +190,7 @@ public void getPropertyWithOptional() { } @Test - public void getPropertyWithOptionalAndAutoGrow() { + void getPropertyWithOptionalAndAutoGrow() { GetterWithOptional target = new GetterWithOptional(); BeanWrapper accessor = createAccessor(target); accessor.setAutoGrowNestedPaths(true); @@ -201,7 +202,7 @@ public void getPropertyWithOptionalAndAutoGrow() { } @Test - public void incompletelyQuotedKeyLeadsToPropertyException() { + void incompletelyQuotedKeyLeadsToPropertyException() { TestBean target = new TestBean(); BeanWrapper accessor = createAccessor(target); assertThatExceptionOfType(NotWritablePropertyException.class).isThrownBy(() -> diff --git a/spring-beans/src/test/java/org/springframework/beans/ConcurrentBeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/ConcurrentBeanWrapperTests.java index f1d4912b16e..d0d3e233270 100644 --- a/spring-beans/src/test/java/org/springframework/beans/ConcurrentBeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/ConcurrentBeanWrapperTests.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. @@ -18,14 +18,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,23 +36,21 @@ * @author Chris Beams * @since 08.03.2004 */ -public class ConcurrentBeanWrapperTests { +class ConcurrentBeanWrapperTests { private final Log logger = LogFactory.getLog(getClass()); - private Set set = Collections.synchronizedSet(new HashSet()); + private final Set set = ConcurrentHashMap.newKeySet(); private Throwable ex = null; - @Test - public void testSingleThread() { - for (int i = 0; i < 100; i++) { - performSet(); - } + @RepeatedTest(100) + void testSingleThread() { + performSet(); } @Test - public void testConcurrent() { + void testConcurrent() { for (int i = 0; i < 10; i++) { TestRun run = new TestRun(this); set.add(run); @@ -82,7 +80,7 @@ private static void performSet() { Properties p = (Properties) System.getProperties().clone(); - assertThat(p.size() != 0).as("The System properties must not be empty").isTrue(); + assertThat(p).as("The System properties must not be empty").isNotEmpty(); for (Iterator i = p.entrySet().iterator(); i.hasNext();) { i.next(); @@ -111,7 +109,7 @@ private static class TestRun implements Runnable { private ConcurrentBeanWrapperTests test; - public TestRun(ConcurrentBeanWrapperTests test) { + TestRun(ConcurrentBeanWrapperTests test) { this.test = test; } diff --git a/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java index 1ca8ddc6029..2cbe8f2ff41 100644 --- a/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/DirectFieldAccessorTests.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. @@ -29,7 +29,7 @@ * @author Chris Beams * @author Stephane Nicoll */ -public class DirectFieldAccessorTests extends AbstractPropertyAccessorTests { +class DirectFieldAccessorTests extends AbstractPropertyAccessorTests { @Override protected DirectFieldAccessor createAccessor(Object target) { @@ -38,7 +38,7 @@ protected DirectFieldAccessor createAccessor(Object target) { @Test - public void withShadowedField() { + void withShadowedField() { final StringBuilder sb = new StringBuilder(); TestBean target = new TestBean() { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java index d70a7bbd1bd..b3230e53e0e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java @@ -63,9 +63,8 @@ public class BeanFactoryUtilsTests { @BeforeEach - public void setUp() { + public void setup() { // Interesting hierarchical factory to test counts. - // Slow to read so we cache it. DefaultListableBeanFactory grandParent = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(grandParent).loadBeanDefinitions(ROOT_CONTEXT); @@ -93,7 +92,7 @@ public void testHierarchicalCountBeansWithNonHierarchicalFactory() { * Check that override doesn't count as two separate beans. */ @Test - public void testHierarchicalCountBeansWithOverride() throws Exception { + public void testHierarchicalCountBeansWithOverride() { // Leaf count assertThat(this.listableBeanFactory.getBeanDefinitionCount() == 1).isTrue(); // Count minus duplicate @@ -101,14 +100,14 @@ public void testHierarchicalCountBeansWithOverride() throws Exception { } @Test - public void testHierarchicalNamesWithNoMatch() throws Exception { + public void testHierarchicalNamesWithNoMatch() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class)); assertThat(names.size()).isEqualTo(0); } @Test - public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception { + public void testHierarchicalNamesWithMatchOnlyInRoot() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class)); assertThat(names.size()).isEqualTo(1); @@ -118,7 +117,7 @@ public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception { } @Test - public void testGetBeanNamesForTypeWithOverride() throws Exception { + public void testGetBeanNamesForTypeWithOverride() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class)); // includes 2 TestBeans from FactoryBeans (DummyFactory definitions) @@ -236,7 +235,7 @@ public void testFindsBeansOfTypeWithDefaultFactory() { } @Test - public void testHierarchicalResolutionWithOverride() throws Exception { + public void testHierarchicalResolutionWithOverride() { Object test3 = this.listableBeanFactory.getBean("test3"); Object test = this.listableBeanFactory.getBean("test"); @@ -276,14 +275,14 @@ public void testHierarchicalResolutionWithOverride() throws Exception { } @Test - public void testHierarchicalNamesForAnnotationWithNoMatch() throws Exception { + public void testHierarchicalNamesForAnnotationWithNoMatch() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class)); assertThat(names.size()).isEqualTo(0); } @Test - public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Exception { + public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() { List names = Arrays.asList( BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class)); assertThat(names.size()).isEqualTo(1); @@ -293,7 +292,7 @@ public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Excep } @Test - public void testGetBeanNamesForAnnotationWithOverride() throws Exception { + public void testGetBeanNamesForAnnotationWithOverride() { AnnotatedBean annotatedBean = new AnnotatedBean(); this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean); List names = Arrays.asList( @@ -433,6 +432,7 @@ public void isSingletonAndIsPrototypeWithStaticFactory() { String basePackage() default ""; } + @Retention(RetentionPolicy.RUNTIME) @ControllerAdvice @interface RestControllerAdvice { @@ -444,18 +444,23 @@ public void isSingletonAndIsPrototypeWithStaticFactory() { String basePackage() default ""; } + @ControllerAdvice("com.example") static class ControllerAdviceClass { } + @RestControllerAdvice("com.example") static class RestControllerAdviceClass { } + static class TestBeanSmartFactoryBean implements SmartFactoryBean { private final TestBean testBean = new TestBean("enigma", 42); + private final boolean singleton; + private final boolean prototype; TestBeanSmartFactoryBean(boolean singleton, boolean prototype) { @@ -478,7 +483,8 @@ public Class getObjectType() { return TestBean.class; } - public TestBean getObject() throws Exception { + @Override + public TestBean getObject() { // We don't really care if the actual instance is a singleton or prototype // for the tests that use this factory. return this.testBean; diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index dd4ad2b8f1e..cbf9983dc0b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,8 +48,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.NotWritablePropertyException; -import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyValue; import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; @@ -84,7 +82,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.Resource; @@ -182,9 +179,9 @@ void prototypeFactoryBeanIgnoredByNonEagerTypeMatching() { assertThat(!DummyFactory.wasPrototypeCreated()).as("prototype not instantiated").isTrue(); String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).hasSize(0); beanNames = lbf.getBeanNamesForAnnotation(SuppressWarnings.class); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).hasSize(0); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -216,9 +213,9 @@ void singletonFactoryBeanIgnoredByNonEagerTypeMatching() { assertThat(!DummyFactory.wasPrototypeCreated()).as("prototype not instantiated").isTrue(); String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).hasSize(0); beanNames = lbf.getBeanNamesForAnnotation(SuppressWarnings.class); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).hasSize(0); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -249,9 +246,9 @@ void nonInitializedFactoryBeanIgnoredByNonEagerTypeMatching() { assertThat(!DummyFactory.wasPrototypeCreated()).as("prototype not instantiated").isTrue(); String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).hasSize(0); beanNames = lbf.getBeanNamesForAnnotation(SuppressWarnings.class); - assertThat(beanNames.length).isEqualTo(0); + assertThat(beanNames).hasSize(0); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -283,7 +280,7 @@ void initializedFactoryBeanFoundByNonEagerTypeMatching() { assertThat(!DummyFactory.wasPrototypeCreated()).as("prototype not instantiated").isTrue(); String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("x1"); assertThat(lbf.containsSingleton("x1")).isTrue(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -337,7 +334,7 @@ void staticFactoryMethodFoundByNonEagerTypeMatching() { TestBeanFactory.initialized = false; String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("x1"); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -349,7 +346,7 @@ void staticFactoryMethodFoundByNonEagerTypeMatching() { assertThat(lbf.isTypeMatch("x1", TestBean.class)).isTrue(); assertThat(lbf.isTypeMatch("&x1", TestBean.class)).isFalse(); assertThat(lbf.getType("x1")).isEqualTo(TestBean.class); - assertThat(lbf.getType("&x1")).isEqualTo(null); + assertThat(lbf.getType("&x1")).isNull(); assertThat(TestBeanFactory.initialized).isFalse(); } @@ -362,7 +359,7 @@ void staticPrototypeFactoryMethodFoundByNonEagerTypeMatching() { TestBeanFactory.initialized = false; String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("x1"); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -374,7 +371,7 @@ void staticPrototypeFactoryMethodFoundByNonEagerTypeMatching() { assertThat(lbf.isTypeMatch("x1", TestBean.class)).isTrue(); assertThat(lbf.isTypeMatch("&x1", TestBean.class)).isFalse(); assertThat(lbf.getType("x1")).isEqualTo(TestBean.class); - assertThat(lbf.getType("&x1")).isEqualTo(null); + assertThat(lbf.getType("&x1")).isNull(); assertThat(TestBeanFactory.initialized).isFalse(); } @@ -389,7 +386,7 @@ void nonStaticFactoryMethodFoundByNonEagerTypeMatching() { TestBeanFactory.initialized = false; String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("x1"); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -401,7 +398,7 @@ void nonStaticFactoryMethodFoundByNonEagerTypeMatching() { assertThat(lbf.isTypeMatch("x1", TestBean.class)).isTrue(); assertThat(lbf.isTypeMatch("&x1", TestBean.class)).isFalse(); assertThat(lbf.getType("x1")).isEqualTo(TestBean.class); - assertThat(lbf.getType("&x1")).isEqualTo(null); + assertThat(lbf.getType("&x1")).isNull(); assertThat(TestBeanFactory.initialized).isFalse(); } @@ -417,7 +414,7 @@ void nonStaticPrototypeFactoryMethodFoundByNonEagerTypeMatching() { TestBeanFactory.initialized = false; String[] beanNames = lbf.getBeanNamesForType(TestBean.class, true, false); - assertThat(beanNames.length).isEqualTo(1); + assertThat(beanNames).hasSize(1); assertThat(beanNames[0]).isEqualTo("x1"); assertThat(lbf.containsSingleton("x1")).isFalse(); assertThat(lbf.containsBean("x1")).isTrue(); @@ -433,7 +430,7 @@ void nonStaticPrototypeFactoryMethodFoundByNonEagerTypeMatching() { assertThat(lbf.isTypeMatch("x1", Object.class)).isTrue(); assertThat(lbf.isTypeMatch("&x1", Object.class)).isFalse(); assertThat(lbf.getType("x1")).isEqualTo(TestBean.class); - assertThat(lbf.getType("&x1")).isEqualTo(null); + assertThat(lbf.getType("&x1")).isNull(); assertThat(TestBeanFactory.initialized).isFalse(); lbf.registerAlias("x1", "x2"); @@ -450,7 +447,7 @@ void nonStaticPrototypeFactoryMethodFoundByNonEagerTypeMatching() { assertThat(lbf.isTypeMatch("x2", Object.class)).isTrue(); assertThat(lbf.isTypeMatch("&x2", Object.class)).isFalse(); assertThat(lbf.getType("x2")).isEqualTo(TestBean.class); - assertThat(lbf.getType("&x2")).isEqualTo(null); + assertThat(lbf.getType("&x2")).isNull(); assertThat(lbf.getAliases("x1").length).isEqualTo(1); assertThat(lbf.getAliases("x1")[0]).isEqualTo("x2"); assertThat(lbf.getAliases("&x1").length).isEqualTo(1); @@ -785,12 +782,13 @@ void canReferenceParentBeanFromChildViaAlias() { factory.registerBeanDefinition("child", childDefinition); factory.registerAlias("parent", "alias"); - TestBean child = (TestBean) factory.getBean("child"); + TestBean child = factory.getBean("child", TestBean.class); assertThat(child.getName()).isEqualTo(EXPECTED_NAME); assertThat(child.getAge()).isEqualTo(EXPECTED_AGE); - Object mergedBeanDefinition2 = factory.getMergedBeanDefinition("child"); + BeanDefinition mergedBeanDefinition1 = factory.getMergedBeanDefinition("child"); + BeanDefinition mergedBeanDefinition2 = factory.getMergedBeanDefinition("child"); - assertThat(mergedBeanDefinition2).as("Use cached merged bean definition").isEqualTo(mergedBeanDefinition2); + assertThat(mergedBeanDefinition1).as("Use cached merged bean definition").isSameAs(mergedBeanDefinition2); } @Test @@ -989,16 +987,13 @@ void customEditor() { @Test void customConverter() { GenericConversionService conversionService = new DefaultConversionService(); - conversionService.addConverter(new Converter() { - @Override - public Float convert(String source) { - try { - NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); - return nf.parse(source).floatValue(); - } - catch (ParseException ex) { - throw new IllegalArgumentException(ex); - } + conversionService.addConverter(String.class, Float.class, source -> { + try { + NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); + return nf.parse(source).floatValue(); + } + catch (ParseException ex) { + throw new IllegalArgumentException(ex); } }); lbf.setConversionService(conversionService); @@ -1013,12 +1008,9 @@ public Float convert(String source) { @Test void customEditorWithBeanReference() { - lbf.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); - registry.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, nf, true)); - } + lbf.addPropertyEditorRegistrar(registry -> { + NumberFormat nf = NumberFormat.getInstance(Locale.GERMAN); + registry.registerCustomEditor(Float.class, new CustomNumberEditor(Float.class, nf, true)); }); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("myFloat", new RuntimeBeanReference("myFloat")); @@ -1838,8 +1830,7 @@ void autowireBeanWithFactoryBeanByType() { assertThat(factoryBean).as("The FactoryBean should have been registered.").isNotNull(); FactoryBeanDependentBean bean = (FactoryBeanDependentBean) lbf.autowire(FactoryBeanDependentBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); - Object mergedBeanDefinition2 = bean.getFactoryBean(); - assertThat(mergedBeanDefinition2).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(mergedBeanDefinition2); + assertThat(bean.getFactoryBean()).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(factoryBean); } @Test @@ -2307,9 +2298,7 @@ void prototypeStringCreatedRepeatedly() { @Test void prototypeWithArrayConversionForConstructor() { - List list = new ManagedList<>(); - list.add("myName"); - list.add("myBeanName"); + List list = ManagedList.of("myName", "myBeanName"); RootBeanDefinition bd = new RootBeanDefinition(DerivedTestBean.class); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); bd.getConstructorArgumentValues().addGenericArgumentValue(list); @@ -2325,9 +2314,7 @@ void prototypeWithArrayConversionForConstructor() { @Test void prototypeWithArrayConversionForFactoryMethod() { - List list = new ManagedList<>(); - list.add("myName"); - list.add("myBeanName"); + List list = ManagedList.of("myName", "myBeanName"); RootBeanDefinition bd = new RootBeanDefinition(DerivedTestBean.class); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); bd.setFactoryMethodName("create"); @@ -2388,8 +2375,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) { BeanWithDestroyMethod.closeCount = 0; lbf.preInstantiateSingletons(); lbf.destroySingletons(); - Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount; - assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2); + assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1); } @Test @@ -2403,8 +2389,7 @@ void destroyMethodOnInnerBean() { BeanWithDestroyMethod.closeCount = 0; lbf.preInstantiateSingletons(); lbf.destroySingletons(); - Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount; - assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2); + assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(2); } @Test @@ -2419,8 +2404,7 @@ void destroyMethodOnInnerBeanAsPrototype() { BeanWithDestroyMethod.closeCount = 0; lbf.preInstantiateSingletons(); lbf.destroySingletons(); - Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount; - assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2); + assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1); } @Test @@ -2542,14 +2526,15 @@ void explicitScopeInheritanceForChildBeanDefinitions() { factory.registerBeanDefinition("child", child); AbstractBeanDefinition def = (AbstractBeanDefinition) factory.getBeanDefinition("child"); - Object mergedBeanDefinition2 = def.getScope(); - assertThat(mergedBeanDefinition2).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(mergedBeanDefinition2); + assertThat(def.getScope()).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(theChildScope); } @Test void scopeInheritanceForChildBeanDefinitions() { + String theParentScope = "bonanza!"; + RootBeanDefinition parent = new RootBeanDefinition(); - parent.setScope("bonanza!"); + parent.setScope(theParentScope); AbstractBeanDefinition child = new ChildBeanDefinition("parent"); child.setBeanClass(TestBean.class); @@ -2559,8 +2544,7 @@ void scopeInheritanceForChildBeanDefinitions() { factory.registerBeanDefinition("child", child); BeanDefinition def = factory.getMergedBeanDefinition("child"); - Object mergedBeanDefinition2 = def.getScope(); - assertThat(mergedBeanDefinition2).as("Child 'scope' not inherited").isEqualTo(mergedBeanDefinition2); + assertThat(def.getScope()).as("Child 'scope' not inherited").isEqualTo(theParentScope); } @Test @@ -2596,15 +2580,12 @@ public boolean postProcessAfterInstantiation(Object bean, String beanName) throw }); lbf.preInstantiateSingletons(); TestBean tb = (TestBean) lbf.getBean("test"); - Object mergedBeanDefinition2 = tb.getName(); - assertThat(mergedBeanDefinition2).as("Name was set on field by IAPP").isEqualTo(mergedBeanDefinition2); + assertThat(tb.getName()).as("Name was set on field by IAPP").isEqualTo(nameSetOnField); if (!skipPropertyPopulation) { - Object mergedBeanDefinition21 = tb.getAge(); - assertThat(mergedBeanDefinition21).as("Property value still set").isEqualTo(mergedBeanDefinition21); + assertThat(tb.getAge()).as("Property value still set").isEqualTo(ageSetByPropertyValue); } else { - Object mergedBeanDefinition21 = tb.getAge(); - assertThat(mergedBeanDefinition21).as("Property value was NOT set and still has default value").isEqualTo(mergedBeanDefinition21); + assertThat(tb.getAge()).as("Property value was NOT set and still has default value").isEqualTo(0); } } @@ -2628,8 +2609,8 @@ void initSecurityAwarePrototypeBean() { void containsBeanReturnsTrueEvenForAbstractBeanDefinition() { lbf.registerBeanDefinition("abs", BeanDefinitionBuilder .rootBeanDefinition(TestBean.class).setAbstract(true).getBeanDefinition()); - assertThat(lbf.containsBean("abs")).isEqualTo(true); - assertThat(lbf.containsBean("bogus")).isEqualTo(false); + assertThat(lbf.containsBean("abs")).isTrue(); + assertThat(lbf.containsBean("bogus")).isFalse(); } @Test @@ -2724,8 +2705,12 @@ public void setBeanName(String name) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } ConstructorDependency that = (ConstructorDependency) o; return spouseAge == that.spouseAge && Objects.equals(spouse, that.spouse) && @@ -3036,7 +3021,7 @@ public CustomTypeConverter(NumberFormat numberFormat) { public Object convertIfNecessary(Object value, @Nullable Class requiredType) { if (value instanceof String && Float.class.isAssignableFrom(requiredType)) { try { - return new Float(this.numberFormat.parse((String) value).floatValue()); + return this.numberFormat.parse((String) value).floatValue(); } catch (ParseException ex) { throw new TypeMismatchException(value, requiredType, ex); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java b/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java index 5b82ef031ff..846f6f6696c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.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. @@ -31,6 +31,7 @@ * invoking a factory method is not instructive to the user and rather misleading. * * @author Chris Beams + * @author Juergen Hoeller */ public class Spr5475Tests { @@ -40,7 +41,8 @@ public void noArgFactoryMethodInvokedWithOneArg() { rootBeanDefinition(Foo.class) .setFactoryMethod("noArgFactory") .addConstructorArgValue("bogusArg").getBeanDefinition(), - "Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String)'. " + + "Error creating bean with name 'foo': No matching factory method found on class " + + "[org.springframework.beans.factory.Spr5475Tests$Foo]: factory method 'noArgFactory(String)'. " + "Check that a method with the specified name and arguments exists and that it is static."); } @@ -51,7 +53,8 @@ public void noArgFactoryMethodInvokedWithTwoArgs() { .setFactoryMethod("noArgFactory") .addConstructorArgValue("bogusArg1") .addConstructorArgValue("bogusArg2".getBytes()).getBeanDefinition(), - "Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String,byte[])'. " + + "Error creating bean with name 'foo': No matching factory method found on class " + + "[org.springframework.beans.factory.Spr5475Tests$Foo]: factory method 'noArgFactory(String,byte[])'. " + "Check that a method with the specified name and arguments exists and that it is static."); } @@ -65,7 +68,8 @@ public void noArgFactoryMethodInvokedWithTwoArgsAndTypesSpecified() { def.setConstructorArgumentValues(cav); assertExceptionMessageForMisconfiguredFactoryMethod(def, - "Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " + + "Error creating bean with name 'foo': No matching factory method found on class " + + "[org.springframework.beans.factory.Spr5475Tests$Foo]: factory method 'noArgFactory(CharSequence,byte[])'. " + "Check that a method with the specified name and arguments exists and that it is static."); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 0493c7f54fc..b2ad93854e2 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; @@ -297,6 +296,121 @@ public void testOptionalResourceInjection() { assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); } + @Test + public void testOptionalResourceInjectionWithSingletonRemoval() { + RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class); + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", rbd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + IndexedTestBean itb = new IndexedTestBean(); + bf.registerSingleton("indexedTestBean", itb); + NestedTestBean ntb1 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + NestedTestBean ntb2 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.destroySingleton("testBean"); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNull(); + assertThat(bean.getTestBean2()).isNull(); + assertThat(bean.getTestBean3()).isNull(); + assertThat(bean.getTestBean4()).isNull(); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.registerSingleton("testBean", tb); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + } + + @Test + public void testOptionalResourceInjectionWithBeanDefinitionRemoval() { + RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class); + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", rbd); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + IndexedTestBean itb = new IndexedTestBean(); + bf.registerSingleton("indexedTestBean", itb); + NestedTestBean ntb1 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + NestedTestBean ntb2 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.removeBeanDefinition("testBean"); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNull(); + assertThat(bean.getTestBean2()).isNull(); + assertThat(bean.getTestBean3()).isNull(); + assertThat(bean.getTestBean4()).isNull(); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + } + @Test public void testOptionalCollectionResourceInjection() { RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class); @@ -533,6 +647,83 @@ public void testConstructorResourceInjection() { assertThat(bean.getBeanFactory()).isSameAs(bf); } + @Test + public void testConstructorResourceInjectionWithSingletonRemoval() { + RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(ntb); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.destroySingleton("nestedTestBean"); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isNull(); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.registerSingleton("nestedTestBean", ntb); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(ntb); + assertThat(bean.getBeanFactory()).isSameAs(bf); + } + + @Test + public void testConstructorResourceInjectionWithBeanDefinitionRemoval() { + RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class)); + + ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean")); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.removeBeanDefinition("nestedTestBean"); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isNull(); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class)); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean")); + assertThat(bean.getBeanFactory()).isSameAs(bf); + } + @Test public void testConstructorResourceInjectionWithNullFromFactoryBean() { RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class); @@ -3468,11 +3659,8 @@ public static class MocksControl { @SuppressWarnings("unchecked") public T createMock(Class toMock) { return (T) Proxy.newProxyInstance(AutowiredAnnotationBeanPostProcessorTests.class.getClassLoader(), new Class[] {toMock}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - throw new UnsupportedOperationException("mocked!"); - } + (InvocationHandler) (proxy, method, args) -> { + throw new UnsupportedOperationException("mocked!"); }); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java index bd30b5b44c2..afa61bd9a60 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -121,6 +121,18 @@ public void testWithNullBean() { assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); } + @Test + public void testWithGenericBean() { + beanFactory.registerBeanDefinition("numberBean", new RootBeanDefinition(NumberBean.class)); + beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class)); + beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class)); + + NumberBean bean = (NumberBean) beanFactory.getBean("numberBean"); + assertThat(bean).isNotNull(); + assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore()); + assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore()); + } + public static abstract class AbstractBean { @@ -147,4 +159,26 @@ public static class BeanConsumer { AbstractBean abstractBean; } + + public static class NumberStore { + } + + + public static class DoubleStore extends NumberStore { + } + + + public static class FloatStore extends NumberStore { + } + + + public static abstract class NumberBean { + + @Lookup + public abstract NumberStore getDoubleStore(); + + @Lookup + public abstract NumberStore getFloatStore(); + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java index a94425ccf1a..48a87a9a773 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/CustomEditorConfigurerTests.java @@ -29,7 +29,6 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.propertyeditors.CustomDateEditor; @@ -50,12 +49,7 @@ public void testCustomEditorConfigurerWithPropertyEditorRegistrar() throws Parse CustomEditorConfigurer cec = new CustomEditorConfigurer(); final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.GERMAN); cec.setPropertyEditorRegistrars(new PropertyEditorRegistrar[] { - new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Date.class, new CustomDateEditor(df, true)); - } - }}); + registry -> registry.registerCustomEditor(Date.class, new CustomDateEditor(df, true))}); cec.postProcessBeanFactory(bf); MutablePropertyValues pvs = new MutablePropertyValues(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java index fae712817bd..810b2477833 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/MethodInvokingFactoryBeanTests.java @@ -113,7 +113,7 @@ public void testGetObjectType() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello"); mcfb.afterPropertiesSet(); mcfb.getObjectType(); @@ -184,7 +184,7 @@ public void testGetObject() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello"); // should pass mcfb.afterPropertiesSet(); } @@ -194,7 +194,7 @@ public void testArgumentConversion() throws Exception { MethodInvokingFactoryBean mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello", "bogus"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello", "bogus"); assertThatExceptionOfType(NoSuchMethodException.class).as( "Matched method with wrong number of args").isThrownBy( mcfb::afterPropertiesSet); @@ -210,14 +210,14 @@ public void testArgumentConversion() throws Exception { mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes2"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), "hello", "bogus"); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), "hello", "bogus"); mcfb.afterPropertiesSet(); assertThat(mcfb.getObject()).isEqualTo("hello"); mcfb = new MethodInvokingFactoryBean(); mcfb.setTargetClass(TestClass1.class); mcfb.setTargetMethod("supertypes2"); - mcfb.setArguments(new ArrayList<>(), new ArrayList(), new Object()); + mcfb.setArguments(new ArrayList<>(), new ArrayList<>(), new Object()); assertThatExceptionOfType(NoSuchMethodException.class).as( "Matched method when shouldn't have matched").isThrownBy( mcfb::afterPropertiesSet); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java index 5dd76865de4..e51017df5d5 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyResourceConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.beans.factory.config; +import java.util.AbstractMap.SimpleEntry; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -111,7 +112,7 @@ public void testPropertyOverrideConfigurer() { assertThat(tb1.getAge()).isEqualTo(99); assertThat(tb2.getAge()).isEqualTo(99); - assertThat(tb1.getName()).isEqualTo(null); + assertThat(tb1.getName()).isNull(); assertThat(tb2.getName()).isEqualTo("test"); } @@ -310,7 +311,7 @@ public void testPropertyOverrideConfigurerWithIgnoreInvalidKeys() { TestBean tb2 = (TestBean) factory.getBean("tb2"); assertThat(tb1.getAge()).isEqualTo(99); assertThat(tb2.getAge()).isEqualTo(99); - assertThat(tb1.getName()).isEqualTo(null); + assertThat(tb1.getName()).isNull(); assertThat(tb2.getName()).isEqualTo("test"); } @@ -357,22 +358,18 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("stringArray", new String[] {"${os.name}", "${age}"}); - List friends = new ManagedList<>(); - friends.add("na${age}me"); - friends.add(new RuntimeBeanReference("${ref}")); + List friends = ManagedList.of("na${age}me", new RuntimeBeanReference("${ref}")); pvs.add("friends", friends); - Set someSet = new ManagedSet<>(); - someSet.add("na${age}me"); - someSet.add(new RuntimeBeanReference("${ref}")); - someSet.add(new TypedStringValue("${age}", Integer.class)); + Set someSet = ManagedSet.of("na${age}me", + new RuntimeBeanReference("${ref}"), new TypedStringValue("${age}", Integer.class)); pvs.add("someSet", someSet); - Map someMap = new ManagedMap<>(); - someMap.put(new TypedStringValue("key${age}"), new TypedStringValue("${age}")); - someMap.put(new TypedStringValue("key${age}ref"), new RuntimeBeanReference("${ref}")); - someMap.put("key1", new RuntimeBeanReference("${ref}")); - someMap.put("key2", "${age}name"); + Map someMap = ManagedMap.ofEntries( + new SimpleEntry<>(new TypedStringValue("key${age}"), new TypedStringValue("${age}")), + new SimpleEntry<>(new TypedStringValue("key${age}ref"), new RuntimeBeanReference("${ref}")), + new SimpleEntry<>("key1", new RuntimeBeanReference("${ref}")), + new SimpleEntry<>("key2", "${age}name")); MutablePropertyValues innerPvs = new MutablePropertyValues(); innerPvs.add("country", "${os.name}"); RootBeanDefinition innerBd = new RootBeanDefinition(TestBean.class); @@ -423,7 +420,7 @@ private void doTestPropertyPlaceholderConfigurer(boolean parentChildSeparation) TestBean inner1 = (TestBean) tb2.getSomeMap().get("key3"); TestBean inner2 = (TestBean) tb2.getSomeMap().get("mykey4"); assertThat(inner1.getAge()).isEqualTo(0); - assertThat(inner1.getName()).isEqualTo(null); + assertThat(inner1.getName()).isNull(); assertThat(inner1.getCountry()).isEqualTo(System.getProperty("os.name")); assertThat(inner2.getAge()).isEqualTo(98); assertThat(inner2.getName()).isEqualTo("namemyvarmyvar${"); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java index 60fbd272ce2..edb1f994f33 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -17,9 +17,11 @@ package org.springframework.beans.factory.config; import java.net.URL; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; import org.yaml.snakeyaml.constructor.ConstructorException; @@ -28,7 +30,6 @@ import org.springframework.core.io.ByteArrayResource; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.entry; @@ -39,10 +40,12 @@ * @author Dave Syer * @author Juergen Hoeller * @author Sam Brannen + * @author Brian Clozel */ class YamlProcessorTests { - private final YamlProcessor processor = new YamlProcessor() {}; + private final YamlProcessor processor = new YamlProcessor() { + }; @Test @@ -79,8 +82,8 @@ void badDocumentStart() { void badResource() { setYaml("foo: bar\ncd\nspam:\n foo: baz"); assertThatExceptionOfType(ScannerException.class) - .isThrownBy(() -> this.processor.process((properties, map) -> {})) - .withMessageContaining("line 3, column 1"); + .isThrownBy(() -> this.processor.process((properties, map) -> {})) + .withMessageContaining("line 3, column 1"); } @Test @@ -127,8 +130,8 @@ void flattenedMapIsSameAsPropertiesButOrdered() { Map bar = (Map) map.get("bar"); assertThat(bar.get("spam")).isEqualTo("bucket"); - List keysFromProperties = properties.keySet().stream().collect(toList()); - List keysFromFlattenedMap = flattenedMap.keySet().stream().collect(toList()); + List keysFromProperties = new ArrayList<>(properties.keySet()); + List keysFromFlattenedMap = new ArrayList<>(flattenedMap.keySet()); assertThat(keysFromProperties).containsExactlyInAnyOrderElementsOf(keysFromFlattenedMap); // Keys in the Properties object are sorted. assertThat(keysFromProperties).containsExactly("bar.spam", "cat", "foo"); @@ -138,16 +141,26 @@ void flattenedMapIsSameAsPropertiesButOrdered() { } @Test - void customTypeSupportedByDefault() throws Exception { - URL url = new URL("https://localhost:9000/"); - setYaml("value: !!java.net.URL [\"" + url + "\"]"); - + @SuppressWarnings("unchecked") + void standardTypesSupportedByDefault() throws Exception { + setYaml("value: !!set\n ? first\n ? second"); this.processor.process((properties, map) -> { - assertThat(properties).containsExactly(entry("value", url)); - assertThat(map).containsExactly(entry("value", url)); + assertThat(properties).containsExactly(entry("value[0]", "first"), entry("value[1]", "second")); + assertThat(map.get("value")).isInstanceOf(Set.class); + Set set = (Set) map.get("value"); + assertThat(set).containsExactly("first", "second"); }); } + @Test + void customTypeNotSupportedByDefault() throws Exception { + URL url = new URL("https://localhost:9000/"); + setYaml("value: !!java.net.URL [\"" + url + "\"]"); + assertThatExceptionOfType(ConstructorException.class) + .isThrownBy(() -> this.processor.process((properties, map) -> {})) + .withMessageContaining("Unsupported type encountered in YAML document: java.net.URL"); + } + @Test void customTypesSupportedDueToExplicitConfiguration() throws Exception { this.processor.setSupportedTypes(URL.class, String.class); @@ -168,8 +181,8 @@ void customTypeNotSupportedDueToExplicitConfiguration() { setYaml("value: !!java.net.URL [\"https://localhost:9000//"]"); assertThatExceptionOfType(ConstructorException.class) - .isThrownBy(() -> this.processor.process((properties, map) -> {})) - .withMessageContaining("Unsupported type encountered in YAML document: java.net.URL"); + .isThrownBy(() -> this.processor.process((properties, map) -> {})) + .withMessageContaining("Unsupported type encountered in YAML document: java.net.URL"); } private void setYaml(String yaml) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionBuilderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionBuilderTests.java index ec157df512c..7a6b938ea1c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionBuilderTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionBuilderTests.java @@ -17,22 +17,25 @@ package org.springframework.beans.factory.support; import java.util.Arrays; +import java.util.function.Function; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rod Johnson * @author Juergen Hoeller + * @author Stephane Nicoll */ -public class BeanDefinitionBuilderTests { +class BeanDefinitionBuilderTests { @Test - public void beanClassWithSimpleProperty() { + void builderWithBeanClassWithSimpleProperty() { String[] dependsOn = new String[] { "A", "B", "C" }; BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class); bdb.setScope(BeanDefinition.SCOPE_PROTOTYPE); @@ -49,7 +52,7 @@ public void beanClassWithSimpleProperty() { } @Test - public void beanClassWithFactoryMethod() { + void builderWithBeanClassAndFactoryMethod() { BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class, "create"); RootBeanDefinition rbd = (RootBeanDefinition) bdb.getBeanDefinition(); assertThat(rbd.hasBeanClass()).isTrue(); @@ -58,7 +61,7 @@ public void beanClassWithFactoryMethod() { } @Test - public void beanClassName() { + void builderWithBeanClassName() { BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class.getName()); RootBeanDefinition rbd = (RootBeanDefinition) bdb.getBeanDefinition(); assertThat(rbd.hasBeanClass()).isFalse(); @@ -66,7 +69,7 @@ public void beanClassName() { } @Test - public void beanClassNameWithFactoryMethod() { + void builderWithBeanClassNameAndFactoryMethod() { BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class.getName(), "create"); RootBeanDefinition rbd = (RootBeanDefinition) bdb.getBeanDefinition(); assertThat(rbd.hasBeanClass()).isFalse(); @@ -74,4 +77,78 @@ public void beanClassNameWithFactoryMethod() { assertThat(rbd.getFactoryMethodName()).isEqualTo("create"); } + @Test + void builderWithResolvableTypeAndInstanceSupplier() { + ResolvableType type = ResolvableType.forClassWithGenerics(Function.class, Integer.class, String.class); + Function function = i -> "value " + i; + RootBeanDefinition rbd = (RootBeanDefinition) BeanDefinitionBuilder + .rootBeanDefinition(type, () -> function).getBeanDefinition(); + assertThat(rbd.getResolvableType()).isEqualTo(type); + assertThat(rbd.getInstanceSupplier()).isNotNull(); + assertThat(rbd.getInstanceSupplier().get()).isInstanceOf(Function.class); + } + + @Test + void builderWithBeanClassAndInstanceSupplier() { + RootBeanDefinition rbd = (RootBeanDefinition) BeanDefinitionBuilder + .rootBeanDefinition(String.class, () -> "test").getBeanDefinition(); + assertThat(rbd.getResolvableType().resolve()).isEqualTo(String.class); + assertThat(rbd.getInstanceSupplier()).isNotNull(); + assertThat(rbd.getInstanceSupplier().get()).isEqualTo("test"); + } + + @Test + void builderWithAutowireMode() { + assertThat(BeanDefinitionBuilder.rootBeanDefinition(TestBean.class) + .setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE).getBeanDefinition().getAutowireMode()) + .isEqualTo(RootBeanDefinition.AUTOWIRE_BY_TYPE); + } + + @Test + void builderWithDependencyCheck() { + assertThat(BeanDefinitionBuilder.rootBeanDefinition(TestBean.class) + .setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_ALL) + .getBeanDefinition().getDependencyCheck()) + .isEqualTo(RootBeanDefinition.DEPENDENCY_CHECK_ALL); + } + + @Test + void builderWithDependsOn() { + assertThat(BeanDefinitionBuilder.rootBeanDefinition(TestBean.class).addDependsOn("test") + .addDependsOn("test2").getBeanDefinition().getDependsOn()) + .containsExactly("test", "test2"); + } + + @Test + void builderWithPrimary() { + assertThat(BeanDefinitionBuilder.rootBeanDefinition(TestBean.class) + .setPrimary(true).getBeanDefinition().isPrimary()).isTrue(); + } + + @Test + void builderWithRole() { + assertThat(BeanDefinitionBuilder.rootBeanDefinition(TestBean.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition().getRole()) + .isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE); + } + + @Test + void builderWithSynthetic() { + assertThat(BeanDefinitionBuilder.rootBeanDefinition(TestBean.class) + .setSynthetic(true).getBeanDefinition().isSynthetic()).isTrue(); + } + + @Test + void builderWithCustomizers() { + BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class) + .applyCustomizers(builder -> { + builder.setFactoryMethodName("create"); + builder.setRole(BeanDefinition.ROLE_SUPPORT); + }) + .applyCustomizers(builder -> builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)) + .getBeanDefinition(); + assertThat(beanDefinition.getFactoryMethodName()).isEqualTo("create"); + assertThat(beanDefinition.getRole()).isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE); + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java index 88dc51e8b09..de770309e05 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -133,6 +133,16 @@ public void genericBeanDefinitionEquality() { assertThat(bd.equals(otherBd)).isTrue(); assertThat(otherBd.equals(bd)).isTrue(); assertThat(bd.hashCode() == otherBd.hashCode()).isTrue(); + + bd.getPropertyValues(); + assertThat(bd.equals(otherBd)).isTrue(); + assertThat(otherBd.equals(bd)).isTrue(); + assertThat(bd.hashCode() == otherBd.hashCode()).isTrue(); + + bd.getConstructorArgumentValues(); + assertThat(bd.equals(otherBd)).isTrue(); + assertThat(otherBd.equals(bd)).isTrue(); + assertThat(bd.hashCode() == otherBd.hashCode()).isTrue(); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index e3ba7b5d93f..ef53180c6a6 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -17,7 +17,6 @@ package org.springframework.beans.factory.support; import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; @@ -34,8 +33,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.springframework.beans.PropertyEditorRegistrar; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; @@ -64,10 +61,10 @@ * @author Sam Brannen * @since 20.01.2006 */ -public class BeanFactoryGenericsTests { +class BeanFactoryGenericsTests { @Test - public void testGenericSetProperty() { + void testGenericSetProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -84,7 +81,7 @@ public void testGenericSetProperty() { } @Test - public void testGenericListProperty() throws Exception { + void testGenericListProperty() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -101,7 +98,7 @@ public void testGenericListProperty() throws Exception { } @Test - public void testGenericListPropertyWithAutowiring() throws Exception { + void testGenericListPropertyWithAutowiring() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerSingleton("resource1", new UrlResource("http://localhost:8080/")); bf.registerSingleton("resource2", new UrlResource("http://localhost:9090/")); @@ -116,7 +113,7 @@ public void testGenericListPropertyWithAutowiring() throws Exception { } @Test - public void testGenericListPropertyWithInvalidElementType() { + void testGenericListPropertyWithInvalidElementType() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); @@ -134,7 +131,7 @@ public void testGenericListPropertyWithInvalidElementType() { } @Test - public void testGenericListPropertyWithOptionalAutowiring() { + void testGenericListPropertyWithOptionalAutowiring() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -146,7 +143,7 @@ public void testGenericListPropertyWithOptionalAutowiring() { } @Test - public void testGenericMapProperty() { + void testGenericMapProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -158,12 +155,12 @@ public void testGenericMapProperty() { bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericListOfArraysProperty() { + void testGenericListOfArraysProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -171,14 +168,14 @@ public void testGenericListOfArraysProperty() { assertThat(gb.getListOfArrays().size()).isEqualTo(1); String[] array = gb.getListOfArrays().get(0); - assertThat(array.length).isEqualTo(2); + assertThat(array).hasSize(2); assertThat(array[0]).isEqualTo("value1"); assertThat(array[1]).isEqualTo("value2"); } @Test - public void testGenericSetConstructor() { + void testGenericSetConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -195,7 +192,7 @@ public void testGenericSetConstructor() { } @Test - public void testGenericSetConstructorWithAutowiring() { + void testGenericSetConstructorWithAutowiring() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerSingleton("integer1", 4); bf.registerSingleton("integer2", 5); @@ -210,7 +207,7 @@ public void testGenericSetConstructorWithAutowiring() { } @Test - public void testGenericSetConstructorWithOptionalAutowiring() { + void testGenericSetConstructorWithOptionalAutowiring() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -222,7 +219,7 @@ public void testGenericSetConstructorWithOptionalAutowiring() { } @Test - public void testGenericSetListConstructor() throws Exception { + void testGenericSetListConstructor() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -245,7 +242,7 @@ public void testGenericSetListConstructor() throws Exception { } @Test - public void testGenericSetListConstructorWithAutowiring() throws Exception { + void testGenericSetListConstructorWithAutowiring() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerSingleton("integer1", 4); bf.registerSingleton("integer2", 5); @@ -264,7 +261,7 @@ public void testGenericSetListConstructorWithAutowiring() throws Exception { } @Test - public void testGenericSetListConstructorWithOptionalAutowiring() throws Exception { + void testGenericSetListConstructorWithOptionalAutowiring() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerSingleton("resource1", new UrlResource("http://localhost:8080/")); bf.registerSingleton("resource2", new UrlResource("http://localhost:9090/")); @@ -279,7 +276,7 @@ public void testGenericSetListConstructorWithOptionalAutowiring() throws Excepti } @Test - public void testGenericSetMapConstructor() { + void testGenericSetMapConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -297,12 +294,12 @@ public void testGenericSetMapConstructor() { assertThat(gb.getIntegerSet().contains(4)).isTrue(); assertThat(gb.getIntegerSet().contains(5)).isTrue(); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericMapResourceConstructor() throws Exception { + void testGenericMapResourceConstructor() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -315,13 +312,13 @@ public void testGenericMapResourceConstructor() throws Exception { bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); assertThat(gb.getResourceList().get(0)).isEqualTo(new UrlResource("http://localhost:8080/")); } @Test - public void testGenericMapMapConstructor() { + void testGenericMapMapConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -338,16 +335,16 @@ public void testGenericMapMapConstructor() { GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getShortMap()).isNotSameAs(gb.getPlainMap()); - assertThat(gb.getPlainMap().size()).isEqualTo(2); + assertThat(gb.getPlainMap()).hasSize(2); assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); assertThat(gb.getPlainMap().get("2")).isEqualTo("3"); assertThat(gb.getShortMap().size()).isEqualTo(2); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericMapMapConstructorWithSameRefAndConversion() { + void testGenericMapMapConstructorWithSameRefAndConversion() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -361,22 +358,22 @@ public void testGenericMapMapConstructorWithSameRefAndConversion() { GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getShortMap()).isNotSameAs(gb.getPlainMap()); - assertThat(gb.getPlainMap().size()).isEqualTo(2); + assertThat(gb.getPlainMap()).hasSize(2); assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); assertThat(gb.getPlainMap().get("2")).isEqualTo("3"); assertThat(gb.getShortMap().size()).isEqualTo(2); - assertThat(gb.getShortMap().get(new Short("1"))).isEqualTo(0); - assertThat(gb.getShortMap().get(new Short("2"))).isEqualTo(3); + assertThat(gb.getShortMap().get(Short.valueOf("1"))).isEqualTo(0); + assertThat(gb.getShortMap().get(Short.valueOf("2"))).isEqualTo(3); } @Test - public void testGenericMapMapConstructorWithSameRefAndNoConversion() { + void testGenericMapMapConstructorWithSameRefAndNoConversion() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); Map input = new HashMap<>(); - input.put(new Short((short) 1), 0); - input.put(new Short((short) 2), 3); + input.put((short) 1, 0); + input.put((short) 2, 3); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); rbd.getConstructorArgumentValues().addGenericArgumentValue(input); @@ -384,13 +381,13 @@ public void testGenericMapMapConstructorWithSameRefAndNoConversion() { GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getShortMap()).isSameAs(gb.getPlainMap()); - assertThat(gb.getShortMap().size()).isEqualTo(2); - assertThat(gb.getShortMap().get(new Short("1"))).isEqualTo(0); - assertThat(gb.getShortMap().get(new Short("2"))).isEqualTo(3); + assertThat(gb.getShortMap()).hasSize(2); + assertThat(gb.getShortMap().get(Short.valueOf("1"))).isEqualTo(0); + assertThat(gb.getShortMap().get(Short.valueOf("2"))).isEqualTo(3); } @Test - public void testGenericMapWithKeyTypeConstructor() { + void testGenericMapWithKeyTypeConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); @@ -407,14 +404,9 @@ public void testGenericMapWithKeyTypeConstructor() { } @Test - public void testGenericMapWithCollectionValueConstructor() { + void testGenericMapWithCollectionValueConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)); - } - }); + bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); Map> input = new HashMap<>(); @@ -438,7 +430,7 @@ public void registerCustomEditors(PropertyEditorRegistry registry) { @Test - public void testGenericSetFactoryMethod() { + void testGenericSetFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -456,7 +448,7 @@ public void testGenericSetFactoryMethod() { } @Test - public void testGenericSetListFactoryMethod() throws Exception { + void testGenericSetListFactoryMethod() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -480,7 +472,7 @@ public void testGenericSetListFactoryMethod() throws Exception { } @Test - public void testGenericSetMapFactoryMethod() { + void testGenericSetMapFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -499,12 +491,12 @@ public void testGenericSetMapFactoryMethod() { assertThat(gb.getIntegerSet().contains(4)).isTrue(); assertThat(gb.getIntegerSet().contains(5)).isTrue(); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericMapResourceFactoryMethod() throws Exception { + void testGenericMapResourceFactoryMethod() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -518,13 +510,13 @@ public void testGenericMapResourceFactoryMethod() throws Exception { bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); assertThat(gb.getResourceList().get(0)).isEqualTo(new UrlResource("http://localhost:8080/")); } @Test - public void testGenericMapMapFactoryMethod() { + void testGenericMapMapFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -543,12 +535,12 @@ public void testGenericMapMapFactoryMethod() { assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); assertThat(gb.getPlainMap().get("2")).isEqualTo("3"); - assertThat(gb.getShortMap().get(new Short("4"))).isEqualTo(5); - assertThat(gb.getShortMap().get(new Short("6"))).isEqualTo(7); + assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); + assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @Test - public void testGenericMapWithKeyTypeFactoryMethod() { + void testGenericMapWithKeyTypeFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -561,19 +553,14 @@ public void testGenericMapWithKeyTypeFactoryMethod() { bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getLongMap().get(new Long("4"))).isEqualTo("5"); - assertThat(gb.getLongMap().get(new Long("6"))).isEqualTo("7"); + assertThat(gb.getLongMap().get(Long.valueOf("4"))).isEqualTo("5"); + assertThat(gb.getLongMap().get(Long.valueOf("6"))).isEqualTo("7"); } @Test - public void testGenericMapWithCollectionValueFactoryMethod() { + void testGenericMapWithCollectionValueFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { - @Override - public void registerCustomEditors(PropertyEditorRegistry registry) { - registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false)); - } - }); + bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); rbd.setFactoryMethodName("createInstance"); @@ -597,7 +584,7 @@ public void registerCustomEditors(PropertyEditorRegistry registry) { } @Test - public void testGenericListBean() throws Exception { + void testGenericListBean() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -607,7 +594,7 @@ public void testGenericListBean() throws Exception { } @Test - public void testGenericSetBean() throws Exception { + void testGenericSetBean() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -617,18 +604,18 @@ public void testGenericSetBean() throws Exception { } @Test - public void testGenericMapBean() throws Exception { + void testGenericMapBean() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); Map map = (Map) bf.getBean("map"); - assertThat(map.size()).isEqualTo(1); + assertThat(map).hasSize(1); assertThat(map.keySet().iterator().next()).isEqualTo(10); assertThat(map.values().iterator().next()).isEqualTo(new URL("http://localhost:8080/")); } @Test - public void testGenericallyTypedIntegerBean() { + void testGenericallyTypedIntegerBean() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -639,7 +626,7 @@ public void testGenericallyTypedIntegerBean() { } @Test - public void testGenericallyTypedSetOfIntegerBean() { + void testGenericallyTypedSetOfIntegerBean() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -651,7 +638,7 @@ public void testGenericallyTypedSetOfIntegerBean() { @Test @EnabledForTestGroups(LONG_RUNNING) - public void testSetBean() throws Exception { + void testSetBean() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); @@ -671,7 +658,7 @@ public void testSetBean() throws Exception { *

    See SPR-9493 */ @Test - public void parameterizedStaticFactoryMethod() { + void parameterizedStaticFactoryMethod() { RootBeanDefinition rbd = new RootBeanDefinition(Mockito.class); rbd.setFactoryMethodName("mock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); @@ -682,7 +669,7 @@ public void parameterizedStaticFactoryMethod() { assertThat(bf.getType("mock")).isEqualTo(Runnable.class); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); } /** @@ -697,7 +684,7 @@ public void parameterizedStaticFactoryMethod() { *

    See SPR-10411 */ @Test - public void parameterizedInstanceFactoryMethod() { + void parameterizedInstanceFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); @@ -714,11 +701,11 @@ public void parameterizedInstanceFactoryMethod() { assertThat(bf.getType("mock")).isEqualTo(Runnable.class); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); } @Test - public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { + void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); @@ -735,11 +722,11 @@ public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { assertThat(bf.getType("mock")).isEqualTo(Runnable.class); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); } @Test - public void parameterizedInstanceFactoryMethodWithWrappedClassName() { + void parameterizedInstanceFactoryMethodWithWrappedClassName() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(); @@ -754,11 +741,11 @@ public void parameterizedInstanceFactoryMethodWithWrappedClassName() { assertThat(bf.getType("mock")).isEqualTo(Runnable.class); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); } @Test - public void parameterizedInstanceFactoryMethodWithInvalidClassName() { + void parameterizedInstanceFactoryMethodWithInvalidClassName() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); @@ -775,11 +762,11 @@ public void parameterizedInstanceFactoryMethodWithInvalidClassName() { assertThat(bf.getType("mock")).isNull(); assertThat(bf.getType("mock")).isNull(); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(0); + assertThat(beans).hasSize(0); } @Test - public void parameterizedInstanceFactoryMethodWithIndexedArgument() { + void parameterizedInstanceFactoryMethodWithIndexedArgument() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); @@ -796,11 +783,11 @@ public void parameterizedInstanceFactoryMethodWithIndexedArgument() { assertThat(bf.getType("mock")).isEqualTo(Runnable.class); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); } @Test // SPR-16720 - public void parameterizedInstanceFactoryMethodWithTempClassLoader() { + void parameterizedInstanceFactoryMethodWithTempClassLoader() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setTempClassLoader(new OverridingClassLoader(getClass().getClassLoader())); @@ -818,11 +805,11 @@ public void parameterizedInstanceFactoryMethodWithTempClassLoader() { assertThat(bf.getType("mock")).isEqualTo(Runnable.class); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); Map beans = bf.getBeansOfType(Runnable.class); - assertThat(beans.size()).isEqualTo(1); + assertThat(beans).hasSize(1); } @Test - public void testGenericMatchingWithBeanNameDifferentiation() { + void testGenericMatchingWithBeanNameDifferentiation() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); @@ -838,15 +825,15 @@ public void testGenericMatchingWithBeanNameDifferentiation() { String[] numberStoreNames = bf.getBeanNamesForType(ResolvableType.forClass(NumberStore.class)); String[] doubleStoreNames = bf.getBeanNamesForType(ResolvableType.forClassWithGenerics(NumberStore.class, Double.class)); String[] floatStoreNames = bf.getBeanNamesForType(ResolvableType.forClassWithGenerics(NumberStore.class, Float.class)); - assertThat(numberStoreNames.length).isEqualTo(2); + assertThat(numberStoreNames).hasSize(2); assertThat(numberStoreNames[0]).isEqualTo("doubleStore"); assertThat(numberStoreNames[1]).isEqualTo("floatStore"); - assertThat(doubleStoreNames.length).isEqualTo(0); - assertThat(floatStoreNames.length).isEqualTo(0); + assertThat(doubleStoreNames).hasSize(0); + assertThat(floatStoreNames).hasSize(0); } @Test - public void testGenericMatchingWithFullTypeDifferentiation() { + void testGenericMatchingWithFullTypeDifferentiation() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); @@ -867,12 +854,12 @@ public void testGenericMatchingWithFullTypeDifferentiation() { String[] numberStoreNames = bf.getBeanNamesForType(ResolvableType.forClass(NumberStore.class)); String[] doubleStoreNames = bf.getBeanNamesForType(ResolvableType.forClassWithGenerics(NumberStore.class, Double.class)); String[] floatStoreNames = bf.getBeanNamesForType(ResolvableType.forClassWithGenerics(NumberStore.class, Float.class)); - assertThat(numberStoreNames.length).isEqualTo(2); + assertThat(numberStoreNames).hasSize(2); assertThat(numberStoreNames[0]).isEqualTo("store1"); assertThat(numberStoreNames[1]).isEqualTo("store2"); - assertThat(doubleStoreNames.length).isEqualTo(1); + assertThat(doubleStoreNames).hasSize(1); assertThat(doubleStoreNames[0]).isEqualTo("store1"); - assertThat(floatStoreNames.length).isEqualTo(1); + assertThat(floatStoreNames).hasSize(1); assertThat(floatStoreNames[0]).isEqualTo("store2"); ObjectProvider> numberStoreProvider = bf.getBeanProvider(ResolvableType.forClass(NumberStore.class)); @@ -938,7 +925,7 @@ public void testGenericMatchingWithFullTypeDifferentiation() { } @Test - public void testGenericMatchingWithUnresolvedOrderedStream() { + void testGenericMatchingWithUnresolvedOrderedStream() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); @@ -1010,11 +997,8 @@ public static class MocksControl { @SuppressWarnings("unchecked") public T createMock(Class toMock) { return (T) Proxy.newProxyInstance(BeanFactoryGenericsTests.class.getClassLoader(), new Class[] {toMock}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - throw new UnsupportedOperationException("mocked!"); - } + (InvocationHandler) (proxy, method, args) -> { + throw new UnsupportedOperationException("mocked!"); }); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java index 6e3f8465e9e..b7bc696c68d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java @@ -18,8 +18,6 @@ import org.junit.jupiter.api.Test; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.TestBean; @@ -40,12 +38,7 @@ public void testSingletons() { beanRegistry.registerSingleton("tb", tb); assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); - TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", new ObjectFactory() { - @Override - public Object getObject() throws BeansException { - return new TestBean(); - } - }); + TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", () -> new TestBean()); assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2); assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java index eeb34b6f8f1..414fd015766 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.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. @@ -36,7 +36,7 @@ public class LookupMethodTests { @BeforeEach - public void setUp() { + public void setup() { beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass())); @@ -83,8 +83,8 @@ public void testWithTwoConstructorArg() { public void testWithThreeArgsShouldFail() { AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); assertThat(bean).isNotNull(); - assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor").isThrownBy(() -> - bean.getThreeArguments("name", 1, 2)); + assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor") + .isThrownBy(() -> bean.getThreeArguments("name", 1, 2)); } @Test @@ -97,6 +97,21 @@ public void testWithOverriddenLookupMethod() { assertThat(expected.isJedi()).isTrue(); } + @Test + public void testWithGenericBean() { + RootBeanDefinition bd = new RootBeanDefinition(NumberBean.class); + bd.getMethodOverrides().addOverride(new LookupOverride("getDoubleStore", null)); + bd.getMethodOverrides().addOverride(new LookupOverride("getFloatStore", null)); + beanFactory.registerBeanDefinition("numberBean", bd); + beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class)); + beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class)); + + NumberBean bean = (NumberBean) beanFactory.getBean("numberBean"); + assertThat(bean).isNotNull(); + assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore()); + assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore()); + } + public static abstract class AbstractBean { @@ -111,4 +126,24 @@ public static abstract class AbstractBean { public abstract TestBean getThreeArguments(String name, int age, int anotherArg); } + + public static class NumberStore { + } + + + public static class DoubleStore extends NumberStore { + } + + + public static class FloatStore extends NumberStore { + } + + + public static abstract class NumberBean { + + public abstract NumberStore getDoubleStore(); + + public abstract NumberStore getFloatStore(); + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java index 9bf6576d518..420226d173a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedListTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,11 +34,8 @@ public class ManagedListTests { @Test public void mergeSunnyDay() { - ManagedList parent = new ManagedList(); - parent.add("one"); - parent.add("two"); - ManagedList child = new ManagedList(); - child.add("three"); + ManagedList parent = ManagedList.of("one", "two"); + ManagedList child = ManagedList.of("three"); child.setMergeEnabled(true); List mergedList = child.merge(parent); assertThat(mergedList.size()).as("merge() obviously did not work.").isEqualTo(3); @@ -46,8 +43,7 @@ public void mergeSunnyDay() { @Test public void mergeWithNullParent() { - ManagedList child = new ManagedList(); - child.add("one"); + ManagedList child = ManagedList.of("one"); child.setMergeEnabled(true); assertThat(child.merge(null)).isSameAs(child); } @@ -61,8 +57,7 @@ public void mergeNotAllowedWhenMergeNotEnabled() { @Test public void mergeWithNonCompatibleParentType() { - ManagedList child = new ManagedList(); - child.add("one"); + ManagedList child = ManagedList.of("one"); child.setMergeEnabled(true); assertThatIllegalArgumentException().isThrownBy(() -> child.merge("hello")); @@ -70,9 +65,7 @@ public void mergeWithNonCompatibleParentType() { @Test public void mergeEmptyChild() { - ManagedList parent = new ManagedList(); - parent.add("one"); - parent.add("two"); + ManagedList parent = ManagedList.of("one", "two"); ManagedList child = new ManagedList(); child.setMergeEnabled(true); List mergedList = child.merge(parent); @@ -82,11 +75,8 @@ public void mergeEmptyChild() { @Test public void mergeChildValuesOverrideTheParents() { // doesn't make much sense in the context of a list... - ManagedList parent = new ManagedList(); - parent.add("one"); - parent.add("two"); - ManagedList child = new ManagedList(); - child.add("one"); + ManagedList parent = ManagedList.of("one", "two"); + ManagedList child = ManagedList.of("one"); child.setMergeEnabled(true); List mergedList = child.merge(parent); assertThat(mergedList.size()).as("merge() obviously did not work.").isEqualTo(3); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java index 1f6dc5e416a..b83fa176d31 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedMapTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.beans.factory.support; +import java.util.AbstractMap.SimpleEntry; import java.util.Map; import org.junit.jupiter.api.Test; @@ -34,11 +35,9 @@ public class ManagedMapTests { @Test public void mergeSunnyDay() { - ManagedMap parent = new ManagedMap(); - parent.put("one", "one"); - parent.put("two", "two"); - ManagedMap child = new ManagedMap(); - child.put("three", "three"); + ManagedMap parent = ManagedMap.ofEntries(new SimpleEntry<>("one", "one"), + new SimpleEntry<>("two", "two")); + ManagedMap child = ManagedMap.ofEntries(new SimpleEntry<>("tree", "three")); child.setMergeEnabled(true); Map mergedMap = (Map) child.merge(parent); assertThat(mergedMap.size()).as("merge() obviously did not work.").isEqualTo(3); @@ -67,9 +66,8 @@ public void mergeNotAllowedWhenMergeNotEnabled() { @Test public void mergeEmptyChild() { - ManagedMap parent = new ManagedMap(); - parent.put("one", "one"); - parent.put("two", "two"); + ManagedMap parent = ManagedMap.ofEntries(new SimpleEntry<>("one", "one"), + new SimpleEntry<>("two", "two")); ManagedMap child = new ManagedMap(); child.setMergeEnabled(true); Map mergedMap = (Map) child.merge(parent); @@ -78,11 +76,9 @@ public void mergeEmptyChild() { @Test public void mergeChildValuesOverrideTheParents() { - ManagedMap parent = new ManagedMap(); - parent.put("one", "one"); - parent.put("two", "two"); - ManagedMap child = new ManagedMap(); - child.put("one", "fork"); + ManagedMap parent = ManagedMap.ofEntries(new SimpleEntry<>("one", "one"), + new SimpleEntry<>("two", "two")); + ManagedMap child = ManagedMap.ofEntries(new SimpleEntry<>("one", "fork")); child.setMergeEnabled(true); Map mergedMap = (Map) child.merge(parent); // child value for 'one' must override parent value... diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java index 39c08997814..c11a63a6da8 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/ManagedSetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,10 +34,8 @@ public class ManagedSetTests { @Test public void mergeSunnyDay() { - ManagedSet parent = new ManagedSet(); - parent.add("one"); - parent.add("two"); - ManagedSet child = new ManagedSet(); + ManagedSet parent = ManagedSet.of("one", "two"); + ManagedSet child = ManagedSet.of("three"); child.add("three"); child.setMergeEnabled(true); Set mergedSet = child.merge(parent); @@ -46,8 +44,7 @@ public void mergeSunnyDay() { @Test public void mergeWithNullParent() { - ManagedSet child = new ManagedSet(); - child.add("one"); + ManagedSet child = ManagedSet.of("one"); child.setMergeEnabled(true); assertThat(child.merge(null)).isSameAs(child); } @@ -60,8 +57,7 @@ public void mergeNotAllowedWhenMergeNotEnabled() { @Test public void mergeWithNonCompatibleParentType() { - ManagedSet child = new ManagedSet(); - child.add("one"); + ManagedSet child = ManagedSet.of("one"); child.setMergeEnabled(true); assertThatIllegalArgumentException().isThrownBy(() -> child.merge("hello")); @@ -69,9 +65,7 @@ public void mergeWithNonCompatibleParentType() { @Test public void mergeEmptyChild() { - ManagedSet parent = new ManagedSet(); - parent.add("one"); - parent.add("two"); + ManagedSet parent = ManagedSet.of("one", "two"); ManagedSet child = new ManagedSet(); child.setMergeEnabled(true); Set mergedSet = child.merge(parent); @@ -81,11 +75,8 @@ public void mergeEmptyChild() { @Test public void mergeChildValuesOverrideTheParents() { // asserts that the set contract is not violated during a merge() operation... - ManagedSet parent = new ManagedSet(); - parent.add("one"); - parent.add("two"); - ManagedSet child = new ManagedSet(); - child.add("one"); + ManagedSet parent = ManagedSet.of("one", "two"); + ManagedSet child = ManagedSet.of("one"); child.setMergeEnabled(true); Set mergedSet = child.merge(parent); assertThat(mergedSet.size()).as("merge() obviously did not work.").isEqualTo(2); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java index 17fd92dd5ff..f660a8af020 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireBeanFactoryTests.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. @@ -244,7 +244,7 @@ public String getName() { @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier - private static @interface TestQualifier { + private @interface TestQualifier { } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java index 368e0b66472..a2bce0b763d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/CallbacksSecurityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -449,7 +449,7 @@ public void testTrustedExecution() throws Exception { public Object run() { // sanity check assertThat(getCurrentSubjectName()).isEqualTo("user1"); - assertThat(NonPrivilegedBean.destroyed).isEqualTo(false); + assertThat(NonPrivilegedBean.destroyed).isFalse(); beanFactory.getBean("trusted-spring-callbacks"); beanFactory.getBean("trusted-custom-init-destroy"); @@ -465,7 +465,7 @@ public Object run() { beanFactory.getBean("trusted-working-property-injection"); beanFactory.destroySingletons(); - assertThat(NonPrivilegedBean.destroyed).isEqualTo(true); + assertThat(NonPrivilegedBean.destroyed).isTrue(); return null; } }, provider.getAccessControlContext()); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java index 4874306e6e1..5f46a7b57b6 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/CustomCallbackBean.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.support.security.support; /** @@ -27,4 +28,5 @@ public void init() { public void destroy() { System.setProperty("security.destroy", "true"); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java index 67005abf783..4ec5a23a5b9 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/DestroyBean.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.support.security.support; import org.springframework.beans.factory.DisposableBean; @@ -26,4 +27,5 @@ public class DestroyBean implements DisposableBean { public void destroy() throws Exception { System.setProperty("security.destroy", "true"); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java index 4f7fb62e5be..71258d70f04 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/FactoryBean.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.support.security.support; /** @@ -33,4 +34,5 @@ public Object makeInstance() { System.getProperties(); return new Object(); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java index 3693bb9d749..b079dae5cb2 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/InitBean.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.support.security.support; import org.springframework.beans.factory.InitializingBean; @@ -26,4 +27,5 @@ public class InitBean implements InitializingBean { public void afterPropertiesSet() throws Exception { System.getProperties(); } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java index 51933137f0d..d9091163f5d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/security/support/PropertyBean.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.factory.support.security.support; /** @@ -27,4 +28,5 @@ public void setSecurityProperty(Object property) { public void setProperty(Object property) { } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolverTests.java index 67cef6f1166..becb7961f2a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolverTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/wiring/ClassNameBeanWiringInfoResolverTests.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. @@ -26,18 +26,18 @@ * * @author Rick Evans */ -public class ClassNameBeanWiringInfoResolverTests { +class ClassNameBeanWiringInfoResolverTests { @Test - public void resolveWiringInfoWithNullBeanInstance() throws Exception { + void resolveWiringInfoWithNullBeanInstance() throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> new ClassNameBeanWiringInfoResolver().resolveWiringInfo(null)); } @Test - public void resolveWiringInfo() { + void resolveWiringInfo() { ClassNameBeanWiringInfoResolver resolver = new ClassNameBeanWiringInfoResolver(); - Long beanInstance = new Long(1); + Long beanInstance = 1L; BeanWiringInfo info = resolver.resolveWiringInfo(beanInstance); assertThat(info).isNotNull(); assertThat(info.getBeanName()).as("Not resolving bean name to the class name of the supplied bean instance as per class contract.").isEqualTo(beanInstance.getClass().getName()); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java index d2f26ee0fe6..ced4e5098d8 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/FactoryMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,17 +100,17 @@ public void testFactoryMethodsWithNullValue() { FactoryMethods fm = (FactoryMethods) xbf.getBean("fullWithNull"); assertThat(fm.getNum()).isEqualTo(27); - assertThat(fm.getName()).isEqualTo(null); + assertThat(fm.getName()).isNull(); assertThat(fm.getTestBean().getName()).isEqualTo("Juergen"); fm = (FactoryMethods) xbf.getBean("fullWithGenericNull"); assertThat(fm.getNum()).isEqualTo(27); - assertThat(fm.getName()).isEqualTo(null); + assertThat(fm.getName()).isNull(); assertThat(fm.getTestBean().getName()).isEqualTo("Juergen"); fm = (FactoryMethods) xbf.getBean("fullWithNamedNull"); assertThat(fm.getNum()).isEqualTo(27); - assertThat(fm.getName()).isEqualTo(null); + assertThat(fm.getName()).isNull(); assertThat(fm.getTestBean().getName()).isEqualTo("Juergen"); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java index 2260a86c52e..083d6f9de35 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/UtilNamespaceHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -342,7 +342,7 @@ public void testNestedInConstructor() { public void testLoadProperties() { Properties props = (Properties) this.beanFactory.getBean("myProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); - assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo(null); + assertThat(props.get("foo2")).as("Incorrect property value").isNull(); Properties props2 = (Properties) this.beanFactory.getBean("myProperties"); assertThat(props == props2).isTrue(); } @@ -351,17 +351,17 @@ public void testLoadProperties() { public void testScopedProperties() { Properties props = (Properties) this.beanFactory.getBean("myScopedProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); - assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo(null); + assertThat(props.get("foo2")).as("Incorrect property value").isNull(); Properties props2 = (Properties) this.beanFactory.getBean("myScopedProperties"); assertThat(props.get("foo")).as("Incorrect property value").isEqualTo("bar"); - assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo(null); + assertThat(props.get("foo2")).as("Incorrect property value").isNull(); assertThat(props != props2).isTrue(); } @Test public void testLocalProperties() { Properties props = (Properties) this.beanFactory.getBean("myLocalProperties"); - assertThat(props.get("foo")).as("Incorrect property value").isEqualTo(null); + assertThat(props.get("foo")).as("Incorrect property value").isNull(); assertThat(props.get("foo2")).as("Incorrect property value").isEqualTo("bar2"); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java index e44f0d9df7a..b9c66fe39c8 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanCollectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -283,7 +283,7 @@ public void testPopulatedSet() throws Exception { Iterator it = hasMap.getSet().iterator(); assertThat(it.next()).isEqualTo("bar"); assertThat(it.next()).isEqualTo(jenny); - assertThat(it.next()).isEqualTo(null); + assertThat(it.next()).isNull(); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java index d48b55f2cb8..4b5c005788d 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlListableBeanFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -181,7 +181,7 @@ public void autoAliasing() { assertThat(beanNames.contains("aliasWithoutId3")).isFalse(); TestBean tb4 = (TestBean) getBeanFactory().getBean(TestBean.class.getName() + "#0"); - assertThat(tb4.getName()).isEqualTo(null); + assertThat(tb4.getName()).isNull(); Map drs = getListableBeanFactory().getBeansOfType(DummyReferencer.class, false, false); assertThat(drs.size()).isEqualTo(5); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java index 0de98a10f00..e3dddf62db2 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/CustomEditorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -64,10 +64,10 @@ * @author Chris Beams * @since 10.06.2003 */ -public class CustomEditorTests { +class CustomEditorTests { @Test - public void testComplexObject() { + void testComplexObject() { TestBean tb = new TestBean(); String newName = "Rod"; String tbString = "Kerry_34"; @@ -80,12 +80,12 @@ public void testComplexObject() { pvs.addPropertyValue(new PropertyValue("touchy", "valid")); pvs.addPropertyValue(new PropertyValue("spouse", tbString)); bw.setPropertyValues(pvs); - assertThat(tb.getSpouse() != null).as("spouse is non-null").isTrue(); + assertThat(tb.getSpouse()).as("spouse is non-null").isNotNull(); assertThat(tb.getSpouse().getName().equals("Kerry") && tb.getSpouse().getAge() == 34).as("spouse name is Kerry and age is 34").isTrue(); } @Test - public void testComplexObjectWithOldValueAccess() { + void testComplexObjectWithOldValueAccess() { TestBean tb = new TestBean(); String newName = "Rod"; String tbString = "Kerry_34"; @@ -100,7 +100,7 @@ public void testComplexObjectWithOldValueAccess() { pvs.addPropertyValue(new PropertyValue("spouse", tbString)); bw.setPropertyValues(pvs); - assertThat(tb.getSpouse() != null).as("spouse is non-null").isTrue(); + assertThat(tb.getSpouse()).as("spouse is non-null").isNotNull(); assertThat(tb.getSpouse().getName().equals("Kerry") && tb.getSpouse().getAge() == 34).as("spouse name is Kerry and age is 34").isTrue(); ITestBean spouse = tb.getSpouse(); @@ -109,7 +109,7 @@ public void testComplexObjectWithOldValueAccess() { } @Test - public void testCustomEditorForSingleProperty() { + void testCustomEditorForSingleProperty() { TestBean tb = new TestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); bw.registerCustomEditor(String.class, "name", new PropertyEditorSupport() { @@ -127,7 +127,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testCustomEditorForAllStringProperties() { + void testCustomEditorForAllStringProperties() { TestBean tb = new TestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); bw.registerCustomEditor(String.class, new PropertyEditorSupport() { @@ -145,7 +145,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testCustomEditorForSingleNestedProperty() { + void testCustomEditorForSingleNestedProperty() { TestBean tb = new TestBean(); tb.setSpouse(new TestBean()); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -164,7 +164,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testCustomEditorForAllNestedStringProperties() { + void testCustomEditorForAllNestedStringProperties() { TestBean tb = new TestBean(); tb.setSpouse(new TestBean()); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -183,7 +183,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testDefaultBooleanEditorForPrimitiveType() { + void testDefaultBooleanEditorForPrimitiveType() { BooleanTestBean tb = new BooleanTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -229,7 +229,7 @@ public void testDefaultBooleanEditorForPrimitiveType() { } @Test - public void testDefaultBooleanEditorForWrapperType() { + void testDefaultBooleanEditorForWrapperType() { BooleanTestBean tb = new BooleanTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -239,28 +239,28 @@ public void testDefaultBooleanEditorForWrapperType() { bw.setPropertyValue("bool2", "false"); assertThat(Boolean.FALSE.equals(bw.getPropertyValue("bool2"))).as("Correct bool2 value").isTrue(); - boolean condition3 = !tb.getBool2().booleanValue(); + boolean condition3 = !tb.getBool2(); assertThat(condition3).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "on"); assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "off"); - boolean condition2 = !tb.getBool2().booleanValue(); + boolean condition2 = !tb.getBool2(); assertThat(condition2).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "yes"); assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "no"); - boolean condition1 = !tb.getBool2().booleanValue(); + boolean condition1 = !tb.getBool2(); assertThat(condition1).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "1"); assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "0"); - boolean condition = !tb.getBool2().booleanValue(); + boolean condition = !tb.getBool2(); assertThat(condition).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", ""); @@ -268,7 +268,7 @@ public void testDefaultBooleanEditorForWrapperType() { } @Test - public void testCustomBooleanEditorWithAllowEmpty() { + void testCustomBooleanEditorWithAllowEmpty() { BooleanTestBean tb = new BooleanTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); bw.registerCustomEditor(Boolean.class, new CustomBooleanEditor(true)); @@ -279,37 +279,37 @@ public void testCustomBooleanEditorWithAllowEmpty() { bw.setPropertyValue("bool2", "false"); assertThat(Boolean.FALSE.equals(bw.getPropertyValue("bool2"))).as("Correct bool2 value").isTrue(); - boolean condition3 = !tb.getBool2().booleanValue(); + boolean condition3 = !tb.getBool2(); assertThat(condition3).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "on"); assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "off"); - boolean condition2 = !tb.getBool2().booleanValue(); + boolean condition2 = !tb.getBool2(); assertThat(condition2).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "yes"); assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "no"); - boolean condition1 = !tb.getBool2().booleanValue(); + boolean condition1 = !tb.getBool2(); assertThat(condition1).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "1"); assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", "0"); - boolean condition = !tb.getBool2().booleanValue(); + boolean condition = !tb.getBool2(); assertThat(condition).as("Correct bool2 value").isTrue(); bw.setPropertyValue("bool2", ""); - assertThat(bw.getPropertyValue("bool2") == null).as("Correct bool2 value").isTrue(); - assertThat(tb.getBool2() == null).as("Correct bool2 value").isTrue(); + assertThat(bw.getPropertyValue("bool2")).as("Correct bool2 value").isNull(); + assertThat(tb.getBool2()).as("Correct bool2 value").isNull(); } @Test - public void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() throws Exception { + void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() throws Exception { String trueString = "pechorin"; String falseString = "nash"; @@ -333,7 +333,7 @@ public void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() throws Excep } @Test - public void testDefaultNumberEditor() { + void testDefaultNumberEditor() { NumberTestBean tb = new NumberTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -350,34 +350,34 @@ public void testDefaultNumberEditor() { bw.setPropertyValue("double2", "6.1"); bw.setPropertyValue("bigDecimal", "4.5"); - assertThat(new Short("1").equals(bw.getPropertyValue("short1"))).as("Correct short1 value").isTrue(); + assertThat(Short.valueOf("1").equals(bw.getPropertyValue("short1"))).as("Correct short1 value").isTrue(); assertThat(tb.getShort1() == 1).as("Correct short1 value").isTrue(); - assertThat(new Short("2").equals(bw.getPropertyValue("short2"))).as("Correct short2 value").isTrue(); - assertThat(new Short("2").equals(tb.getShort2())).as("Correct short2 value").isTrue(); - assertThat(new Integer("7").equals(bw.getPropertyValue("int1"))).as("Correct int1 value").isTrue(); + assertThat(Short.valueOf("2").equals(bw.getPropertyValue("short2"))).as("Correct short2 value").isTrue(); + assertThat(Short.valueOf("2").equals(tb.getShort2())).as("Correct short2 value").isTrue(); + assertThat(Integer.valueOf("7").equals(bw.getPropertyValue("int1"))).as("Correct int1 value").isTrue(); assertThat(tb.getInt1() == 7).as("Correct int1 value").isTrue(); - assertThat(new Integer("8").equals(bw.getPropertyValue("int2"))).as("Correct int2 value").isTrue(); - assertThat(new Integer("8").equals(tb.getInt2())).as("Correct int2 value").isTrue(); - assertThat(new Long("5").equals(bw.getPropertyValue("long1"))).as("Correct long1 value").isTrue(); + assertThat(Integer.valueOf("8").equals(bw.getPropertyValue("int2"))).as("Correct int2 value").isTrue(); + assertThat(Integer.valueOf("8").equals(tb.getInt2())).as("Correct int2 value").isTrue(); + assertThat(Long.valueOf("5").equals(bw.getPropertyValue("long1"))).as("Correct long1 value").isTrue(); assertThat(tb.getLong1() == 5).as("Correct long1 value").isTrue(); - assertThat(new Long("6").equals(bw.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); - assertThat(new Long("6").equals(tb.getLong2())).as("Correct long2 value").isTrue(); + assertThat(Long.valueOf("6").equals(bw.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); + assertThat(Long.valueOf("6").equals(tb.getLong2())).as("Correct long2 value").isTrue(); assertThat(new BigInteger("3").equals(bw.getPropertyValue("bigInteger"))).as("Correct bigInteger value").isTrue(); assertThat(new BigInteger("3").equals(tb.getBigInteger())).as("Correct bigInteger value").isTrue(); - assertThat(new Float("7.1").equals(bw.getPropertyValue("float1"))).as("Correct float1 value").isTrue(); - assertThat(new Float("7.1").equals(new Float(tb.getFloat1()))).as("Correct float1 value").isTrue(); - assertThat(new Float("8.1").equals(bw.getPropertyValue("float2"))).as("Correct float2 value").isTrue(); - assertThat(new Float("8.1").equals(tb.getFloat2())).as("Correct float2 value").isTrue(); - assertThat(new Double("5.1").equals(bw.getPropertyValue("double1"))).as("Correct double1 value").isTrue(); + assertThat(Float.valueOf("7.1").equals(bw.getPropertyValue("float1"))).as("Correct float1 value").isTrue(); + assertThat(Float.valueOf("7.1").equals(tb.getFloat1())).as("Correct float1 value").isTrue(); + assertThat(Float.valueOf("8.1").equals(bw.getPropertyValue("float2"))).as("Correct float2 value").isTrue(); + assertThat(Float.valueOf("8.1").equals(tb.getFloat2())).as("Correct float2 value").isTrue(); + assertThat(Double.valueOf("5.1").equals(bw.getPropertyValue("double1"))).as("Correct double1 value").isTrue(); assertThat(tb.getDouble1() == 5.1).as("Correct double1 value").isTrue(); - assertThat(new Double("6.1").equals(bw.getPropertyValue("double2"))).as("Correct double2 value").isTrue(); - assertThat(new Double("6.1").equals(tb.getDouble2())).as("Correct double2 value").isTrue(); + assertThat(Double.valueOf("6.1").equals(bw.getPropertyValue("double2"))).as("Correct double2 value").isTrue(); + assertThat(Double.valueOf("6.1").equals(tb.getDouble2())).as("Correct double2 value").isTrue(); assertThat(new BigDecimal("4.5").equals(bw.getPropertyValue("bigDecimal"))).as("Correct bigDecimal value").isTrue(); assertThat(new BigDecimal("4.5").equals(tb.getBigDecimal())).as("Correct bigDecimal value").isTrue(); } @Test - public void testCustomNumberEditorWithoutAllowEmpty() { + void testCustomNumberEditorWithoutAllowEmpty() { NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN); NumberTestBean tb = new NumberTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -407,34 +407,34 @@ public void testCustomNumberEditorWithoutAllowEmpty() { bw.setPropertyValue("double2", "6,1"); bw.setPropertyValue("bigDecimal", "4,5"); - assertThat(new Short("1").equals(bw.getPropertyValue("short1"))).as("Correct short1 value").isTrue(); + assertThat(bw.getPropertyValue("short1")).as("Correct short1 value").isEqualTo(Short.valueOf("1")); assertThat(tb.getShort1() == 1).as("Correct short1 value").isTrue(); - assertThat(new Short("2").equals(bw.getPropertyValue("short2"))).as("Correct short2 value").isTrue(); - assertThat(new Short("2").equals(tb.getShort2())).as("Correct short2 value").isTrue(); - assertThat(new Integer("7").equals(bw.getPropertyValue("int1"))).as("Correct int1 value").isTrue(); + assertThat(bw.getPropertyValue("short2")).as("Correct short2 value").isEqualTo(Short.valueOf("2")); + assertThat(tb.getShort2()).as("Correct short2 value").isEqualTo(Short.valueOf("2")); + assertThat(bw.getPropertyValue("int1")).as("Correct int1 value").isEqualTo(Integer.valueOf("7")); assertThat(tb.getInt1() == 7).as("Correct int1 value").isTrue(); - assertThat(new Integer("8").equals(bw.getPropertyValue("int2"))).as("Correct int2 value").isTrue(); - assertThat(new Integer("8").equals(tb.getInt2())).as("Correct int2 value").isTrue(); - assertThat(new Long("5").equals(bw.getPropertyValue("long1"))).as("Correct long1 value").isTrue(); + assertThat(bw.getPropertyValue("int2")).as("Correct int2 value").isEqualTo(Integer.valueOf("8")); + assertThat(tb.getInt2()).as("Correct int2 value").isEqualTo(Integer.valueOf("8")); + assertThat(bw.getPropertyValue("long1")).as("Correct long1 value").isEqualTo(Long.valueOf("5")); assertThat(tb.getLong1() == 5).as("Correct long1 value").isTrue(); - assertThat(new Long("6").equals(bw.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); - assertThat(new Long("6").equals(tb.getLong2())).as("Correct long2 value").isTrue(); + assertThat(bw.getPropertyValue("long2")).as("Correct long2 value").isEqualTo(Long.valueOf("6")); + assertThat(tb.getLong2()).as("Correct long2 value").isEqualTo(Long.valueOf("6")); assertThat(new BigInteger("3").equals(bw.getPropertyValue("bigInteger"))).as("Correct bigInteger value").isTrue(); assertThat(new BigInteger("3").equals(tb.getBigInteger())).as("Correct bigInteger value").isTrue(); - assertThat(new Float("7.1").equals(bw.getPropertyValue("float1"))).as("Correct float1 value").isTrue(); - assertThat(new Float("7.1").equals(new Float(tb.getFloat1()))).as("Correct float1 value").isTrue(); - assertThat(new Float("8.1").equals(bw.getPropertyValue("float2"))).as("Correct float2 value").isTrue(); - assertThat(new Float("8.1").equals(tb.getFloat2())).as("Correct float2 value").isTrue(); - assertThat(new Double("5.1").equals(bw.getPropertyValue("double1"))).as("Correct double1 value").isTrue(); + assertThat(bw.getPropertyValue("float1")).as("Correct float1 value").isEqualTo(Float.valueOf("7.1")); + assertThat(Float.valueOf(tb.getFloat1())).as("Correct float1 value").isEqualTo(Float.valueOf("7.1")); + assertThat(bw.getPropertyValue("float2")).as("Correct float2 value").isEqualTo(Float.valueOf("8.1")); + assertThat(tb.getFloat2()).as("Correct float2 value").isEqualTo(Float.valueOf("8.1")); + assertThat(bw.getPropertyValue("double1")).as("Correct double1 value").isEqualTo(Double.valueOf("5.1")); assertThat(tb.getDouble1() == 5.1).as("Correct double1 value").isTrue(); - assertThat(new Double("6.1").equals(bw.getPropertyValue("double2"))).as("Correct double2 value").isTrue(); - assertThat(new Double("6.1").equals(tb.getDouble2())).as("Correct double2 value").isTrue(); + assertThat(bw.getPropertyValue("double2")).as("Correct double2 value").isEqualTo(Double.valueOf("6.1")); + assertThat(tb.getDouble2()).as("Correct double2 value").isEqualTo(Double.valueOf("6.1")); assertThat(new BigDecimal("4.5").equals(bw.getPropertyValue("bigDecimal"))).as("Correct bigDecimal value").isTrue(); assertThat(new BigDecimal("4.5").equals(tb.getBigDecimal())).as("Correct bigDecimal value").isTrue(); } @Test - public void testCustomNumberEditorWithAllowEmpty() { + void testCustomNumberEditorWithAllowEmpty() { NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN); NumberTestBean tb = new NumberTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -443,10 +443,10 @@ public void testCustomNumberEditorWithAllowEmpty() { bw.setPropertyValue("long1", "5"); bw.setPropertyValue("long2", "6"); - assertThat(new Long("5").equals(bw.getPropertyValue("long1"))).as("Correct long1 value").isTrue(); + assertThat(Long.valueOf("5").equals(bw.getPropertyValue("long1"))).as("Correct long1 value").isTrue(); assertThat(tb.getLong1() == 5).as("Correct long1 value").isTrue(); - assertThat(new Long("6").equals(bw.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); - assertThat(new Long("6").equals(tb.getLong2())).as("Correct long2 value").isTrue(); + assertThat(Long.valueOf("6").equals(bw.getPropertyValue("long2"))).as("Correct long2 value").isTrue(); + assertThat(Long.valueOf("6").equals(tb.getLong2())).as("Correct long2 value").isTrue(); bw.setPropertyValue("long2", ""); assertThat(bw.getPropertyValue("long2") == null).as("Correct long2 value").isTrue(); @@ -458,7 +458,7 @@ public void testCustomNumberEditorWithAllowEmpty() { } @Test - public void testCustomNumberEditorWithFrenchBigDecimal() throws Exception { + void testCustomNumberEditorWithFrenchBigDecimal() throws Exception { NumberFormat nf = NumberFormat.getNumberInstance(Locale.FRENCH); NumberTestBean tb = new NumberTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); @@ -475,14 +475,14 @@ public void testCustomNumberEditorWithFrenchBigDecimal() throws Exception { } @Test - public void testParseShortGreaterThanMaxValueWithoutNumberFormat() { + void testParseShortGreaterThanMaxValueWithoutNumberFormat() { CustomNumberEditor editor = new CustomNumberEditor(Short.class, true); assertThatExceptionOfType(NumberFormatException.class).as("greater than Short.MAX_VALUE + 1").isThrownBy(() -> editor.setAsText(String.valueOf(Short.MAX_VALUE + 1))); } @Test - public void testByteArrayPropertyEditor() { + void testByteArrayPropertyEditor() { PrimitiveArrayBean bean = new PrimitiveArrayBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.setPropertyValue("byteArray", "myvalue"); @@ -490,7 +490,7 @@ public void testByteArrayPropertyEditor() { } @Test - public void testCharArrayPropertyEditor() { + void testCharArrayPropertyEditor() { PrimitiveArrayBean bean = new PrimitiveArrayBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.setPropertyValue("charArray", "myvalue"); @@ -498,7 +498,7 @@ public void testCharArrayPropertyEditor() { } @Test - public void testCharacterEditor() { + void testCharacterEditor() { CharBean cb = new CharBean(); BeanWrapper bw = new BeanWrapperImpl(cb); @@ -520,78 +520,78 @@ public void testCharacterEditor() { } @Test - public void testCharacterEditorWithAllowEmpty() { + void testCharacterEditorWithAllowEmpty() { CharBean cb = new CharBean(); BeanWrapper bw = new BeanWrapperImpl(cb); bw.registerCustomEditor(Character.class, new CharacterEditor(true)); - bw.setPropertyValue("myCharacter", new Character('c')); - assertThat(cb.getMyCharacter()).isEqualTo(new Character('c')); + bw.setPropertyValue("myCharacter", 'c'); + assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('c')); bw.setPropertyValue("myCharacter", "c"); - assertThat(cb.getMyCharacter()).isEqualTo(new Character('c')); + assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('c')); bw.setPropertyValue("myCharacter", "\u0041"); - assertThat(cb.getMyCharacter()).isEqualTo(new Character('A')); + assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf('A')); bw.setPropertyValue("myCharacter", " "); - assertThat(cb.getMyCharacter()).isEqualTo(new Character(' ')); + assertThat(cb.getMyCharacter()).isEqualTo(Character.valueOf(' ')); bw.setPropertyValue("myCharacter", ""); assertThat(cb.getMyCharacter()).isNull(); } @Test - public void testCharacterEditorSetAsTextWithStringLongerThanOneCharacter() throws Exception { + void testCharacterEditorSetAsTextWithStringLongerThanOneCharacter() throws Exception { PropertyEditor charEditor = new CharacterEditor(false); assertThatIllegalArgumentException().isThrownBy(() -> charEditor.setAsText("ColdWaterCanyon")); } @Test - public void testCharacterEditorGetAsTextReturnsEmptyStringIfValueIsNull() throws Exception { + void testCharacterEditorGetAsTextReturnsEmptyStringIfValueIsNull() throws Exception { PropertyEditor charEditor = new CharacterEditor(false); - assertThat(charEditor.getAsText()).isEqualTo(""); + assertThat(charEditor.getAsText()).isEmpty(); charEditor = new CharacterEditor(true); charEditor.setAsText(null); - assertThat(charEditor.getAsText()).isEqualTo(""); + assertThat(charEditor.getAsText()).isEmpty(); charEditor.setAsText(""); - assertThat(charEditor.getAsText()).isEqualTo(""); + assertThat(charEditor.getAsText()).isEmpty(); charEditor.setAsText(" "); assertThat(charEditor.getAsText()).isEqualTo(" "); } @Test - public void testCharacterEditorSetAsTextWithNullNotAllowingEmptyAsNull() throws Exception { + void testCharacterEditorSetAsTextWithNullNotAllowingEmptyAsNull() throws Exception { PropertyEditor charEditor = new CharacterEditor(false); assertThatIllegalArgumentException().isThrownBy(() -> charEditor.setAsText(null)); } @Test - public void testClassEditor() { + void testClassEditor() { PropertyEditor classEditor = new ClassEditor(); classEditor.setAsText(TestBean.class.getName()); assertThat(classEditor.getValue()).isEqualTo(TestBean.class); assertThat(classEditor.getAsText()).isEqualTo(TestBean.class.getName()); classEditor.setAsText(null); - assertThat(classEditor.getAsText()).isEqualTo(""); + assertThat(classEditor.getAsText()).isEmpty(); classEditor.setAsText(""); - assertThat(classEditor.getAsText()).isEqualTo(""); + assertThat(classEditor.getAsText()).isEmpty(); classEditor.setAsText("\t "); - assertThat(classEditor.getAsText()).isEqualTo(""); + assertThat(classEditor.getAsText()).isEmpty(); } @Test - public void testClassEditorWithNonExistentClass() throws Exception { + void testClassEditorWithNonExistentClass() throws Exception { PropertyEditor classEditor = new ClassEditor(); assertThatIllegalArgumentException().isThrownBy(() -> classEditor.setAsText("hairdresser.on.Fire")); } @Test - public void testClassEditorWithArray() { + void testClassEditorWithArray() { PropertyEditor classEditor = new ClassEditor(); classEditor.setAsText("org.springframework.beans.testfixture.beans.TestBean[]"); assertThat(classEditor.getValue()).isEqualTo(TestBean[].class); @@ -602,7 +602,7 @@ public void testClassEditorWithArray() { * SPR_2165 - ClassEditor is inconsistent with multidimensional arrays */ @Test - public void testGetAsTextWithTwoDimensionalArray() throws Exception { + void testGetAsTextWithTwoDimensionalArray() throws Exception { String[][] chessboard = new String[8][8]; ClassEditor editor = new ClassEditor(); editor.setValue(chessboard.getClass()); @@ -613,7 +613,7 @@ public void testGetAsTextWithTwoDimensionalArray() throws Exception { * SPR_2165 - ClassEditor is inconsistent with multidimensional arrays */ @Test - public void testGetAsTextWithRidiculousMultiDimensionalArray() throws Exception { + void testGetAsTextWithRidiculousMultiDimensionalArray() throws Exception { String[][][][][] ridiculousChessboard = new String[8][4][0][1][3]; ClassEditor editor = new ClassEditor(); editor.setValue(ridiculousChessboard.getClass()); @@ -621,7 +621,7 @@ public void testGetAsTextWithRidiculousMultiDimensionalArray() throws Exception } @Test - public void testFileEditor() { + void testFileEditor() { PropertyEditor fileEditor = new FileEditor(); fileEditor.setAsText("file:myfile.txt"); assertThat(fileEditor.getValue()).isEqualTo(new File("myfile.txt")); @@ -629,7 +629,7 @@ public void testFileEditor() { } @Test - public void testFileEditorWithRelativePath() { + void testFileEditorWithRelativePath() { PropertyEditor fileEditor = new FileEditor(); try { fileEditor.setAsText("myfile.txt"); @@ -641,7 +641,7 @@ public void testFileEditorWithRelativePath() { } @Test - public void testFileEditorWithAbsolutePath() { + void testFileEditorWithAbsolutePath() { PropertyEditor fileEditor = new FileEditor(); // testing on Windows if (new File("C:/myfile.txt").isAbsolute()) { @@ -656,18 +656,18 @@ public void testFileEditorWithAbsolutePath() { } @Test - public void testLocaleEditor() { + void testLocaleEditor() { PropertyEditor localeEditor = new LocaleEditor(); localeEditor.setAsText("en_CA"); assertThat(localeEditor.getValue()).isEqualTo(Locale.CANADA); assertThat(localeEditor.getAsText()).isEqualTo("en_CA"); localeEditor = new LocaleEditor(); - assertThat(localeEditor.getAsText()).isEqualTo(""); + assertThat(localeEditor.getAsText()).isEmpty(); } @Test - public void testPatternEditor() { + void testPatternEditor() { final String REGEX = "a.*"; PropertyEditor patternEditor = new PatternEditor(); @@ -676,15 +676,15 @@ public void testPatternEditor() { assertThat(patternEditor.getAsText()).isEqualTo(REGEX); patternEditor = new PatternEditor(); - assertThat(patternEditor.getAsText()).isEqualTo(""); + assertThat(patternEditor.getAsText()).isEmpty(); patternEditor = new PatternEditor(); patternEditor.setAsText(null); - assertThat(patternEditor.getAsText()).isEqualTo(""); + assertThat(patternEditor.getAsText()).isEmpty(); } @Test - public void testCustomBooleanEditor() { + void testCustomBooleanEditor() { CustomBooleanEditor editor = new CustomBooleanEditor(false); editor.setAsText("true"); @@ -696,15 +696,15 @@ public void testCustomBooleanEditor() { assertThat(editor.getAsText()).isEqualTo("false"); editor.setValue(null); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); assertThatIllegalArgumentException().isThrownBy(() -> editor.setAsText(null)); } @Test - public void testCustomBooleanEditorWithEmptyAsNull() { + void testCustomBooleanEditorWithEmptyAsNull() { CustomBooleanEditor editor = new CustomBooleanEditor(true); editor.setAsText("true"); @@ -716,34 +716,34 @@ public void testCustomBooleanEditorWithEmptyAsNull() { assertThat(editor.getAsText()).isEqualTo("false"); editor.setValue(null); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testCustomDateEditor() { + void testCustomDateEditor() { CustomDateEditor editor = new CustomDateEditor(null, false); editor.setValue(null); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testCustomDateEditorWithEmptyAsNull() { + void testCustomDateEditorWithEmptyAsNull() { CustomDateEditor editor = new CustomDateEditor(null, true); editor.setValue(null); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testCustomDateEditorWithExactDateLength() { + void testCustomDateEditorWithExactDateLength() { int maxLength = 10; String validDate = "01/01/2005"; String invalidDate = "01/01/05"; - assertThat(validDate.length() == maxLength).isTrue(); - assertThat(invalidDate.length() == maxLength).isFalse(); + assertThat(validDate).hasSize(maxLength); + assertThat(invalidDate.length()).isNotEqualTo(maxLength); CustomDateEditor editor = new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), true, maxLength); editor.setAsText(validDate); @@ -753,39 +753,39 @@ public void testCustomDateEditorWithExactDateLength() { } @Test - public void testCustomNumberEditor() { + void testCustomNumberEditor() { CustomNumberEditor editor = new CustomNumberEditor(Integer.class, false); editor.setAsText("5"); assertThat(editor.getValue()).isEqualTo(5); assertThat(editor.getAsText()).isEqualTo("5"); editor.setValue(null); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testCustomNumberEditorWithHex() { + void testCustomNumberEditorWithHex() { CustomNumberEditor editor = new CustomNumberEditor(Integer.class, false); editor.setAsText("0x" + Integer.toHexString(64)); assertThat(editor.getValue()).isEqualTo(64); } @Test - public void testCustomNumberEditorWithEmptyAsNull() { + void testCustomNumberEditorWithEmptyAsNull() { CustomNumberEditor editor = new CustomNumberEditor(Integer.class, true); editor.setAsText("5"); assertThat(editor.getValue()).isEqualTo(5); assertThat(editor.getAsText()).isEqualTo("5"); editor.setAsText(""); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); editor.setValue(null); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testStringTrimmerEditor() { + void testStringTrimmerEditor() { StringTrimmerEditor editor = new StringTrimmerEditor(false); editor.setAsText("test"); assertThat(editor.getValue()).isEqualTo("test"); @@ -795,15 +795,15 @@ public void testStringTrimmerEditor() { assertThat(editor.getAsText()).isEqualTo("test"); editor.setAsText(""); assertThat(editor.getValue()).isEqualTo(""); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); editor.setValue(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); editor.setAsText(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testStringTrimmerEditorWithEmptyAsNull() { + void testStringTrimmerEditorWithEmptyAsNull() { StringTrimmerEditor editor = new StringTrimmerEditor(true); editor.setAsText("test"); assertThat(editor.getValue()).isEqualTo("test"); @@ -812,14 +812,14 @@ public void testStringTrimmerEditorWithEmptyAsNull() { assertThat(editor.getValue()).isEqualTo("test"); assertThat(editor.getAsText()).isEqualTo("test"); editor.setAsText(" "); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); editor.setValue(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testStringTrimmerEditorWithCharsToDelete() { + void testStringTrimmerEditorWithCharsToDelete() { StringTrimmerEditor editor = new StringTrimmerEditor("\r\n\f", false); editor.setAsText("te\ns\ft"); assertThat(editor.getValue()).isEqualTo("test"); @@ -829,13 +829,13 @@ public void testStringTrimmerEditorWithCharsToDelete() { assertThat(editor.getAsText()).isEqualTo("test"); editor.setAsText(""); assertThat(editor.getValue()).isEqualTo(""); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); editor.setValue(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testStringTrimmerEditorWithCharsToDeleteAndEmptyAsNull() { + void testStringTrimmerEditorWithCharsToDeleteAndEmptyAsNull() { StringTrimmerEditor editor = new StringTrimmerEditor("\r\n\f", true); editor.setAsText("te\ns\ft"); assertThat(editor.getValue()).isEqualTo("test"); @@ -844,14 +844,14 @@ public void testStringTrimmerEditorWithCharsToDeleteAndEmptyAsNull() { assertThat(editor.getValue()).isEqualTo("test"); assertThat(editor.getAsText()).isEqualTo("test"); editor.setAsText(" \n\f "); - assertThat(editor.getValue()).isEqualTo(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getValue()).isNull(); + assertThat(editor.getAsText()).isEmpty(); editor.setValue(null); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testIndexedPropertiesWithCustomEditorForType() { + void testIndexedPropertiesWithCustomEditorForType() { IndexedTestBean bean = new IndexedTestBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.registerCustomEditor(String.class, new PropertyEditorSupport() { @@ -904,7 +904,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testIndexedPropertiesWithCustomEditorForProperty() { + void testIndexedPropertiesWithCustomEditorForProperty() { IndexedTestBean bean = new IndexedTestBean(false); BeanWrapper bw = new BeanWrapperImpl(bean); bw.registerCustomEditor(String.class, "array.name", new PropertyEditorSupport() { @@ -971,7 +971,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testIndexedPropertiesWithIndividualCustomEditorForProperty() { + void testIndexedPropertiesWithIndividualCustomEditorForProperty() { IndexedTestBean bean = new IndexedTestBean(false); BeanWrapper bw = new BeanWrapperImpl(bean); bw.registerCustomEditor(String.class, "array[0].name", new PropertyEditorSupport() { @@ -1056,7 +1056,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testNestedIndexedPropertiesWithCustomEditorForProperty() { + void testNestedIndexedPropertiesWithCustomEditorForProperty() { IndexedTestBean bean = new IndexedTestBean(); TestBean tb0 = bean.getArray()[0]; TestBean tb1 = bean.getArray()[1]; @@ -1140,7 +1140,7 @@ public String getAsText() { } @Test - public void testNestedIndexedPropertiesWithIndexedCustomEditorForProperty() { + void testNestedIndexedPropertiesWithIndexedCustomEditorForProperty() { IndexedTestBean bean = new IndexedTestBean(); TestBean tb0 = bean.getArray()[0]; TestBean tb1 = bean.getArray()[1]; @@ -1191,7 +1191,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testIndexedPropertiesWithDirectAccessAndPropertyEditors() { + void testIndexedPropertiesWithDirectAccessAndPropertyEditors() { IndexedTestBean bean = new IndexedTestBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.registerCustomEditor(TestBean.class, "array", new PropertyEditorSupport() { @@ -1245,7 +1245,7 @@ public String getAsText() { } @Test - public void testIndexedPropertiesWithDirectAccessAndSpecificPropertyEditors() { + void testIndexedPropertiesWithDirectAccessAndSpecificPropertyEditors() { IndexedTestBean bean = new IndexedTestBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.registerCustomEditor(TestBean.class, "array[0]", new PropertyEditorSupport() { @@ -1332,7 +1332,7 @@ public String getAsText() { } @Test - public void testIndexedPropertiesWithListPropertyEditor() { + void testIndexedPropertiesWithListPropertyEditor() { IndexedTestBean bean = new IndexedTestBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.registerCustomEditor(List.class, "list", new PropertyEditorSupport() { @@ -1350,7 +1350,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testConversionToOldCollections() throws PropertyVetoException { + void testConversionToOldCollections() throws PropertyVetoException { OldCollectionsBean tb = new OldCollectionsBean(); BeanWrapper bw = new BeanWrapperImpl(tb); bw.registerCustomEditor(Vector.class, new CustomCollectionEditor(Vector.class)); @@ -1367,7 +1367,7 @@ public void testConversionToOldCollections() throws PropertyVetoException { } @Test - public void testUninitializedArrayPropertyWithCustomEditor() { + void testUninitializedArrayPropertyWithCustomEditor() { IndexedTestBean bean = new IndexedTestBean(false); BeanWrapper bw = new BeanWrapperImpl(bean); PropertyEditor pe = new CustomNumberEditor(Integer.class, true); @@ -1383,7 +1383,7 @@ public void testUninitializedArrayPropertyWithCustomEditor() { } @Test - public void testArrayToArrayConversion() throws PropertyVetoException { + void testArrayToArrayConversion() throws PropertyVetoException { IndexedTestBean tb = new IndexedTestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); bw.registerCustomEditor(TestBean.class, new PropertyEditorSupport() { @@ -1399,7 +1399,7 @@ public void setAsText(String text) throws IllegalArgumentException { } @Test - public void testArrayToStringConversion() throws PropertyVetoException { + void testArrayToStringConversion() throws PropertyVetoException { TestBean tb = new TestBean(); BeanWrapper bw = new BeanWrapperImpl(tb); bw.registerCustomEditor(String.class, new PropertyEditorSupport() { @@ -1408,16 +1408,16 @@ public void setAsText(String text) throws IllegalArgumentException { setValue("-" + text + "-"); } }); - bw.setPropertyValue("name", new String[] {"a", "b"}); + bw.setPropertyValue("name", new String[]{"a", "b"}); assertThat(tb.getName()).isEqualTo("-a,b-"); } @Test - public void testClassArrayEditorSunnyDay() throws Exception { + void testClassArrayEditorSunnyDay() throws Exception { ClassArrayEditor classArrayEditor = new ClassArrayEditor(); classArrayEditor.setAsText("java.lang.String,java.util.HashMap"); Class[] classes = (Class[]) classArrayEditor.getValue(); - assertThat(classes.length).isEqualTo(2); + assertThat(classes).hasSize(2); assertThat(classes[0]).isEqualTo(String.class); assertThat(classes[1]).isEqualTo(HashMap.class); assertThat(classArrayEditor.getAsText()).isEqualTo("java.lang.String,java.util.HashMap"); @@ -1426,11 +1426,11 @@ public void testClassArrayEditorSunnyDay() throws Exception { } @Test - public void testClassArrayEditorSunnyDayWithArrayTypes() throws Exception { + void testClassArrayEditorSunnyDayWithArrayTypes() throws Exception { ClassArrayEditor classArrayEditor = new ClassArrayEditor(); classArrayEditor.setAsText("java.lang.String[],java.util.Map[],int[],float[][][]"); Class[] classes = (Class[]) classArrayEditor.getValue(); - assertThat(classes.length).isEqualTo(4); + assertThat(classes).hasSize(4); assertThat(classes[0]).isEqualTo(String[].class); assertThat(classes[1]).isEqualTo(Map[].class); assertThat(classes[2]).isEqualTo(int[].class); @@ -1441,31 +1441,31 @@ public void testClassArrayEditorSunnyDayWithArrayTypes() throws Exception { } @Test - public void testClassArrayEditorSetAsTextWithNull() throws Exception { + void testClassArrayEditorSetAsTextWithNull() throws Exception { ClassArrayEditor editor = new ClassArrayEditor(); editor.setAsText(null); assertThat(editor.getValue()).isNull(); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testClassArrayEditorSetAsTextWithEmptyString() throws Exception { + void testClassArrayEditorSetAsTextWithEmptyString() throws Exception { ClassArrayEditor editor = new ClassArrayEditor(); editor.setAsText(""); assertThat(editor.getValue()).isNull(); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testClassArrayEditorSetAsTextWithWhitespaceString() throws Exception { + void testClassArrayEditorSetAsTextWithWhitespaceString() throws Exception { ClassArrayEditor editor = new ClassArrayEditor(); editor.setAsText("\n"); assertThat(editor.getValue()).isNull(); - assertThat(editor.getAsText()).isEqualTo(""); + assertThat(editor.getAsText()).isEmpty(); } @Test - public void testCharsetEditor() throws Exception { + void testCharsetEditor() throws Exception { CharsetEditor editor = new CharsetEditor(); String name = "UTF-8"; editor.setAsText(name); diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java index 40354fc4464..f0c659bcbdb 100644 --- a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/PathEditorTests.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. @@ -34,57 +34,78 @@ public class PathEditorTests { @Test - public void testClasspathPathName() throws Exception { + public void testClasspathPathName() { PropertyEditor pathEditor = new PathEditor(); pathEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass()) + ".class"); Object value = pathEditor.getValue(); - boolean condition = value instanceof Path; - assertThat(condition).isTrue(); + assertThat(value instanceof Path).isTrue(); Path path = (Path) value; assertThat(path.toFile().exists()).isTrue(); } @Test - public void testWithNonExistentResource() throws Exception { + public void testWithNonExistentResource() { PropertyEditor propertyEditor = new PathEditor(); assertThatIllegalArgumentException().isThrownBy(() -> propertyEditor.setAsText("classpath:/no_way_this_file_is_found.doc")); } @Test - public void testWithNonExistentPath() throws Exception { + public void testWithNonExistentPath() { PropertyEditor pathEditor = new PathEditor(); pathEditor.setAsText("file:/no_way_this_file_is_found.doc"); Object value = pathEditor.getValue(); - boolean condition1 = value instanceof Path; - assertThat(condition1).isTrue(); + assertThat(value instanceof Path).isTrue(); Path path = (Path) value; - boolean condition = !path.toFile().exists(); - assertThat(condition).isTrue(); + assertThat(!path.toFile().exists()).isTrue(); } @Test - public void testAbsolutePath() throws Exception { + public void testAbsolutePath() { PropertyEditor pathEditor = new PathEditor(); pathEditor.setAsText("/no_way_this_file_is_found.doc"); Object value = pathEditor.getValue(); - boolean condition1 = value instanceof Path; - assertThat(condition1).isTrue(); + assertThat(value instanceof Path).isTrue(); Path path = (Path) value; - boolean condition = !path.toFile().exists(); - assertThat(condition).isTrue(); + assertThat(!path.toFile().exists()).isTrue(); } @Test - public void testUnqualifiedPathNameFound() throws Exception { + public void testWindowsAbsolutePath() { + PropertyEditor pathEditor = new PathEditor(); + pathEditor.setAsText("C:\\no_way_this_file_is_found.doc"); + Object value = pathEditor.getValue(); + assertThat(value instanceof Path).isTrue(); + Path path = (Path) value; + assertThat(!path.toFile().exists()).isTrue(); + } + + @Test + public void testWindowsAbsoluteFilePath() { + PropertyEditor pathEditor = new PathEditor(); + try { + pathEditor.setAsText("file://C:\\no_way_this_file_is_found.doc"); + Object value = pathEditor.getValue(); + assertThat(value instanceof Path).isTrue(); + Path path = (Path) value; + assertThat(!path.toFile().exists()).isTrue(); + } + catch (IllegalArgumentException ex) { + if (File.separatorChar == '\\') { // on Windows, otherwise silently ignore + throw ex; + } + } + } + + @Test + public void testUnqualifiedPathNameFound() { PropertyEditor pathEditor = new PathEditor(); String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass()) + ".class"; pathEditor.setAsText(fileName); Object value = pathEditor.getValue(); - boolean condition = value instanceof Path; - assertThat(condition).isTrue(); + assertThat(value instanceof Path).isTrue(); Path path = (Path) value; File file = path.toFile(); assertThat(file.exists()).isTrue(); @@ -96,14 +117,13 @@ public void testUnqualifiedPathNameFound() throws Exception { } @Test - public void testUnqualifiedPathNameNotFound() throws Exception { + public void testUnqualifiedPathNameNotFound() { PropertyEditor pathEditor = new PathEditor(); String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" + ClassUtils.getShortName(getClass()) + ".clazz"; pathEditor.setAsText(fileName); Object value = pathEditor.getValue(); - boolean condition = value instanceof Path; - assertThat(condition).isTrue(); + assertThat(value instanceof Path).isTrue(); Path path = (Path) value; File file = path.toFile(); assertThat(file.exists()).isFalse(); diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/DummyBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/DummyBean.java index cac5cced1c8..82f40bae28a 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/DummyBean.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/DummyBean.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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.beans.testfixture.beans; /** @@ -65,4 +66,5 @@ public int getAge() { public TestBean getSpouse() { return spouse; } + } diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java index 2e53ce2f885..3ca9a82d72f 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/MustBeInitialized.java @@ -40,8 +40,9 @@ public void afterPropertiesSet() throws Exception { * managed the bean's lifecycle correctly */ public void businessMethod() { - if (!this.inited) + if (!this.inited) { throw new RuntimeException("Factory didn't call afterPropertiesSet() on MustBeInitialized object"); + } } } diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java index 661eff92feb..7b725d39c9b 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/Pet.java @@ -39,12 +39,18 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } final Pet pet = (Pet) o; - if (name != null ? !name.equals(pet.name) : pet.name != null) return false; + if (name != null ? !name.equals(pet.name) : pet.name != null) { + return false; + } return true; } diff --git a/spring-context-indexer/spring-context-indexer.gradle b/spring-context-indexer/spring-context-indexer.gradle index a9769ada026..2c1df2e91dc 100644 --- a/spring-context-indexer/spring-context-indexer.gradle +++ b/spring-context-indexer/spring-context-indexer.gradle @@ -1,9 +1,9 @@ description = "Spring Context Indexer" dependencies { - testCompile(project(":spring-context")) - testCompile("javax.inject:javax.inject") - testCompile("javax.annotation:javax.annotation-api") - testCompile("javax.transaction:javax.transaction-api") - testCompile("org.eclipse.persistence:javax.persistence") + testImplementation(project(":spring-context")) + testImplementation("javax.inject:javax.inject") + testImplementation("javax.annotation:javax.annotation-api") + testImplementation("javax.transaction:javax.transaction-api") + testImplementation("org.eclipse.persistence:javax.persistence") } diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java index fcd0d63e12f..9f1edf7f0cc 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/CandidateComponentsIndexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -19,7 +19,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -37,7 +36,7 @@ import javax.lang.model.element.TypeElement; /** - * Annotation {@link Processor} that writes {@link CandidateComponentsMetadata} + * Annotation {@link Processor} that writes a {@link CandidateComponentsMetadata} * file for spring components. * * @author Stephane Nicoll @@ -46,9 +45,6 @@ */ public class CandidateComponentsIndexer implements Processor { - private static final Set TYPE_KINDS = - Collections.unmodifiableSet(EnumSet.of(ElementKind.CLASS, ElementKind.INTERFACE)); - private MetadataStore metadataStore; private MetadataCollector metadataCollector; @@ -136,7 +132,8 @@ private void writeMetaData() { private static List staticTypesIn(Iterable elements) { List list = new ArrayList<>(); for (Element element : elements) { - if (TYPE_KINDS.contains(element.getKind()) && element.getModifiers().contains(Modifier.STATIC)) { + if ((element.getKind().isClass() || element.getKind() == ElementKind.INTERFACE) && + element.getModifiers().contains(Modifier.STATIC) && element instanceof TypeElement) { list.add((TypeElement) element); } } diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/IndexedStereotypesProvider.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/IndexedStereotypesProvider.java index c8ef2b75185..6027c0e6680 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/IndexedStereotypesProvider.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/IndexedStereotypesProvider.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. @@ -26,7 +26,7 @@ /** * A {@link StereotypesProvider} implementation that extracts the stereotypes - * flagged by the {@value INDEXED_ANNOTATION} annotation. This implementation + * flagged by the {@value #INDEXED_ANNOTATION} annotation. This implementation * honors stereotypes defined this way on meta-annotations. * * @author Stephane Nicoll @@ -48,7 +48,7 @@ public IndexedStereotypesProvider(TypeHelper typeHelper) { public Set getStereotypes(Element element) { Set stereotypes = new LinkedHashSet<>(); ElementKind kind = element.getKind(); - if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) { + if (!kind.isClass() && kind != ElementKind.INTERFACE) { return stereotypes; } Set seen = new HashSet<>(); diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/MetadataStore.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/MetadataStore.java index c00f682b77e..c27fae2c079 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/MetadataStore.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/MetadataStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/PackageInfoStereotypesProvider.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/PackageInfoStereotypesProvider.java index d35b4370c27..685f4e25864 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/PackageInfoStereotypesProvider.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/PackageInfoStereotypesProvider.java @@ -24,7 +24,7 @@ /** * A {@link StereotypesProvider} implementation that provides the - * {@value STEREOTYPE} stereotype for each package-info. + * {@value #STEREOTYPE} stereotype for each package-info. * * @author Stephane Nicoll * @since 5.0 diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java index 378343c6a33..3a7eb01dbbc 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StandardStereotypesProvider.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. @@ -24,8 +24,8 @@ import javax.lang.model.element.ElementKind; /** - * A {@link StereotypesProvider} that extract a stereotype for each - * {@code javax.*} annotation placed on a class or interface. + * A {@link StereotypesProvider} that extracts a stereotype for each + * {@code javax.*} annotation present on a class or interface. * * @author Stephane Nicoll * @since 5.0 diff --git a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StereotypesProvider.java b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StereotypesProvider.java index e0e5f7a7422..4061ca6ac40 100644 --- a/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StereotypesProvider.java +++ b/spring-context-indexer/src/main/java/org/springframework/context/index/processor/StereotypesProvider.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. @@ -22,7 +22,7 @@ /** * Provide the list of stereotypes that match an {@link Element}. - * If an element has one more stereotypes, it is referenced in the index + *

    If an element has one or more stereotypes, it is referenced in the index * of candidate components and each stereotype can be queried individually. * * @author Stephane Nicoll diff --git a/spring-context-support/spring-context-support.gradle b/spring-context-support/spring-context-support.gradle index f8a8631293a..843d1920ebe 100644 --- a/spring-context-support/spring-context-support.gradle +++ b/spring-context-support/spring-context-support.gradle @@ -1,9 +1,9 @@ description = "Spring Context Support" dependencies { - compile(project(":spring-beans")) - compile(project(":spring-context")) - compile(project(":spring-core")) + api(project(":spring-beans")) + api(project(":spring-context")) + api(project(":spring-core")) optional(project(":spring-jdbc")) // for Quartz support optional(project(":spring-tx")) // for Quartz support optional("javax.activation:javax.activation-api") @@ -14,18 +14,18 @@ dependencies { optional("org.quartz-scheduler:quartz") optional("org.codehaus.fabric3.api:commonj") optional("org.freemarker:freemarker") - testCompile(project(":spring-context")) - testCompile(testFixtures(project(":spring-beans"))) - testCompile(testFixtures(project(":spring-context"))) - testCompile(testFixtures(project(":spring-core"))) - testCompile(testFixtures(project(":spring-tx"))) - testCompile("org.hsqldb:hsqldb") - testCompile("org.hibernate:hibernate-validator") - testCompile("javax.annotation:javax.annotation-api") - testRuntime("org.ehcache:jcache") - testRuntime("org.ehcache:ehcache") - testRuntime("org.glassfish:javax.el") - testRuntime("com.sun.mail:javax.mail") + testImplementation(project(":spring-context")) + testImplementation(testFixtures(project(":spring-beans"))) + testImplementation(testFixtures(project(":spring-context"))) + testImplementation(testFixtures(project(":spring-core"))) + testImplementation(testFixtures(project(":spring-tx"))) + testImplementation("org.hsqldb:hsqldb") + testImplementation("org.hibernate:hibernate-validator") + testImplementation("javax.annotation:javax.annotation-api") + testRuntimeOnly("org.ehcache:jcache") + testRuntimeOnly("org.ehcache:ehcache") + testRuntimeOnly("org.glassfish:javax.el") + testRuntimeOnly("com.sun.mail:javax.mail") testFixturesApi("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation("org.assertj:assertj-core") testFixturesImplementation("org.mockito:mockito-core") diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java index 068341965ad..8a7137819d3 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -130,8 +130,8 @@ public void setShared(boolean shared) { @Override public void afterPropertiesSet() throws CacheException { - if (logger.isInfoEnabled()) { - logger.info("Initializing EhCache CacheManager" + + if (logger.isDebugEnabled()) { + logger.debug("Initializing EhCache CacheManager" + (this.cacheManagerName != null ? " '" + this.cacheManagerName + "'" : "")); } @@ -188,8 +188,8 @@ public boolean isSingleton() { @Override public void destroy() { if (this.cacheManager != null && this.locallyManaged) { - if (logger.isInfoEnabled()) { - logger.info("Shutting down EhCache CacheManager" + + if (logger.isDebugEnabled()) { + logger.debug("Shutting down EhCache CacheManager" + (this.cacheManagerName != null ? " '" + this.cacheManagerName + "'" : "")); } this.cacheManager.shutdown(); diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java index 1cd5ce612f3..4d2af4b9461 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/AbstractJCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -20,7 +20,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.cache.annotation.AbstractCachingConfiguration; -import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.interceptor.CacheResolver; import org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource; import org.springframework.cache.jcache.interceptor.JCacheOperationSource; @@ -46,11 +45,14 @@ public abstract class AbstractJCacheConfiguration extends AbstractCachingConfigu @Override - protected void useCachingConfigurer(CachingConfigurer config) { - super.useCachingConfigurer(config); - if (config instanceof JCacheConfigurer) { - this.exceptionCacheResolver = ((JCacheConfigurer) config)::exceptionCacheResolver; - } + protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) { + super.useCachingConfigurer(cachingConfigurerSupplier); + this.exceptionCacheResolver = cachingConfigurerSupplier.adapt(config -> { + if (config instanceof JCacheConfigurer) { + return ((JCacheConfigurer) config).exceptionCacheResolver(); + } + return null; + }); } @Bean(name = "jCacheOperationSource") diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java index 989e720aeb9..039729a02d5 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurer.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. @@ -26,8 +26,7 @@ *

    To be implemented by classes annotated with * {@link org.springframework.cache.annotation.EnableCaching} that wish * or need to specify explicitly how exception caches are resolved for - * annotation-driven cache management. Consider extending {@link JCacheConfigurerSupport}, - * which provides a stub implementation of all interface methods. + * annotation-driven cache management. * *

    See {@link org.springframework.cache.annotation.EnableCaching} for * general examples and context; see {@link #exceptionCacheResolver()} for @@ -36,7 +35,6 @@ * @author Stephane Nicoll * @since 4.1 * @see CachingConfigurer - * @see JCacheConfigurerSupport * @see org.springframework.cache.annotation.EnableCaching */ public interface JCacheConfigurer extends CachingConfigurer { @@ -60,6 +58,8 @@ public interface JCacheConfigurer extends CachingConfigurer { * See {@link org.springframework.cache.annotation.EnableCaching} for more complete examples. */ @Nullable - CacheResolver exceptionCacheResolver(); + default CacheResolver exceptionCacheResolver() { + return null; + } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java index 3edf2a2c153..8b20e4b1482 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.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. @@ -79,7 +79,7 @@ public JCacheOperation getCacheOperation(Method method, @Nullable Class ta @Nullable private JCacheOperation computeCacheOperation(Method method, @Nullable Class targetClass) { - // Don't allow no-public methods as required. + // Don't allow non-public methods, as configured. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java index d1771b1bd6e..036a4f6cb58 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractJCacheOperation.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. @@ -138,7 +138,7 @@ protected ExceptionTypeFilter createExceptionTypeFilter( @Override public String toString() { - return getOperationDescription().append("]").toString(); + return getOperationDescription().append(']').toString(); } /** @@ -148,7 +148,7 @@ public String toString() { protected StringBuilder getOperationDescription() { StringBuilder result = new StringBuilder(); result.append(getClass().getSimpleName()); - result.append("["); + result.append('['); result.append(this.methodDetails); return result; } diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java index f795390fa24..1eff5cebfaa 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java @@ -896,7 +896,7 @@ private void setHtmlTextToMimePart(MimePart mimePart, String text) throws Messag *

    NOTE: Invoke {@code addInline} after {@link #setText}; * else, mail readers might not be able to resolve inline references correctly. * @param contentId the content ID to use. Will end up as "Content-ID" header - * in the body part, surrounded by angle brackets: e.g. "myId" -> "<myId>". + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". * Can be referenced in HTML source via src="/service/cid:myId" expressions. * @param dataSource the {@code javax.activation.DataSource} to take * the content from, determining the InputStream and the content type @@ -923,7 +923,7 @@ public void addInline(String contentId, DataSource dataSource) throws MessagingE *

    NOTE: Invoke {@code addInline} after {@link #setText}; * else, mail readers might not be able to resolve inline references correctly. * @param contentId the content ID to use. Will end up as "Content-ID" header - * in the body part, surrounded by angle brackets: e.g. "myId" -> "<myId>". + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". * Can be referenced in HTML source via src="/service/cid:myId" expressions. * @param file the File resource to take the content from * @throws MessagingException in case of errors @@ -950,7 +950,7 @@ public void addInline(String contentId, File file) throws MessagingException { *

    NOTE: Invoke {@code addInline} after {@link #setText}; * else, mail readers might not be able to resolve inline references correctly. * @param contentId the content ID to use. Will end up as "Content-ID" header - * in the body part, surrounded by angle brackets: e.g. "myId" -> "<myId>". + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". * Can be referenced in HTML source via src="/service/cid:myId" expressions. * @param resource the resource to take the content from * @throws MessagingException in case of errors @@ -976,7 +976,7 @@ public void addInline(String contentId, Resource resource) throws MessagingExcep *

    NOTE: Invoke {@code addInline} after {@code setText}; * else, mail readers might not be able to resolve inline references correctly. * @param contentId the content ID to use. Will end up as "Content-ID" header - * in the body part, surrounded by angle brackets: e.g. "myId" -> "<myId>". + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". * Can be referenced in HTML source via src="/service/cid:myId" expressions. * @param inputStreamSource the resource to take the content from * @param contentType the content type to use for the element diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java index a9adcc823d5..8d295684407 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -171,6 +171,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java index e0b12f443f9..f6042bee934 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -39,6 +39,8 @@ * Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed * {@link DataSource} instead of using a Quartz-managed JDBC connection pool. * This JobStore will be used if SchedulerFactoryBean's "dataSource" property is set. + * You may also configure it explicitly, possibly as a custom subclass of this + * {@code LocalDataSourceJobStore} or as an equivalent {@code JobStoreCMT} variant. * *

    Supports both transactional and non-transactional DataSource access. * With a non-XA DataSource and local Spring transactions, a single DataSource @@ -58,6 +60,8 @@ * @since 1.1 * @see SchedulerFactoryBean#setDataSource * @see SchedulerFactoryBean#setNonTransactionalDataSource + * @see SchedulerFactoryBean#getConfigTimeDataSource() + * @see SchedulerFactoryBean#getConfigTimeNonTransactionalDataSource() * @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection */ diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index 50ef456cfc9..15185bba6c3 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.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. @@ -310,9 +310,11 @@ public void setTaskExecutor(Executor taskExecutor) { /** * Set the default {@link DataSource} to be used by the Scheduler. - * If set, this will override corresponding settings in Quartz properties. *

    Note: If this is set, the Quartz settings should not define * a job store "dataSource" to avoid meaningless double configuration. + * Also, do not define a "org.quartz.jobStore.class" property at all. + * (You may explicitly define Spring's {@link LocalDataSourceJobStore} + * but that's the default when using this method anyway.) *

    A Spring-specific subclass of Quartz' JobStoreCMT will be used. * It is therefore strongly recommended to perform all operations on * the Scheduler within Spring-managed (or plain JTA) transactions. @@ -570,7 +572,7 @@ private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws S CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps); if (this.dataSource != null) { - mergedProps.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); + mergedProps.putIfAbsent(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); } // Determine scheduler name across local settings and Quartz properties... diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java index 35534962d9f..6cfa717398e 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,6 +76,7 @@ public void execute(Runnable task) { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java index e90a3ece936..f8c0de21f2d 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java @@ -178,14 +178,11 @@ public void setCacheNameNullRestoreDynamicMode() { @Test public void cacheLoaderUseLoadingCache() { CaffeineCacheManager cm = new CaffeineCacheManager("c1"); - cm.setCacheLoader(new CacheLoader() { - @Override - public Object load(Object key) throws Exception { - if ("ping".equals(key)) { - return "pong"; - } - throw new IllegalArgumentException("I only know ping"); + cm.setCacheLoader(key -> { + if ("ping".equals(key)) { + return "pong"; } + throw new IllegalArgumentException("I only know ping"); }); Cache cache1 = cm.getCache("c1"); Cache.ValueWrapper value = cache1.get("ping"); diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java index 21572035330..7f760354d8b 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,7 +106,7 @@ void testPutIfAbsentNullValue() { Cache.ValueWrapper wrapper = cache.putIfAbsent(key, "anotherValue"); // A value is set but is 'null' assertThat(wrapper).isNotNull(); - assertThat(wrapper.get()).isEqualTo(null); + assertThat(wrapper.get()).isNull(); // not changed assertThat(cache.get(key).get()).isEqualTo(value); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java index 188cc293c97..e73dd79a222 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheAnnotationTests.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. @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.SimpleKeyGenerator; @@ -104,7 +104,7 @@ public void testEvictAllEarlyWithTransaction() { @Configuration @EnableCaching - static class EnableCachingConfig extends CachingConfigurerSupport { + static class EnableCachingConfig implements CachingConfigurer { @Autowired CachingProvider cachingProvider; diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java index 5c12aceac02..1dcc12540d1 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -180,7 +180,7 @@ public CacheResolver exceptionCacheResolver() { @Configuration @EnableCaching - public static class EmptyConfigSupportConfig extends JCacheConfigurerSupport { + public static class EmptyConfigSupportConfig implements JCacheConfigurer { @Bean public CacheManager cm() { return new NoOpCacheManager(); @@ -190,7 +190,7 @@ public CacheManager cm() { @Configuration @EnableCaching - static class FullCachingConfigSupport extends JCacheConfigurerSupport { + static class FullCachingConfigSupport implements JCacheConfigurer { @Override @Bean @@ -220,7 +220,7 @@ public CacheResolver exceptionCacheResolver() { @Configuration @EnableCaching - static class NoExceptionCacheResolverConfig extends JCacheConfigurerSupport { + static class NoExceptionCacheResolverConfig implements JCacheConfigurer { @Override @Bean diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java index 3b341a0c4df..561c0af54d1 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheErrorHandlerTests.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. @@ -34,7 +34,7 @@ import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.cache.jcache.config.JCacheConfigurerSupport; +import org.springframework.cache.jcache.config.JCacheConfigurer; import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -141,7 +141,7 @@ public void clearFail() { @Configuration @EnableCaching - static class Config extends JCacheConfigurerSupport { + static class Config implements JCacheConfigurer { @Bean @Override diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java index dd5497764b0..35db912df97 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheKeyGeneratorTests.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. @@ -34,7 +34,7 @@ import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.SimpleKey; import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.cache.jcache.config.JCacheConfigurerSupport; +import org.springframework.cache.jcache.config.JCacheConfigurer; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -97,7 +97,7 @@ public void getFiltered() { @Configuration @EnableCaching - static class Config extends JCacheConfigurerSupport { + static class Config implements JCacheConfigurer { @Bean @Override @@ -151,7 +151,7 @@ private void expect(Object... params) { @Override public Object generate(Object target, Method method, Object... params) { assertThat(Arrays.equals(expectedParams, params)).as("Unexpected parameters: expected: " - + Arrays.toString(this.expectedParams) + " but got: " + Arrays.toString(params)).isTrue(); + + Arrays.toString(this.expectedParams) + " but got: " + Arrays.toString(params)).isTrue(); return new SimpleKey(params); } } diff --git a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java index 3bd467b9106..47045905723 100644 --- a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java +++ b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java @@ -182,12 +182,9 @@ public void javaMailSenderWithMimeMessagePreparator() { final List messages = new ArrayList<>(); - MimeMessagePreparator preparator = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("you@mail.org")); - messages.add(mimeMessage); - } + MimeMessagePreparator preparator = mimeMessage -> { + mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("you@mail.org")); + messages.add(mimeMessage); }; sender.send(preparator); @@ -208,19 +205,13 @@ public void javaMailSenderWithMimeMessagePreparators() { final List messages = new ArrayList<>(); - MimeMessagePreparator preparator1 = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("he@mail.org")); - messages.add(mimeMessage); - } + MimeMessagePreparator preparator1 = mimeMessage -> { + mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("he@mail.org")); + messages.add(mimeMessage); }; - MimeMessagePreparator preparator2 = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("she@mail.org")); - messages.add(mimeMessage); - } + MimeMessagePreparator preparator2 = mimeMessage -> { + mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress("she@mail.org")); + messages.add(mimeMessage); }; sender.send(preparator1, preparator2); @@ -323,12 +314,7 @@ public void javaMailSenderWithParseExceptionOnSimpleMessage() { @Test public void javaMailSenderWithParseExceptionOnMimeMessagePreparator() { MockJavaMailSender sender = new MockJavaMailSender(); - MimeMessagePreparator preparator = new MimeMessagePreparator() { - @Override - public void prepare(MimeMessage mimeMessage) throws MessagingException { - mimeMessage.setFrom(new InternetAddress("")); - } - }; + MimeMessagePreparator preparator = mimeMessage -> mimeMessage.setFrom(new InternetAddress("")); try { sender.send(preparator); } diff --git a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java index 6c7d5b8691c..9d461d2e400 100644 --- a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java +++ b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Properties; import javax.sql.DataSource; @@ -30,6 +31,7 @@ import org.quartz.SchedulerFactory; import org.quartz.impl.JobDetailImpl; import org.quartz.impl.SchedulerRepository; +import org.quartz.impl.jdbcjobstore.JobStoreTX; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -40,6 +42,8 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -57,10 +61,10 @@ * @author Sam Brannen * @since 20.02.2004 */ -public class QuartzSupportTests { +class QuartzSupportTests { @Test - public void schedulerFactoryBeanWithApplicationContext() throws Exception { + void schedulerFactoryBeanWithApplicationContext() throws Exception { TestBean tb = new TestBean("tb", 99); StaticApplicationContext ac = new StaticApplicationContext(); @@ -97,7 +101,7 @@ protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String sc @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerWithTaskExecutor() throws Exception { + void schedulerWithTaskExecutor() throws Exception { CountingTaskExecutor taskExecutor = new CountingTaskExecutor(); DummyJob.count = 0; @@ -130,7 +134,7 @@ public void schedulerWithTaskExecutor() throws Exception { @Test @SuppressWarnings({ "unchecked", "rawtypes" }) - public void jobDetailWithRunnableInsteadOfJob() { + void jobDetailWithRunnableInsteadOfJob() { JobDetailImpl jobDetail = new JobDetailImpl(); assertThatIllegalArgumentException().isThrownBy(() -> jobDetail.setJobClass((Class) DummyRunnable.class)); @@ -138,7 +142,7 @@ public void jobDetailWithRunnableInsteadOfJob() { @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerWithQuartzJobBean() throws Exception { + void schedulerWithQuartzJobBean() throws Exception { DummyJob.param = 0; DummyJob.count = 0; @@ -171,7 +175,7 @@ public void schedulerWithQuartzJobBean() throws Exception { @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerWithSpringBeanJobFactory() throws Exception { + void schedulerWithSpringBeanJobFactory() throws Exception { DummyJob.param = 0; DummyJob.count = 0; @@ -206,7 +210,7 @@ public void schedulerWithSpringBeanJobFactory() throws Exception { @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerWithSpringBeanJobFactoryAndParamMismatchNotIgnored() throws Exception { + void schedulerWithSpringBeanJobFactoryAndParamMismatchNotIgnored() throws Exception { DummyJob.param = 0; DummyJob.count = 0; @@ -242,7 +246,7 @@ public void schedulerWithSpringBeanJobFactoryAndParamMismatchNotIgnored() throws @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerWithSpringBeanJobFactoryAndQuartzJobBean() throws Exception { + void schedulerWithSpringBeanJobFactoryAndQuartzJobBean() throws Exception { DummyJobBean.param = 0; DummyJobBean.count = 0; @@ -276,7 +280,7 @@ public void schedulerWithSpringBeanJobFactoryAndQuartzJobBean() throws Exception @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerWithSpringBeanJobFactoryAndJobSchedulingData() throws Exception { + void schedulerWithSpringBeanJobFactoryAndJobSchedulingData() throws Exception { DummyJob.param = 0; DummyJob.count = 0; @@ -294,7 +298,7 @@ public void schedulerWithSpringBeanJobFactoryAndJobSchedulingData() throws Excep } @Test // SPR-772 - public void multipleSchedulers() throws Exception { + void multipleSchedulers() throws Exception { try (ClassPathXmlApplicationContext ctx = context("multipleSchedulers.xml")) { Scheduler scheduler1 = (Scheduler) ctx.getBean("scheduler1"); Scheduler scheduler2 = (Scheduler) ctx.getBean("scheduler2"); @@ -305,7 +309,7 @@ public void multipleSchedulers() throws Exception { } @Test // SPR-16884 - public void multipleSchedulersWithQuartzProperties() throws Exception { + void multipleSchedulersWithQuartzProperties() throws Exception { try (ClassPathXmlApplicationContext ctx = context("multipleSchedulersWithQuartzProperties.xml")) { Scheduler scheduler1 = (Scheduler) ctx.getBean("scheduler1"); Scheduler scheduler2 = (Scheduler) ctx.getBean("scheduler2"); @@ -317,12 +321,13 @@ public void multipleSchedulersWithQuartzProperties() throws Exception { @Test @EnabledForTestGroups(LONG_RUNNING) - public void twoAnonymousMethodInvokingJobDetailFactoryBeans() throws Exception { - Thread.sleep(3000); + void twoAnonymousMethodInvokingJobDetailFactoryBeans() throws Exception { try (ClassPathXmlApplicationContext ctx = context("multipleAnonymousMethodInvokingJobDetailFB.xml")) { QuartzTestBean exportService = (QuartzTestBean) ctx.getBean("exportService"); QuartzTestBean importService = (QuartzTestBean) ctx.getBean("importService"); + Thread.sleep(400); + assertThat(exportService.getImportCount()).as("doImport called exportService").isEqualTo(0); assertThat(exportService.getExportCount()).as("doExport not called on exportService").isEqualTo(2); assertThat(importService.getImportCount()).as("doImport not called on importService").isEqualTo(2); @@ -332,12 +337,13 @@ public void twoAnonymousMethodInvokingJobDetailFactoryBeans() throws Exception { @Test @EnabledForTestGroups(LONG_RUNNING) - public void schedulerAccessorBean() throws Exception { - Thread.sleep(3000); + void schedulerAccessorBean() throws Exception { try (ClassPathXmlApplicationContext ctx = context("schedulerAccessorBean.xml")) { QuartzTestBean exportService = (QuartzTestBean) ctx.getBean("exportService"); QuartzTestBean importService = (QuartzTestBean) ctx.getBean("importService"); + Thread.sleep(400); + assertThat(exportService.getImportCount()).as("doImport called exportService").isEqualTo(0); assertThat(exportService.getExportCount()).as("doExport not called on exportService").isEqualTo(2); assertThat(importService.getImportCount()).as("doImport not called on importService").isEqualTo(2); @@ -347,7 +353,7 @@ public void schedulerAccessorBean() throws Exception { @Test @SuppressWarnings("resource") - public void schedulerAutoStartsOnContextRefreshedEventByDefault() throws Exception { + void schedulerAutoStartsOnContextRefreshedEventByDefault() throws Exception { StaticApplicationContext context = new StaticApplicationContext(); context.registerBeanDefinition("scheduler", new RootBeanDefinition(SchedulerFactoryBean.class)); Scheduler bean = context.getBean("scheduler", Scheduler.class); @@ -358,7 +364,7 @@ public void schedulerAutoStartsOnContextRefreshedEventByDefault() throws Excepti @Test @SuppressWarnings("resource") - public void schedulerAutoStartupFalse() throws Exception { + void schedulerAutoStartupFalse() throws Exception { StaticApplicationContext context = new StaticApplicationContext(); BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(SchedulerFactoryBean.class) .addPropertyValue("autoStartup", false).getBeanDefinition(); @@ -370,7 +376,7 @@ public void schedulerAutoStartupFalse() throws Exception { } @Test - public void schedulerRepositoryExposure() throws Exception { + void schedulerRepositoryExposure() throws Exception { try (ClassPathXmlApplicationContext ctx = context("schedulerRepositoryExposure.xml")) { assertThat(ctx.getBean("scheduler")).isSameAs(SchedulerRepository.getInstance().lookup("myScheduler")); } @@ -381,7 +387,7 @@ public void schedulerRepositoryExposure() throws Exception { * TODO: Against Quartz 2.2, this test's job doesn't actually execute anymore... */ @Test - public void schedulerWithHsqlDataSource() throws Exception { + void schedulerWithHsqlDataSource() throws Exception { DummyJob.param = 0; DummyJob.count = 0; @@ -396,12 +402,36 @@ public void schedulerWithHsqlDataSource() throws Exception { } } + @Test + @SuppressWarnings("resource") + void schedulerFactoryBeanWithCustomJobStore() throws Exception { + StaticApplicationContext context = new StaticApplicationContext(); + + String dbName = "mydb"; + EmbeddedDatabase database = new EmbeddedDatabaseBuilder().setName(dbName).build(); + + Properties properties = new Properties(); + properties.setProperty("org.quartz.jobStore.class", JobStoreTX.class.getName()); + properties.setProperty("org.quartz.jobStore.dataSource", dbName); + + BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(SchedulerFactoryBean.class) + .addPropertyValue("autoStartup", false) + .addPropertyValue("dataSource", database) + .addPropertyValue("quartzProperties", properties) + .getBeanDefinition(); + context.registerBeanDefinition("scheduler", beanDefinition); + + Scheduler scheduler = context.getBean(Scheduler.class); + + assertThat(scheduler.getMetaData().getJobStoreClass()).isEqualTo(JobStoreTX.class); + } + private ClassPathXmlApplicationContext context(String path) { return new ClassPathXmlApplicationContext(path, getClass()); } - public static class CountingTaskExecutor implements TaskExecutor { + private static class CountingTaskExecutor implements TaskExecutor { private int count; @@ -413,12 +443,14 @@ public void execute(Runnable task) { } - public static class DummyJob implements Job { + private static class DummyJob implements Job { private static int param; private static int count; + @SuppressWarnings("unused") + // Must be public public void setParam(int value) { if (param > 0) { throw new IllegalStateException("Param already set"); @@ -433,12 +465,13 @@ public synchronized void execute(JobExecutionContext jobExecutionContext) throws } - public static class DummyJobBean extends QuartzJobBean { + private static class DummyJobBean extends QuartzJobBean { private static int param; private static int count; + @SuppressWarnings("unused") public void setParam(int value) { if (param > 0) { throw new IllegalStateException("Param already set"); @@ -453,7 +486,7 @@ protected synchronized void executeInternal(JobExecutionContext jobExecutionCont } - public static class DummyRunnable implements Runnable { + private static class DummyRunnable implements Runnable { @Override public void run() { diff --git a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/multipleAnonymousMethodInvokingJobDetailFB.xml b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/multipleAnonymousMethodInvokingJobDetailFB.xml index e45ccd195fe..58e771f3ad4 100644 --- a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/multipleAnonymousMethodInvokingJobDetailFB.xml +++ b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/multipleAnonymousMethodInvokingJobDetailFB.xml @@ -19,7 +19,7 @@ - + @@ -30,7 +30,7 @@ - + diff --git a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerAccessorBean.xml b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerAccessorBean.xml index 0ba9c4a57a8..3634002664c 100644 --- a/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerAccessorBean.xml +++ b/spring-context-support/src/test/resources/org/springframework/scheduling/quartz/schedulerAccessorBean.xml @@ -21,7 +21,7 @@ - + @@ -32,7 +32,7 @@ - + diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index 0b06a7c56f0..fe3faea9f9e 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -4,10 +4,10 @@ apply plugin: "groovy" apply plugin: "kotlin" dependencies { - compile(project(":spring-aop")) - compile(project(":spring-beans")) - compile(project(":spring-core")) - compile(project(":spring-expression")) + api(project(":spring-aop")) + api(project(":spring-beans")) + api(project(":spring-core")) + api(project(":spring-expression")) optional(project(":spring-instrument")) optional("javax.annotation:javax.annotation-api") optional("javax.ejb:javax.ejb-api") @@ -26,22 +26,22 @@ dependencies { optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.jetbrains.kotlin:kotlin-stdlib") optional("org.reactivestreams:reactive-streams") - testCompile(testFixtures(project(":spring-aop"))) - testCompile(testFixtures(project(":spring-beans"))) - testCompile(testFixtures(project(":spring-core"))) - testCompile("io.projectreactor:reactor-core") - testCompile("org.codehaus.groovy:groovy-jsr223") - testCompile("org.codehaus.groovy:groovy-test") - testCompile("org.codehaus.groovy:groovy-xml") - testCompile("org.apache.commons:commons-pool2") - testCompile("javax.inject:javax.inject-tck") - testCompile("org.awaitility:awaitility") - testRuntime("javax.xml.bind:jaxb-api") - testRuntime("org.glassfish:javax.el") + testImplementation(testFixtures(project(":spring-aop"))) + testImplementation(testFixtures(project(":spring-beans"))) + testImplementation(testFixtures(project(":spring-core"))) + testImplementation("io.projectreactor:reactor-core") + testImplementation("org.codehaus.groovy:groovy-jsr223") + testImplementation("org.codehaus.groovy:groovy-test") + testImplementation("org.codehaus.groovy:groovy-xml") + testImplementation("org.apache.commons:commons-pool2") + testImplementation("org.awaitility:awaitility") + testImplementation("javax.inject:javax.inject-tck") + testRuntimeOnly("javax.xml.bind:jaxb-api") + testRuntimeOnly("org.glassfish:javax.el") // Substitute for javax.management:jmxremote_optional:1.0.1_04 (not available on Maven Central) - testRuntime("org.glassfish.external:opendmk_jmxremote_optional_jar") - testRuntime("org.javamoney:moneta") - testRuntime("org.junit.vintage:junit-vintage-engine") // for @Inject TCK + testRuntimeOnly("org.glassfish.external:opendmk_jmxremote_optional_jar") + testRuntimeOnly("org.javamoney:moneta") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine") // for @Inject TCK testFixturesApi("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation(testFixtures(project(":spring-beans"))) testFixturesImplementation("com.google.code.findbugs:jsr305") diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java index c49d8d20c6e..d6473e28e67 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -16,9 +16,12 @@ package org.springframework.cache.annotation; -import java.util.Collection; +import java.util.List; +import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.interceptor.CacheErrorHandler; @@ -30,6 +33,7 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; +import org.springframework.util.function.SingletonSupplier; /** * Abstract base {@code @Configuration} class providing common structure @@ -63,36 +67,67 @@ public abstract class AbstractCachingConfiguration implements ImportAware { @Override public void setImportMetadata(AnnotationMetadata importMetadata) { this.enableCaching = AnnotationAttributes.fromMap( - importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false)); + importMetadata.getAnnotationAttributes(EnableCaching.class.getName())); if (this.enableCaching == null) { throw new IllegalArgumentException( "@EnableCaching is not present on importing class " + importMetadata.getClassName()); } } - @Autowired(required = false) - void setConfigurers(Collection configurers) { - if (CollectionUtils.isEmpty(configurers)) { - return; - } - if (configurers.size() > 1) { - throw new IllegalStateException(configurers.size() + " implementations of " + - "CachingConfigurer were found when only 1 was expected. " + - "Refactor the configuration such that CachingConfigurer is " + - "implemented only once or not at all."); - } - CachingConfigurer configurer = configurers.iterator().next(); - useCachingConfigurer(configurer); + @Autowired + void setConfigurers(ObjectProvider configurers) { + Supplier configurer = () -> { + List candidates = configurers.stream().collect(Collectors.toList()); + if (CollectionUtils.isEmpty(candidates)) { + return null; + } + if (candidates.size() > 1) { + throw new IllegalStateException(candidates.size() + " implementations of " + + "CachingConfigurer were found when only 1 was expected. " + + "Refactor the configuration such that CachingConfigurer is " + + "implemented only once or not at all."); + } + return candidates.get(0); + }; + useCachingConfigurer(new CachingConfigurerSupplier(configurer)); } /** * Extract the configuration from the nominated {@link CachingConfigurer}. */ - protected void useCachingConfigurer(CachingConfigurer config) { - this.cacheManager = config::cacheManager; - this.cacheResolver = config::cacheResolver; - this.keyGenerator = config::keyGenerator; - this.errorHandler = config::errorHandler; + protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) { + this.cacheManager = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheManager); + this.cacheResolver = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheResolver); + this.keyGenerator = cachingConfigurerSupplier.adapt(CachingConfigurer::keyGenerator); + this.errorHandler = cachingConfigurerSupplier.adapt(CachingConfigurer::errorHandler); + } + + + protected static class CachingConfigurerSupplier { + + private final Supplier supplier; + + public CachingConfigurerSupplier(Supplier supplier) { + this.supplier = SingletonSupplier.of(supplier); + } + + /** + * Adapt the {@link CachingConfigurer} supplier to another supplier + * provided by the specified mapping function. If the underlying + * {@link CachingConfigurer} is {@code null}, {@code null} is returned + * and the mapping function is not invoked. + * @param provider the provider to use to adapt the supplier + * @param the type of the supplier + * @return another supplier mapped by the specified function + */ + @Nullable + public Supplier adapt(Function provider) { + return () -> { + CachingConfigurer cachingConfigurer = this.supplier.get(); + return (cachingConfigurer != null ? provider.apply(cachingConfigurer) : null); + }; + } + } } diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java index 8743df7e668..abf47bfd0fa 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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. @@ -31,7 +31,8 @@ * *

    In contrast to the {@link Cacheable @Cacheable} annotation, this annotation * does not cause the advised method to be skipped. Rather, it always causes the - * method to be invoked and its result to be stored in the associated cache. Note + * method to be invoked and its result to be stored in the associated cache if the + * {@link #condition()} and {@link #unless()} expressions match accordingly. Note * that Java8's {@code Optional} return types are automatically handled and its * content is stored in the cache if present. * @@ -118,10 +119,15 @@ /** * Spring Expression Language (SpEL) expression used for making the cache * put operation conditional. + *

    This expression is evaluated after the method has been called due to the + * nature of the put operation and can therefore refer to the {@code result}. *

    Default is {@code ""}, meaning the method result is always cached. *

    The SpEL expression evaluates against a dedicated context that provides the * following meta-data: *